Skip to content

Commit

Permalink
feat: add notes to graph part details tab
Browse files Browse the repository at this point in the history
  • Loading branch information
keyserj committed Oct 26, 2023
1 parent aaece48 commit 275c70d
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/common/edge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const edgeSchema = z.object({
topicId: z.number(),
arguedDiagramPartId: z.string().uuid().nullable(),
type: zRelationNames,
notes: z.string().max(10000),
sourceId: z.string().uuid(),
targetId: z.string().uuid(),
});
Expand Down
2 changes: 2 additions & 0 deletions src/common/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const nodeSchema = z.object({
arguedDiagramPartId: z.string().uuid().nullable(),
type: zNodeTypes,
text: z.string().max(200),
notes: z.string().max(10000),
});

export type Node = z.infer<typeof nodeSchema>;
Expand All @@ -35,5 +36,6 @@ export const getNewTopicProblemNode = (topicId: number, topicTitle: string): Nod
arguedDiagramPartId: null,
type: "problem",
text: lowerCase(topicTitle),
notes: "",
};
};
87 changes: 70 additions & 17 deletions src/web/topic/components/Surface/GraphPartDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,91 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Timeline } from "@mui/icons-material";
import { Divider, List, ListItem, ListItemIcon, ListItemText } from "@mui/material";
import { Divider, List, ListItem, ListItemIcon, ListItemText, TextField } from "@mui/material";
import { useForm } from "react-hook-form";
import { z } from "zod";

import { nodeSchema } from "../../../../common/node";
import { useSessionUser } from "../../../common/hooks";
import { setGraphPartNotes } from "../../store/actions";
import { useUserCanEditTopicData } from "../../store/userHooks";
import { GraphPart, isNode } from "../../utils/diagram";
import { nodeDecorations } from "../../utils/node";
import { StandaloneEdge } from "../Edge/StandaloneEdge";
import { EditableNode } from "../Node/EditableNode";

const formSchema = () => {
return z.object({
// same restrictions as edge, so we should be fine reusing node's schema
notes: nodeSchema.shape.notes,
});
};
type FormData = z.infer<ReturnType<typeof formSchema>>;

interface Props {
graphPart: GraphPart;
}

export const GraphPartDetails = ({ graphPart }: Props) => {
const { sessionUser } = useSessionUser();
const userCanEditTopicData = useUserCanEditTopicData(sessionUser?.username);

const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
mode: "onBlur",
reValidateMode: "onBlur",
resolver: zodResolver(formSchema()),
defaultValues: {
notes: graphPart.data.notes,
},
});

const partIsNode = isNode(graphPart);
const GraphPartIcon = partIsNode ? nodeDecorations[graphPart.type].NodeIcon : Timeline;
const headerText = partIsNode
? `${nodeDecorations[graphPart.type].title} Node`
: `"${graphPart.label}" Edge`;

return (
<List>
<ListItem disablePadding={false}>
<ListItemIcon>
<GraphPartIcon />
</ListItemIcon>
<ListItemText primary={headerText} />
</ListItem>

<Divider />

<ListItem disablePadding={false} sx={{ justifyContent: "center" }}>
{partIsNode ? <EditableNode node={graphPart} /> : <StandaloneEdge edge={graphPart} />}
</ListItem>

<Divider />
</List>
<form
onBlur={(event) => {
void handleSubmit((data) => {
if (graphPart.data.notes === data.notes) return;
setGraphPartNotes(graphPart, data.notes);
})(event);
}}
>
<List>
<ListItem disablePadding={false}>
<ListItemIcon>
<GraphPartIcon />
</ListItemIcon>
<ListItemText primary={headerText} />
</ListItem>

<Divider />

<ListItem disablePadding={false} sx={{ justifyContent: "center" }}>
{partIsNode ? <EditableNode node={graphPart} /> : <StandaloneEdge edge={graphPart} />}
</ListItem>

<Divider />

<ListItem disablePadding={false}>
<TextField
{...register("notes")}
label="Notes"
error={!!errors.notes}
helperText={errors.notes?.message}
multiline
fullWidth
maxRows={10}
disabled={!userCanEditTopicData}
/>
</ListItem>
</List>
</form>
);
};
13 changes: 10 additions & 3 deletions src/web/topic/components/Surface/TopicDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,18 @@ export const TopicDrawer = ({ isLandscape }: Props) => {

<TabPanel value="1">
{selectedGraphPart !== undefined ? (
<GraphPartDetails graphPart={selectedGraphPart} />
// key ensures that details components re-render, re-setting default values
<GraphPartDetails graphPart={selectedGraphPart} key={selectedGraphPart.id} />
) : activeArguedDiagramPart !== null ? (
<GraphPartDetails graphPart={activeArguedDiagramPart} />
<GraphPartDetails
graphPart={activeArguedDiagramPart}
key={activeArguedDiagramPart.id}
/>
) : activeTableProblemNode !== null ? (
<GraphPartDetails graphPart={activeTableProblemNode} />
<GraphPartDetails
graphPart={activeTableProblemNode}
key={activeTableProblemNode.id}
/>
) : (
<TopicDetails />
)}
Expand Down
15 changes: 14 additions & 1 deletion src/web/topic/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import set from "lodash/set";
import { type EdgeSelectionChange, type NodeSelectionChange } from "reactflow";

import { errorWithData } from "../../../common/errorHandling";
import { type Node, Score, findGraphPart, findNode } from "../utils/diagram";
import { GraphPart, type Node, Score, findGraphPart, findNode } from "../utils/diagram";
import { useTopicStore } from "./store";
import {
getActiveDiagram,
Expand Down Expand Up @@ -61,6 +61,19 @@ export const setNodeLabel = (node: Node, value: string) => {
useTopicStore.setState(finishDraft(state), false, "setNodeLabel");
};

export const setGraphPartNotes = (graphPart: GraphPart, value: string) => {
const state = createDraft(useTopicStore.getState());

const diagram = getDiagramOrThrow(state, graphPart.data.diagramId);
const foundGraphPart = findGraphPart(graphPart.id, diagram);

/* eslint-disable functional/immutable-data, no-param-reassign */
foundGraphPart.data.notes = value;
/* eslint-enable functional/immutable-data, no-param-reassign */

useTopicStore.setState(finishDraft(state), false, "setGraphPartNotes");
};

export const setSelected = (selectChanges: NodeSelectionChange[] | EdgeSelectionChange[]) => {
const state = createDraft(useTopicStore.getState());

Expand Down
4 changes: 4 additions & 0 deletions src/web/topic/utils/apiConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const convertToStoreNode = (apiNode: TopicNode, diagramId: string) => {
return buildNode({
id: apiNode.id,
label: apiNode.text,
notes: apiNode.notes,
type: apiNode.type,
diagramId: diagramId,
});
Expand All @@ -29,6 +30,7 @@ export const convertToStoreNode = (apiNode: TopicNode, diagramId: string) => {
export const convertToStoreEdge = (apiEdge: TopicEdge, diagramId: string) => {
return buildEdge({
id: apiEdge.id,
notes: apiEdge.notes,
sourceNodeId: apiEdge.sourceId,
targetNodeId: apiEdge.targetId,
relation: apiEdge.type,
Expand Down Expand Up @@ -62,6 +64,7 @@ export const convertToApiNode = (storeNode: StoreNode, topicId: number): ApiNode
storeNode.data.diagramId !== topicDiagramId ? storeNode.data.diagramId : null,
type: storeNode.type,
text: storeNode.data.label,
notes: storeNode.data.notes,
};
};

Expand All @@ -72,6 +75,7 @@ export const convertToApiEdge = (storeEdge: StoreEdge, topicId: number): ApiEdge
arguedDiagramPartId:
storeEdge.data.diagramId !== topicDiagramId ? storeEdge.data.diagramId : null,
type: storeEdge.label,
notes: storeEdge.data.notes,
sourceId: storeEdge.source,
targetId: storeEdge.target,
};
Expand Down
9 changes: 8 additions & 1 deletion src/web/topic/utils/diagram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Node {
id: string;
data: {
label: string;
notes: string;
diagramId: string;
showing: boolean;
newlyAdded: boolean; // jank to allow focusing nodes after adding them
Expand All @@ -43,14 +44,16 @@ export interface Node {
interface BuildProps {
id?: string;
label?: string;
notes?: string;
type: FlowNodeType;
diagramId: string;
}
export const buildNode = ({ id, label, type, diagramId }: BuildProps): Node => {
export const buildNode = ({ id, label, notes, type, diagramId }: BuildProps): Node => {
const node = {
id: id ?? uuid(),
data: {
label: label ?? `new node`,
notes: notes ?? "",
diagramId: diagramId,
showing: true,
newlyAdded: false,
Expand All @@ -72,6 +75,7 @@ export interface Edge {
id: string;
data: {
diagramId: string;
notes: string;
};
label: RelationName;
selected: boolean;
Expand All @@ -83,13 +87,15 @@ export interface Edge {

interface BuildEdgeProps {
id?: string;
notes?: string;
sourceNodeId: string;
targetNodeId: string;
relation: RelationName;
diagramId: string;
}
export const buildEdge = ({
id,
notes,
sourceNodeId,
targetNodeId,
relation,
Expand All @@ -99,6 +105,7 @@ export const buildEdge = ({
id: id ?? uuid(),
data: {
diagramId: diagramId,
notes: notes ?? "",
},
label: relation,
selected: false,
Expand Down

0 comments on commit 275c70d

Please sign in to comment.