Skip to content

Commit

Permalink
Merge pull request #291 from amelioro/unrestrict-node-connections
Browse files Browse the repository at this point in the history
Unrestrict node connections
  • Loading branch information
keyserj authored Dec 18, 2023
2 parents dd6f572 + 9ef55fd commit 983ba07
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 17 deletions.
5 changes: 5 additions & 0 deletions src/common/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export const nodeSchema = z.object({
topicId: z.number(),
arguedDiagramPartId: z.string().uuid().nullable(),
type: zNodeTypes,
customType: z
.string()
.max(30)
.regex(/^[a-z ]+$/i)
.optional(),
text: z.string().max(200),
notes: z.string().max(10000),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
BEGIN;

-- AlterTable
ALTER TABLE "nodes" DROP COLUMN "customType";

COMMIT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
BEGIN;

-- AlterTable
ALTER TABLE "nodes" ADD COLUMN "customType" VARCHAR(30);

COMMIT;
1 change: 1 addition & 0 deletions src/db/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ model Node {
topicId Int
arguedDiagramPartId String? @db.Uuid // only set if this is a claim node; ideally this would be FK but could point to either Node or Edge
type NodeType
customType String? @db.VarChar(30) // arbitrary max, "Solution Component" is 18 chars
text String @db.VarChar(200) // arbitrary max, ~50 chars fit on 3 lines of the node's text area
notes String @default("") @db.VarChar(10000) // arbitrarily large max length
createdAt DateTime @default(now())
Expand Down
17 changes: 15 additions & 2 deletions src/web/topic/components/Node/EditableNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { useEffect, useRef } from "react";

import { useSessionUser } from "../../../common/hooks";
import { openContextMenu } from "../../../common/store/contextMenuActions";
import { useUnrestrictedEditing } from "../../../view/actionConfigStore";
import { setSelected, useIsGraphPartSelected } from "../../../view/navigateStore";
import { finishAddingNode, setNodeLabel } from "../../store/actions";
import { finishAddingNode, setCustomNodeType, setNodeLabel } from "../../store/actions";
import { useUserCanEditTopicData } from "../../store/userHooks";
import { Node } from "../../utils/graph";
import { nodeDecorations } from "../../utils/node";
Expand Down Expand Up @@ -35,6 +36,7 @@ interface Props {
export const EditableNode = ({ node, supplemental = false, className = "" }: Props) => {
const { sessionUser } = useSessionUser();
const userCanEditTopicData = useUserCanEditTopicData(sessionUser?.username);
const unrestrictedEditing = useUnrestrictedEditing();
const selected = useIsGraphPartSelected(node.id);

const theme = useTheme();
Expand Down Expand Up @@ -64,6 +66,7 @@ export const EditableNode = ({ node, supplemental = false, className = "" }: Pro
const nodeDecoration = nodeDecorations[node.type];
const color = theme.palette[node.type].main;
const NodeIcon = nodeDecoration.NodeIcon;
const typeText = node.data.customType ?? nodeDecoration.title;

// Require selecting a node before editing it, because oftentimes you'll want to select a node to
// view more details, and the editing will be distracting. Only edit after clicking when selected.
Expand All @@ -80,7 +83,17 @@ export const EditableNode = ({ node, supplemental = false, className = "" }: Pro
<YEdgeDiv>
<NodeTypeDiv>
<NodeIcon sx={{ width: "0.875rem", height: "0.875rem" }} />
<NodeTypeSpan>{nodeDecoration.title}</NodeTypeSpan>
<NodeTypeSpan
contentEditable={userCanEditTopicData && unrestrictedEditing}
suppressContentEditableWarning // https://stackoverflow.com/a/49639256/8409296
onBlur={(event) => {
if (event.target.textContent && event.target.textContent !== node.data.customType)
setCustomNodeType(node, event.target.textContent.trim());
}}
className="nopan"
>
{typeText}
</NodeTypeSpan>
</NodeTypeDiv>
<NodeIndicatorGroup node={node} />
</YEdgeDiv>
Expand Down
16 changes: 16 additions & 0 deletions src/web/topic/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import set from "lodash/set";

import { edgeSchema } from "../../../common/edge";
import { errorWithData } from "../../../common/errorHandling";
import { nodeSchema } from "../../../common/node";
import {
Edge,
GraphPart,
Expand Down Expand Up @@ -63,6 +64,21 @@ export const setNodeLabel = (node: Node, value: string) => {
useTopicStore.setState(finishDraft(state), false, "setNodeLabel");
};

export const setCustomNodeType = (node: Node, value: string) => {
const state = createDraft(useTopicStore.getState());

if (!nodeSchema.shape.customType.parse(value))
throw errorWithData("label should only contain alphaspace", value);

const foundNode = findNode(node.id, state.nodes);

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

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

export const setCustomEdgeLabel = (edge: Edge, value: string) => {
const state = createDraft(useTopicStore.getState());

Expand Down
2 changes: 2 additions & 0 deletions src/web/topic/utils/apiConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const convertToStoreNode = (apiNode: TopicNode) => {
notes: apiNode.notes,
type: apiNode.type,
arguedDiagramPartId: apiNode.arguedDiagramPartId ?? undefined,
customType: apiNode.customType ?? undefined,
});
};

Expand Down Expand Up @@ -56,6 +57,7 @@ export const convertToApiNode = (storeNode: StoreNode, topicId: number): ApiNode
topicId: topicId,
arguedDiagramPartId: storeNode.data.arguedDiagramPartId ?? null,
type: storeNode.type,
customType: storeNode.data.customType,
text: storeNode.data.label,
notes: storeNode.data.notes,
};
Expand Down
17 changes: 9 additions & 8 deletions src/web/topic/utils/edge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,15 @@ export const getRelation = (
child: NodeType,
relationName?: RelationName
): Relation | undefined => {
if (relationName) {
return relations.find(
(relation) =>
relation.parent === parent && relation.child === child && relation.name === relationName
);
} else {
return relations.find((relation) => relation.parent === parent && relation.child === child);
}
const relation = relationName
? relations.find(
(relation) =>
relation.parent === parent && relation.child === child && relation.name === relationName
)
: relations.find((relation) => relation.parent === parent && relation.child === child);

// we're assuming that anything can relate to anything else; potentially this should only be true when unrestricted editing is on
return relation ?? { child, name: "relatesTo", parent };
};

export const composedRelations: Relation[] = [
Expand Down
27 changes: 20 additions & 7 deletions src/web/topic/utils/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export interface Graph {
export interface Node {
id: string;
data: {
/**
* Distinguished from `type` because this is explicitly open user input, and `type` can maintain stricter typing
*/
customType?: string;
label: string;
notes: string;
arguedDiagramPartId?: string;
Expand All @@ -30,15 +34,24 @@ export interface ProblemNode extends Node {

interface BuildProps {
id?: string;
customType?: string;
label?: string;
notes?: string;
type: FlowNodeType;
arguedDiagramPartId?: string;
}
export const buildNode = ({ id, label, notes, type, arguedDiagramPartId }: BuildProps): Node => {
export const buildNode = ({
id,
customType,
label,
notes,
type,
arguedDiagramPartId,
}: BuildProps): Node => {
const node = {
id: id ?? uuid(),
data: {
customType: customType,
label: label ?? `new node`,
notes: notes ?? "",
arguedDiagramPartId: arguedDiagramPartId,
Expand All @@ -59,12 +72,12 @@ export type RelationDirection = "parent" | "child";
export interface Edge {
id: string;
data: {
arguedDiagramPartId?: string;
notes: string;
/**
* Distinguished from `label` because this is explicitly open user input, and `label` can maintain stricter typing
*/
customLabel?: string;
notes: string;
arguedDiagramPartId?: string;
};
label: RelationName;
markerStart: { type: MarkerType; width: number; height: number };
Expand All @@ -91,28 +104,28 @@ export interface Edge {

interface BuildEdgeProps {
id?: string;
customLabel?: string;
notes?: string;
sourceId: string;
targetId: string;
relation: RelationName;
arguedDiagramPartId?: string;
customLabel?: string;
}
export const buildEdge = ({
id,
customLabel,
notes,
sourceId,
targetId,
relation,
arguedDiagramPartId,
customLabel,
}: BuildEdgeProps): Edge => {
return {
id: id ?? uuid(),
data: {
arguedDiagramPartId: arguedDiagramPartId,
notes: notes ?? "",
customLabel: customLabel,
notes: notes ?? "",
arguedDiagramPartId: arguedDiagramPartId,
},
label: relation,
markerStart: markerStart,
Expand Down

0 comments on commit 983ba07

Please sign in to comment.