feature (tagging): prepare the ground for a new tagging feature

This commit is contained in:
Mickael Kerjean 2022-10-06 23:30:29 +11:00
parent 9f083ea94c
commit 9a6e24c296
13 changed files with 124 additions and 18 deletions

View file

@ -1,6 +1,7 @@
import React from "react";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { URL_FILES } from "../helpers/";
import { NgIf, Icon, EventEmitter } from "./";
import ReactCSSTransitionGroup from "react-addons-css-transition-group";
@ -65,6 +66,7 @@ export class BreadCrumb extends React.Component {
this.state.path.map((path, index) => {
return (
<Path key={"breadcrumb_"+index}
baseURL={index === 0 ? URL_FILES : this.props.baseURL || URL_FILES}
currentSelection={this.props.currentSelection} path={path}
isLast={this.state.path.length === index + 1}
needSaving={this.props.needSaving} />
@ -157,7 +159,7 @@ class PathElementWrapperComponent extends React.Component {
className += " highlight";
}
let href = "/files" + (this.props.path.full || "");
let href = this.props.baseURL + (this.props.path.full || "");
href = href
.replace(/\%/g, "%2525") // Hack to get the Link Component to work
// See ExistingThing in 'thing-existing.js'

View file

@ -1,6 +1,6 @@
export {
URL_HOME, goToHome, URL_FILES, goToFiles, URL_VIEWER, goToViewer, URL_LOGIN,
goToLogin, URL_LOGOUT, goToLogout, urlParams, URL_ADMIN, URL_SHARE,
goToLogin, URL_LOGOUT, goToLogout, urlParams, URL_ADMIN, URL_SHARE, URL_TAGS,
} from "./navigate";
export { opener } from "./mimetype";
export { debounce, throttle } from "./backpressure";

View file

@ -29,6 +29,8 @@ export function goToLogout(history) {
return Promise.resolve("ok");
}
export const URL_TAGS = "/tags";
export const URL_ADMIN = "/admin";
export const URL_SHARE = "/s";

View file

@ -5,3 +5,4 @@ export { Config, Backend, Middleware } from "./config";
export { Log } from "./log";
export { Admin } from "./admin";
export { Audit } from "./audit";
export { Tags } from "./tags";

18
client/model/tags.js Normal file
View file

@ -0,0 +1,18 @@
// This is a WIP: tags are stored in indexedDB like this:
// {
// tag: "name",
// path: "/test/"
// share: "",
// backend: "__hash__",
// }
class TagManager {
getAll(path = "/") {
// TODO: path could be: /tagA/tagB/tagC
// meaning we get whatever is matching tagA,B,C
// return Promise.resolve(["project", "test"])
return Promise.resolve([]);
}
}
export const Tags = new TagManager();

View file

@ -5,7 +5,7 @@ import { SelectableGroup } from "react-selectable";
import "./filespage.scss";
import "./error.scss";
import { Files } from "../model/";
import { Files, Tags } from "../model/";
import {
sort, onCreate, onRename, onMultiRename, onDelete, onMultiDelete,
onMultiDownload, onUpload, onSearch,
@ -44,6 +44,7 @@ export class FilesPageComponent extends React.Component {
selected: [],
metadata: null,
frequents: null,
tags: null,
page_number: PAGE_NUMBER_INIT,
loading: true,
};
@ -149,7 +150,10 @@ export class FilesPageComponent extends React.Component {
}, (error) => this.props.error(error));
this.observers.push(observer);
if (path === "/") {
Files.frequents().then((s) => this.setState({ frequents: s }));
Promise.all([Files.frequents(), Tags.getAll()])
.then(([s, t]) => {
this.setState({ frequents: s, tags: t });
});
}
}
@ -279,7 +283,7 @@ export class FilesPageComponent extends React.Component {
className="container"
cond={!!this.state.is_search || !this.state.loading}>
<NgIf cond={this.state.path === "/" && window.self === window.top}>
<FrequentlyAccess files={this.state.frequents} />
<FrequentlyAccess tags={this.state.tags} files={this.state.frequents} />
</NgIf>
<Submenu
path={this.state.path}

View file

@ -1,14 +1,20 @@
import React from "react";
import { Link } from "react-router-dom";
import ReactCSSTransitionGroup from "react-addons-css-transition-group";
import { Container, Icon, NgIf } from "../../components/";
import { Link } from "react-router-dom";
import { URL_TAGS, URL_FILES } from "../../helpers/";
import Path from "path";
import { t } from "../../locales/";
import "./frequently_access.scss";
export function FrequentlyAccess({ files }) {
export function FrequentlyAccess({ files, tags }) {
let showPlaceholder = true;
if (files === null || tags === null) showPlaceholder = false;
else if (files && files.length > 0) showPlaceholder = false;
else if(tags && tags.length > 0) showPlaceholder = false;
return (
<div className="component_frequently-access">
<ReactCSSTransitionGroup
@ -24,7 +30,7 @@ export function FrequentlyAccess({ files }) {
return (
<Link
key={path}
to={"/files"+path+window.location.search}>
to={URL_FILES+path+window.location.search}>
<Icon name={"directory"} />
<div>{Path.basename(path)}</div>
</Link>
@ -33,8 +39,25 @@ export function FrequentlyAccess({ files }) {
}
</div>
</NgIf>
<NgIf cond={!!tags && tags.length > 0}>
<Link className="caption" to={URL_TAGS}>{t("Tag")}</Link>
<div className="frequent_wrapper">
{
tags && tags.map((tag, index) => {
return (
<Link
key={tag}
to={"/tags/" + tag}>
<Icon name={"directory"} />
<div>{tag}</div>
</Link>
);
})
}
</div>
</NgIf>
<NgIf
cond={!!files && files.length === 0}
cond={showPlaceholder}
className="nothing_placeholder">
<svg aria-hidden="true" focusable="false" data-icon="layer-group" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M12.41 148.02l232.94 105.67c6.8 3.09 14.49 3.09 21.29 0l232.94-105.67c16.55-7.51 16.55-32.52 0-40.03L266.65 2.31a25.607 25.607 0 0 0-21.29 0L12.41 107.98c-16.55 7.51-16.55 32.53 0 40.04zm487.18 88.28l-58.09-26.33-161.64 73.27c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.51 209.97l-58.1 26.33c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 276.3c16.55-7.5 16.55-32.5 0-40zm0 127.8l-57.87-26.23-161.86 73.37c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.29 337.87 12.41 364.1c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 404.1c16.55-7.5 16.55-32.5 0-40z"></path>

View file

@ -1,6 +1,6 @@
.component_frequently-access{
min-height: 110px;
margin-top: 5px;
min-height: 145px;
padding: 0 0 7px 0;
.component_container{
padding: 0;
}
@ -8,6 +8,7 @@
height: 25px;
}
.caption{
margin-top: 15px;
letter-spacing: 0.06em;
display: inline-block;
text-transform: uppercase;
@ -46,19 +47,21 @@
@media (max-width: 450px){ a:nth-child(3){display: none;} }
}
.nothing_placeholder{
padding: 15px;
padding: 20px;
text-align: center;
background: var(--super-light);
border: 1px dashed rgba(0,0,0,0.1);
border-radius: 2px;
color: var(--light);
font-weight: 100;
svg{
svg {
display: block;
width: 45px;
margin: 0 auto;
padding-bottom: 5px;
}
position: relative;
top: 25px;
}
}

View file

@ -5,3 +5,4 @@ export { LogoutPage } from "./logout";
export { NotFoundPage } from "./notfoundpage";
export { FilesPage } from "./filespage";
export { ViewerPage } from "./viewerpage";
export { TagsPage } from "./tagspage";

49
client/pages/tagspage.js Normal file
View file

@ -0,0 +1,49 @@
import React from "react";
import { Redirect } from "react-router";
import { NgIf, NgShow, Loader, LoggedInOnly, BreadCrumb, Card } from "../components/";
import { URL_TAGS } from "../helpers/";
import { t } from "../locales/";
import "./filespage.scss";
export function TagsPageComponent() {
const loading = false;
const files = [1,2,3,4,5,6];
let path = (decodeURIComponent(location.pathname).replace(URL_TAGS, "") || "/" );
if (path == "/") {
return ( <Redirect to={URL_TAGS + "/tags/"} /> );
}
console.log(path)
return (
<div className="component_page_filespage">
<BreadCrumb className="breadcrumb" path={path} baseURL={URL_TAGS} />
<div className="page_container">
<div className="scroll-y">
<NgShow className="container" cond={!loading}>
<NgIf cond={!loading}>
{
files.map((file, idx) => {
return (
<div className={"component_thing"} key={idx}>
<Card>
{ file }
</Card>
</div>
);
})
}
</NgIf>
</NgShow>
</div>
<NgIf cond={loading}>
<Loader/>
</NgIf>
</div>
</div>
);
}
export const TagsPage = LoggedInOnly(
TagsPageComponent,
);

View file

@ -2,11 +2,11 @@ import React, { Suspense } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import {
NotFoundPage, ConnectPage, HomePage, SharePage, LogoutPage,
FilesPage, ViewerPage,
FilesPage, ViewerPage, TagsPage,
} from "./pages/";
import {
URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT,
URL_ADMIN, URL_SHARE,
URL_ADMIN, URL_SHARE, URL_TAGS,
} from "./helpers/";
import {
ModalPrompt, ModalAlert, ModalConfirm, Notification, UploadQueue,
@ -31,6 +31,7 @@ export default function AppRouter() {
<Route path={URL_LOGIN} component={ConnectPage} />
<Route path={`${URL_FILES}/:path*`} component={FilesPage} />
<Route path={`${URL_VIEWER}/:path*`} component={ViewerPage} />
<Route path={`${URL_TAGS}/:path*`} component={TagsPage} />
<Route path={URL_LOGOUT} component={LogoutPage} />
<Route path={URL_ADMIN} component={AdminPage} />
<Route component={NotFoundPage} />

View file

@ -17,8 +17,9 @@ import (
)
type Session struct {
Home *string `json:"home,omitempty"`
IsAuth bool `json:"is_authenticated"`
Home *string `json:"home,omitempty"`
IsAuth bool `json:"is_authenticated"`
Backend string `json:"backendID"`
}
func SessionGet(ctx *App, res http.ResponseWriter, req *http.Request) {
@ -36,6 +37,7 @@ func SessionGet(ctx *App, res http.ResponseWriter, req *http.Request) {
}
r.IsAuth = true
r.Home = NewString(home)
r.Backend = GenerateID(ctx)
SendSuccessResult(res, r)
}

View file

@ -43,7 +43,7 @@ func IndexHandler(_path string) func(*App, http.ResponseWriter, *http.Request) {
return
} else if url != "/" && strings.HasPrefix(url, "/s/") == false &&
strings.HasPrefix(url, "/view/") == false && strings.HasPrefix(url, "/files/") == false &&
url != "/login" && url != "/logout" && strings.HasPrefix(url, "/admin") == false {
url != "/login" && url != "/logout" && strings.HasPrefix(url, "/admin") == false && strings.HasPrefix(url, "/tags") == false {
NotFoundHandler(ctx, res, req)
return
}