Can move tags around.

pull/24/head
Sander Vocke 5 years ago
parent 3d00b5b6ca
commit e81f61637f
  1. 2
      client/src/components/appbar/AddTabMenu.tsx
  2. 37
      client/src/components/windows/manage_tags/ManageTagMenu.tsx
  3. 70
      client/src/components/windows/manage_tags/ManageTagsWindow.tsx

@ -35,6 +35,6 @@ export default function AddTabMenu(props: IProps) {
windowType: WindowType.ManageTags, windowType: WindowType.ManageTags,
}) })
}} }}
>{WindowType.ManageTags}</MenuItem> >Manage Tags</MenuItem>
</Menu> </Menu>
} }

@ -25,13 +25,39 @@ export function MenuEditText(props: {
/> />
} }
export function PickTag(props: {
tags: any[]
open: boolean
root: boolean
onPick: (v: number | null) => void
}) {
return <>
{props.root && <MenuItem onClick={() => props.onPick(null)}>/</MenuItem>}
{props.tags.map((tag: any) => {
if ('children' in tag && tag.children.length > 0) {
return <NestedMenuItem
parentMenuOpen={props.open}
label={tag.name}
onClick={() => props.onPick(tag.tagId)}
>
<PickTag tags={tag.children} open={props.open} root={false} onPick={props.onPick} />
</NestedMenuItem>
}
return <MenuItem onClick={() => props.onPick(tag.tagId)}>{tag.name}</MenuItem>
})
}</>
}
export interface IProps { export interface IProps {
position: null | number[], position: null | number[],
open: boolean, open: boolean,
onClose: () => void, onClose: () => void,
onRename: (s: string) => void, onRename: (s: string) => void,
onDelete: () => void, onDelete: () => void,
onMove: (to: number | null) => void,
tag: any, tag: any,
changedTags: any[], // Tags organized hierarchically with "children" fields
} }
export default function ManageTagMenu(props: IProps) { export default function ManageTagMenu(props: IProps) {
@ -64,6 +90,15 @@ export default function ManageTagMenu(props: IProps) {
}} }}
/> />
</NestedMenuItem> </NestedMenuItem>
<NestedMenuItem
parentMenuOpen={props.open}
label="Move to"
>
<PickTag tags={props.changedTags} open={props.open} root={true} onPick={(v: number | null) => {
console.log("onPick:", v)
props.onClose();
props.onMove(v);
}} />
</NestedMenuItem>
</Menu> </Menu>
} }

@ -19,7 +19,8 @@ export enum TagChangeType {
export interface TagChange { export interface TagChange {
type: TagChangeType, type: TagChangeType,
id: number, // MuDBase ID. If not in database yet, negative IDs will be used until submitted. id: number, // MuDBase ID. If not in database yet, negative IDs will be used until submitted.
parent?: number, // MuDBase ID. If not in database yet, negative IDs will be used until submitted. parent?: number | null, // MuDBase ID. If not in database yet, negative IDs will be used until submitted.
// null refers to the tags root.
name?: string, name?: string,
} }
@ -51,10 +52,12 @@ export function ManageTagsWindowReducer(state: ManageTagsWindowState, action: an
} }
export function organiseTags(allTags: Record<number, any>, fromId: number | null): any[] { export function organiseTags(allTags: Record<number, any>, fromId: number | null): any[] {
const base = Object.values(allTags).filter((tag: any) => const base = Object.values(allTags).filter((tag: any) => {
(fromId === null && !tag.parentId) || var par: any = ("proposedParent" in tag) ? tag.proposedParent : tag.parentId;
(tag.parentId && tag.parentId === fromId)
); return (fromId === null && !par) ||
(par && par === fromId)
});
return base.map((tag: any) => { return base.map((tag: any) => {
return { return {
@ -102,6 +105,7 @@ export function SingleTag(props: {
prependElems: any[], prependElems: any[],
dispatch: (action: any) => void, dispatch: (action: any) => void,
state: ManageTagsWindowState, state: ManageTagsWindowState,
changedTags: any[],
}) { }) {
const tag = props.tag; const tag = props.tag;
const hasChildren = 'children' in tag && tag.children.length > 0; const hasChildren = 'children' in tag && tag.children.length > 0;
@ -153,6 +157,7 @@ export function SingleTag(props: {
<Typography variant="h5">/</Typography>]} <Typography variant="h5">/</Typography>]}
dispatch={props.dispatch} dispatch={props.dispatch}
state={props.state} state={props.state}
changedTags={props.changedTags}
/>)} />)}
<ManageTagMenu <ManageTagMenu
position={menuPos} position={menuPos}
@ -183,13 +188,27 @@ export function SingleTag(props: {
] ]
}) })
}} }}
onMove={(to: number | null) => {
props.dispatch({
type: ManageTagsWindowActions.SetPendingChanges,
value: [
...props.state.pendingChanges,
{
type: TagChangeType.MoveTo,
id: tag.tagId,
parent: to,
}
]
})
}}
tag={tag} tag={tag}
changedTags={props.changedTags}
/> />
</> </>
} }
function addTagChanges(tags: Record<number, any>, changes: TagChange[]) { function annotateTagsWithChanges(tags: Record<number, any>, changes: TagChange[]) {
var retval = tags; var retval = _.cloneDeep(tags);
const applyDelete = (id: number) => { const applyDelete = (id: number) => {
retval[id].proposeDelete = true; retval[id].proposeDelete = true;
@ -205,6 +224,37 @@ function addTagChanges(tags: Record<number, any>, changes: TagChange[]) {
case TagChangeType.Delete: case TagChangeType.Delete:
applyDelete(change.id); applyDelete(change.id);
break; break;
case TagChangeType.MoveTo:
retval[change.id].proposedParent = change.parent;
break;
default:
throw new Error("Unimplemented tag change")
}
})
return retval;
}
function applyTagsChanges(tags: Record<number, any>, changes: TagChange[]) {
var retval = _.cloneDeep(tags);
const applyDelete = (id: number) => {
Object.values(tags).filter((t: any) => t.parentId === id)
.forEach((child: any) => applyDelete(child.tagId));
delete retval[id].proposeDelete;
}
changes.forEach((change: TagChange) => {
switch (change.type) {
case TagChangeType.Rename:
retval[change.id].name = change.name;
break;
case TagChangeType.Delete:
applyDelete(change.id);
break;
case TagChangeType.MoveTo:
retval[change.id].parentId = change.parent;
if (change.parent === null) { delete retval[change.id].parentId; }
break;
default: default:
throw new Error("Unimplemented tag change") throw new Error("Unimplemented tag change")
} }
@ -234,7 +284,10 @@ export default function ManageTagsWindow(props: IProps) {
})(); })();
}, [props.state.fetchedTags]); }, [props.state.fetchedTags]);
const tagsWithChanges = addTagChanges(props.state.fetchedTags || {}, props.state.pendingChanges) const tagsWithChanges = annotateTagsWithChanges(props.state.fetchedTags || {}, props.state.pendingChanges)
const changedTags = organiseTags(
applyTagsChanges(props.state.fetchedTags || {}, props.state.pendingChanges),
null);
const tags = organiseTags(tagsWithChanges, null); const tags = organiseTags(tagsWithChanges, null);
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap">
@ -263,6 +316,7 @@ export default function ManageTagsWindow(props: IProps) {
prependElems={[]} prependElems={[]}
dispatch={props.dispatch} dispatch={props.dispatch}
state={props.state} state={props.state}
changedTags={changedTags}
/>; />;
})} })}
</Box> </Box>

Loading…
Cancel
Save