String IDs.

pull/24/head
Sander Vocke 5 years ago
parent cdccfc4551
commit 52865497cd
  1. 1246
      client/package-lock.json
  2. 5
      client/package.json
  3. 18
      client/src/components/windows/manage_tags/ManageTagMenu.tsx
  4. 40
      client/src/components/windows/manage_tags/ManageTagsWindow.tsx
  5. 44
      client/src/components/windows/manage_tags/TagChange.tsx

File diff suppressed because it is too large Load Diff

@ -24,8 +24,9 @@
"react-dnd-html5-backend": "^11.1.3", "react-dnd-html5-backend": "^11.1.3",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.1", "react-scripts": "^3.4.3",
"typescript": "~3.7.2" "typescript": "~3.7.2",
"uuid": "^8.3.0"
}, },
"scripts": { "scripts": {
"dev": "BROWSER=none react-scripts start", "dev": "BROWSER=none react-scripts start",

@ -29,7 +29,7 @@ export function PickTag(props: {
tags: any[] tags: any[]
open: boolean open: boolean
root: boolean root: boolean
onPick: (v: number | null) => void onPick: (v: string | null) => void
}) { }) {
return <> return <>
@ -39,28 +39,27 @@ export function PickTag(props: {
return <NestedMenuItem return <NestedMenuItem
parentMenuOpen={props.open} parentMenuOpen={props.open}
label={tag.name} label={tag.name}
onClick={() => props.onPick(tag.tagId)} onClick={() => props.onPick(tag.tagId.toString())}
> >
<PickTag tags={tag.children} open={props.open} root={false} onPick={props.onPick} /> <PickTag tags={tag.children} open={props.open} root={false} onPick={props.onPick} />
</NestedMenuItem> </NestedMenuItem>
} }
return <MenuItem onClick={() => props.onPick(tag.tagId)}>{tag.name}</MenuItem> return <MenuItem onClick={() => props.onPick(tag.tagId.toString())}>{tag.name}</MenuItem>
}) })
}</> }</>
} }
export interface IProps { export default function ManageTagMenu(props: {
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, onMove: (to: string | null) => void,
onCreateChild: (name: string) => void,
tag: any, tag: any,
changedTags: any[], // Tags organized hierarchically with "children" fields changedTags: any[], // Tags organized hierarchically with "children" fields
} }) {
export default function ManageTagMenu(props: IProps) {
const pos = props.open && props.position ? const pos = props.open && props.position ?
{ left: props.position[0], top: props.position[1] } { left: props.position[0], top: props.position[1] }
: { left: 0, top: 0 } : { left: 0, top: 0 }
@ -94,8 +93,7 @@ export default function ManageTagMenu(props: IProps) {
parentMenuOpen={props.open} parentMenuOpen={props.open}
label="Move to" label="Move to"
> >
<PickTag tags={props.changedTags} open={props.open} root={true} onPick={(v: number | null) => { <PickTag tags={props.changedTags} open={props.open} root={true} onPick={(v: string | null) => {
console.log("onPick:", v)
props.onClose(); props.onClose();
props.onMove(v); props.onMove(v);
}} /> }} />

@ -11,7 +11,10 @@ import { queryTags } from '../../../lib/query/Backend';
var _ = require('lodash'); var _ = require('lodash');
export interface ManageTagsWindowState extends WindowState { export interface ManageTagsWindowState extends WindowState {
fetchedTags: Record<number, any> | null, // Tags are indexed by a string ID. This can be a stringified MuDBase ID integer,
// or a UID for tags which only exist in the front-end and haven't been committed
// to the database.
fetchedTags: Record<string, any> | null,
pendingChanges: TagChange[], pendingChanges: TagChange[],
} }
@ -37,7 +40,7 @@ export function ManageTagsWindowReducer(state: ManageTagsWindowState, action: an
} }
} }
export function organiseTags(allTags: Record<number, any>, fromId: number | null): any[] { export function organiseTags(allTags: Record<string, any>, fromId: string | null): any[] {
const base = Object.values(allTags).filter((tag: any) => { const base = Object.values(allTags).filter((tag: any) => {
var par: any = ("proposedParent" in tag) ? tag.proposedParent : tag.parentId; var par: any = ("proposedParent" in tag) ? tag.proposedParent : tag.parentId;
@ -55,14 +58,21 @@ export function organiseTags(allTags: Record<number, any>, fromId: number | null
export async function getAllTags() { export async function getAllTags() {
return (async () => { return (async () => {
var retval: Record<number, any> = {}; var retval: Record<string, any> = {};
const tags = await queryTags({ const tags = await queryTags({
query: undefined, query: undefined,
offset: 0, offset: 0,
limit: -1, limit: -1,
}); });
// Convert numeric IDs to string IDs because that is
// what we work with within this component.
tags.forEach((tag: any) => { tags.forEach((tag: any) => {
retval[tag.tagId] = tag; retval[tag.tagId.toString()] = {
...tag,
tagId: tag.tagId && tag.tagId.toString(),
parentId: tag.parentId && tag.parentId.toString(),
childIds: tag.childIds && tag.childIds.map((c: number) => c.toString()),
}
}); });
return retval; return retval;
})(); })();
@ -156,7 +166,7 @@ export function SingleTag(props: {
] ]
}) })
}} }}
onMove={(to: number | null) => { onMove={(to: string | null) => {
props.dispatch({ props.dispatch({
type: ManageTagsWindowActions.SetPendingChanges, type: ManageTagsWindowActions.SetPendingChanges,
value: [ value: [
@ -168,6 +178,9 @@ export function SingleTag(props: {
} }
] ]
}) })
}}
onCreateChild={(name: string) => {
}} }}
tag={tag} tag={tag}
changedTags={props.changedTags} changedTags={props.changedTags}
@ -175,10 +188,10 @@ export function SingleTag(props: {
</> </>
} }
function annotateTagsWithChanges(tags: Record<number, any>, changes: TagChange[]) { function annotateTagsWithChanges(tags: Record<string, any>, changes: TagChange[]) {
var retval = _.cloneDeep(tags); var retval: Record<string, any> = tags;
const applyDelete = (id: number) => { const applyDelete = (id: string) => {
retval[id].proposeDelete = true; retval[id].proposeDelete = true;
Object.values(tags).filter((t: any) => t.parentId === id) Object.values(tags).filter((t: any) => t.parentId === id)
.forEach((child: any) => applyDelete(child.tagId)); .forEach((child: any) => applyDelete(child.tagId));
@ -202,10 +215,10 @@ function annotateTagsWithChanges(tags: Record<number, any>, changes: TagChange[]
return retval; return retval;
} }
function applyTagsChanges(tags: Record<number, any>, changes: TagChange[]) { function applyTagsChanges(tags: Record<string, any>, changes: TagChange[]) {
var retval = _.cloneDeep(tags); var retval = _.cloneDeep(tags);
const applyDelete = (id: number) => { const applyDelete = (id: string) => {
Object.values(tags).filter((t: any) => t.parentId === id) Object.values(tags).filter((t: any) => t.parentId === id)
.forEach((child: any) => applyDelete(child.tagId)); .forEach((child: any) => applyDelete(child.tagId));
delete retval[id].proposeDelete; delete retval[id].proposeDelete;
@ -230,13 +243,11 @@ function applyTagsChanges(tags: Record<number, any>, changes: TagChange[]) {
return retval; return retval;
} }
export interface IProps { export default function ManageTagsWindow(props: {
state: ManageTagsWindowState, state: ManageTagsWindowState,
dispatch: (action: any) => void, dispatch: (action: any) => void,
mainDispatch: (action: any) => void, mainDispatch: (action: any) => void,
} }) {
export default function ManageTagsWindow(props: IProps) {
useEffect(() => { useEffect(() => {
if (props.state.fetchedTags !== null) { if (props.state.fetchedTags !== null) {
return; return;
@ -285,6 +296,7 @@ export default function ManageTagsWindow(props: IProps) {
value: [], value: [],
})} })}
onSave={() => { }} onSave={() => { }}
getTagDetails={(id: string) => tagsWithChanges[id]}
/> />
</Box>} </Box>}
<Box <Box

@ -15,48 +15,19 @@ 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: string, // Stringified integer == MuDBase ID. Other string == not yet committed to DB.
parent?: number | null, // MuDBase ID. If not in database yet, negative IDs will be used until submitted. parent?: string | null, // Stringified integer == MuDBase ID. Other string == not yet committed to DB.
// null refers to the tags root. // null refers to the tags root.
name?: string, name?: string,
} }
export async function getTag(id: number) {
return (await queryTags({
query: {
a: QueryLeafBy.TagId,
b: id,
leafOp: QueryLeafOp.Equals,
},
offset: 0,
limit: 1,
}))[0];
}
export function TagChangeDisplay(props: { export function TagChangeDisplay(props: {
change: TagChange, change: TagChange,
getTagDetails: (id: string) => any,
}) { }) {
const [tag, setTag] = useState<any>(undefined); const tag = props.getTagDetails(props.change.id);
const [oldParent, setOldParent] = useState<any>(undefined); const oldParent = tag.parentId ? props.getTagDetails(tag.parentId.toString()) : null;
const [newParent, setNewParent] = useState<any>(undefined); const newParent = props.change.parent ? props.getTagDetails(props.change.parent) : null;
useEffect(() => {
getTag(props.change.id).then((tag: any) => {
if (tag.parentId) {
getTag(tag.parentId).then((parent: any) => setOldParent(parent));
} else {
setOldParent(null);
}
setTag(tag);
})
if (props.change.type === TagChangeType.MoveTo) {
if (props.change.parent) {
getTag(props.change.parent).then((tag: any) => setNewParent(tag));
} else {
setNewParent(null);
}
}
}, [props.change.id]);
const MakeTag = (props: { name: string }) => <Chip label={props.name} /> const MakeTag = (props: { name: string }) => <Chip label={props.name} />
const MainTag = tag ? const MainTag = tag ?
@ -88,6 +59,7 @@ export default function ControlTagChanges(props: {
changes: TagChange[], changes: TagChange[],
onSave: () => void, onSave: () => void,
onDiscard: () => void, onDiscard: () => void,
getTagDetails: (id: string) => any,
}) { }) {
return <Box display="flex"><Paper style={{ padding: 10, minWidth: 0 }}> return <Box display="flex"><Paper style={{ padding: 10, minWidth: 0 }}>
<Typography variant="h5">Pending changes</Typography> <Typography variant="h5">Pending changes</Typography>
@ -95,7 +67,7 @@ export default function ControlTagChanges(props: {
{props.changes.map((change: any) => {props.changes.map((change: any) =>
<Box display="flex"> <Box display="flex">
<Typography>-&nbsp;</Typography> <Typography>-&nbsp;</Typography>
<TagChangeDisplay change={change} /> <TagChangeDisplay change={change} getTagDetails={props.getTagDetails} />
</Box> </Box>
)} )}
</Box> </Box>

Loading…
Cancel
Save