From a8fb8f30b4fb84fb475aef3b0651f6eb20177788 Mon Sep 17 00:00:00 2001 From: Kristopher Santos Date: Fri, 28 Mar 2025 21:22:36 +0800 Subject: [PATCH] FEAT: Member Modal, Code Gen, and Toolbar Changes - Added dimming style for members added in the local state - Made the save button conditional in Share Modal - Added tooltips for the rest of the panel icon buttons - Removed initial setup button - Added new tab and code gen logic for the Code Generation modal - Implemented appending of target id when connecting table nodes - Implemented toolbar switching - Reworked the adding of nodes to now add on mouse click position --- src/components/modals/CodeGenModal.tsx | 82 +++++++++++++++- src/components/modals/ShareModal.tsx | 51 +++++----- src/components/ui/TooltipIconButton.tsx | 35 +++++++ src/css/react-flow.css | 4 + src/data/repo/useCodeGenRepo.ts | 119 +++++++++++++++++++++--- src/data/repo/useToolbarRepo.ts | 26 ++++++ src/layouts/BottomMiddleBar.tsx | 53 ++++++----- src/layouts/TopLeftBar.tsx | 56 +++++------ src/layouts/TopMiddleBar.tsx | 39 +------- src/layouts/TopRightBar.tsx | 12 +-- src/pages/editor/index.tsx | 29 ++++-- src/pages/editor/nodes/TableNode.tsx | 71 +++++++++++--- src/store/useToolbarStore.ts | 27 ++++++ 13 files changed, 454 insertions(+), 150 deletions(-) create mode 100644 src/components/ui/TooltipIconButton.tsx create mode 100644 src/css/react-flow.css create mode 100644 src/data/repo/useToolbarRepo.ts create mode 100644 src/store/useToolbarStore.ts diff --git a/src/components/modals/CodeGenModal.tsx b/src/components/modals/CodeGenModal.tsx index 8cfae09..25b7497 100644 --- a/src/components/modals/CodeGenModal.tsx +++ b/src/components/modals/CodeGenModal.tsx @@ -1,11 +1,10 @@ -import { Box, Button, Code, CopyButton, ScrollArea, Tabs, Text } from "@mantine/core" +import { Box, Button, CopyButton, Tabs, Text } from "@mantine/core" import { ContextModalProps } from "@mantine/modals" import { IconClipboard, IconClipboardCheck } from "@tabler/icons-react"; import { useEffect } from "react"; import SyntaxHighlighter from 'react-syntax-highlighter' import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs"; import useCodeGenRepo from "../../data/repo/useCodeGenRepo"; -import useEditorRepo from "../../data/repo/useEditorRepo"; import { useEditorStore } from "../../store/useEditorStore"; @@ -31,12 +30,19 @@ function CodeGenModal({ context, id, innerProps }: ContextModalProps) { Types + + Functions + + + + + ) @@ -47,7 +53,7 @@ function TypesPanel() { const getDataSnap = useEditorStore((state) => state.getDataSnapshot); - const { typeString, typesList, selectType} = useCodeGenRepo(getDataSnap()) + const { typeString, typesList, selectTable} = useCodeGenRepo(getDataSnap()) return( <> @@ -55,12 +61,12 @@ function TypesPanel() { - selectType(undefined)}> + selectTable(undefined)}> All { typesList.map((item)=>( - selectType(item)}> + selectTable(item)}> {item} )) @@ -109,4 +115,70 @@ function TypesPanel() { ) } +function FunctionsPanel() { + + const getDataSnap = useEditorStore((state) => state.getDataSnapshot); + + const { functionString, typesList, selectTable} = useCodeGenRepo(getDataSnap()) + + return( + <> + + + + + selectTable(undefined)}> + All + + { + typesList.map((item)=>( + selectTable(item)}> + {item} + + )) + } + + + + + + + + + {({ copied, copy }) => ( + + )} + + + + + {functionString} + + + + + + + + + + ) +} + export default CodeGenModal \ No newline at end of file diff --git a/src/components/modals/ShareModal.tsx b/src/components/modals/ShareModal.tsx index bad71b2..4b90670 100644 --- a/src/components/modals/ShareModal.tsx +++ b/src/components/modals/ShareModal.tsx @@ -8,7 +8,6 @@ import { CopyButton, Divider, Group, - Input, InputBase, Loader, Menu, @@ -19,7 +18,6 @@ import { Text, TextInput, Title, - Tooltip, useCombobox, } from "@mantine/core"; import { ContextModalProps } from "@mantine/modals"; @@ -27,7 +25,6 @@ import { IconCopy, IconDotsVertical, IconLock, - IconPlus, } from "@tabler/icons-react"; import { useEffect, useMemo, useState } from "react"; import useProjectRepo from "../../data/repo/useProjectRepo"; @@ -62,6 +59,7 @@ function ShareModal({ context, id }: ContextModalProps) { >([]); // useState to keep track project members locally. const [localMembers, setLocalMembers] = useState([]); + const [localRemovedMembers, setLocalRemovedMembers] = useState([]); const [searchTerm, setSearchTerm] = useState(""); const currentUserRole = useMemo(() => { @@ -167,6 +165,9 @@ function ShareModal({ context, id }: ContextModalProps) { }; const handleRemoveMember = (memberId: string) => { + const removedMember = localMembers.find(member => member.id === memberId) + if (!removedMember) return + setLocalRemovedMembers([...localMembers, removedMember]) setLocalMembers((prev) => prev.filter((member) => member.id !== memberId)); }; @@ -205,6 +206,7 @@ function ShareModal({ context, id }: ContextModalProps) { ]); await fetchProjectMembers(selectedProject.id); + setLocalRemovedMembers([]) showNotification({ color: "green", @@ -352,7 +354,7 @@ function ShareModal({ context, id }: ContextModalProps) { )} - + {initialLoading ? (
@@ -370,6 +372,7 @@ function ShareModal({ context, id }: ContextModalProps) { } onRemove={() => handleRemoveMember(member.id)} currentUserRole={currentUserRole} + dimmed={member.id.startsWith("temp-")} /> ) : null ) @@ -379,7 +382,20 @@ function ShareModal({ context, id }: ContextModalProps) { - + {(currentUserRole === "Owner" || currentUserRole === "Admin") + && (members.some((member) => member.id.startsWith("temp-")) || localRemovedMembers.length > 0) && ( + <Button + variant="filled" + fullWidth={true} + mt={10} + loading={initialLoading} + onClick={handleSave} + > + Save + </Button> + )} + + <Title order={5} my="xs"> General Access @@ -406,18 +422,6 @@ function ShareModal({ context, id }: ContextModalProps) { } }} /> - - {(currentUserRole === "Owner" || currentUserRole === "Admin") && ( - - )} ); } @@ -486,6 +490,7 @@ interface MemberItemProps { onRemove: () => void; isCurrentUser: boolean; currentUserRole: string; + dimmed?: boolean } function MemberItem({ @@ -498,6 +503,7 @@ function MemberItem({ onRemove, isCurrentUser, currentUserRole, + dimmed }: MemberItemProps) { const truncatedEmail = username.length > 25 ? `${username.slice(0, 22)}...` : username; @@ -510,10 +516,10 @@ function MemberItem({ justify="space-between" > - + - + {name}{" "} {isCurrentUser && ( @@ -521,13 +527,13 @@ function MemberItem({ )} - + {truncatedEmail} - + {role === "Owner" ? ( {role} ) : // Ensure only Admin and Owner can update member roles @@ -540,6 +546,7 @@ function MemberItem({ value={role} data={["Viewer", "Editor", "Admin"]} onChange={(value) => value && onRoleChange(value)} + opacity={dimmed ? 0.5 : 1} /> ) : ( {role} @@ -550,7 +557,7 @@ function MemberItem({ role !== "Owner" && ( - + diff --git a/src/components/ui/TooltipIconButton.tsx b/src/components/ui/TooltipIconButton.tsx new file mode 100644 index 0000000..250d904 --- /dev/null +++ b/src/components/ui/TooltipIconButton.tsx @@ -0,0 +1,35 @@ +import { ActionIcon, ActionIconProps, Tooltip } from "@mantine/core"; +import { ReactNode } from "react"; + + +interface TooltipIconButtonProps extends ActionIconProps{ + icon: ReactNode; + label: string; + disabled?: boolean; + active?: boolean; + onClick?: () => void; +} + +export default function TooltipIconButton({ + icon, + label, + disabled, + active, + onClick, + ...iconProps +}: TooltipIconButtonProps) { + return ( + + + {icon} + + + ); +} \ No newline at end of file diff --git a/src/css/react-flow.css b/src/css/react-flow.css new file mode 100644 index 0000000..878de13 --- /dev/null +++ b/src/css/react-flow.css @@ -0,0 +1,4 @@ + +.react-flow.cursor-cross .react-flow__pane { + cursor: crosshair; /* add !important if necessary */ +} \ No newline at end of file diff --git a/src/data/repo/useCodeGenRepo.ts b/src/data/repo/useCodeGenRepo.ts index ce87ffb..e9aa2e9 100644 --- a/src/data/repo/useCodeGenRepo.ts +++ b/src/data/repo/useCodeGenRepo.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { IEditorDataSnapshot, NodeState } from "../../types/EditorStoreTypes"; import { EditorNode, TableNode } from "../../types/EditorTypes"; -function isTableNode(node: EditorNode): node is TableNode { +export const isTableNode = (node: EditorNode | Pick): node is TableNode =>{ return node.type === "table"; } @@ -34,11 +34,11 @@ function generateTypes(dataSnap: IEditorDataSnapshot, title?: string): string { return filteredNodes .map((node) => { const relations = edges - .filter((edge) => edge.source === node.id) + .filter((edge) => edge.target === node.id) .map((edge) => { - const targetNode = nodeMap.get(edge.target); - if (targetNode && isTableNode(targetNode)) { - return ` ${targetNode.data.name.toLowerCase()}Id: string;`; + const sourcetNode = nodeMap.get(edge.source); + if (sourcetNode && isTableNode(sourcetNode)) { + return ` ${sourcetNode.data.name.charAt(0).toLowerCase() + sourcetNode.data.name.slice(1)}Id: string;`; } return ""; }) @@ -50,36 +50,129 @@ function generateTypes(dataSnap: IEditorDataSnapshot, title?: string): string { .join("\n"); return `interface ${node.data.name} {\n id: string;${ - fields ? "\n" + fields : "" - }${relations ? "\n" + relations : ""}\n}`; + relations ? "\n" + relations : "" + }${fields ? "\n" + fields : ""}\n}`; }) .join("\n\n"); } +function generateFunctions(dataSnap: IEditorDataSnapshot, title?: string): string { + const { nodes, edges } = dataSnap; + + const nodeMap = new Map(nodes.map((node) => [node.id, node])); + + const filteredNodes = nodes.filter( + (node): node is TableNode => + isTableNode(node) && (!title || node.data.name === title) + ); + + + const converter = `const converter = () => ({\n${ + [ + ` toFirestore: (data: WithFieldValue) => data,`, + ` fromFirestore: (\n snap: QueryDocumentSnapshot, \n options?: SnapshotOptions\n ) => {\n${ + [ + ` const data = snap.data(options)`, + ` return { ...data, id: snap.id }` + ].join("\n") + }\n }` + ].join("\n") + }\n})\n\n` + + const generatedCode = filteredNodes + .map((node) => { + + const relations = edges + .filter((edge) => edge.target === node.id) + .map((edge) => { + const sourceNode = nodeMap.get(edge.source); + if (sourceNode && isTableNode(sourceNode)) { + return sourceNode.data.name.toLowerCase() + } + return ""; + }) + .filter(Boolean) + .join("\n"); + + + const relationsNameUpperCase = relations.charAt(0).toUpperCase() + relations.slice(1) + const relationsNameLowerCase = relations.charAt(0).toLocaleLowerCase() + relations.slice(1) + const name = node.data.name + const nameUpperCase = name.charAt(0).toUpperCase() + name.slice(1) + const nameLowerCase = name.charAt(0).toLocaleLowerCase() + name.slice(1) + + const typeConverter = `const ${nameLowerCase}Converter = converter()` + + const getById = `export async function get${nameUpperCase}ById(id: string): Promise<${nameUpperCase} | null>{\n${ + [ + ` const docRef = await getDoc(`, + ` doc(firestore, '${nameLowerCase}', id).withConverter(${nameLowerCase}Converter)\n )\n`, + ` if(!docRef.exists()) return null\n`, + ` return docRef.data()`, + ].join("\n") + }\n}` + + const getList = `export async function get${nameUpperCase}s(): Promise<${nameUpperCase}[]>{\n${ + [ + ` const colRef = await getDocs(`, + ` collection(firestore, '${nameLowerCase}').withConverter(${nameLowerCase}Converter)\n )\n`, + ` return colRef.docs.map(doc => ({...doc.data()}))`, + ].join("\n") + }\n}` + + const getListById = `export async function get${nameUpperCase}sBy${relationsNameUpperCase}Id(${relationsNameLowerCase}Id: string): Promise<${nameUpperCase}[]>{\n${ + [ + ` const queryRef = query(`, + ` collection(firestore, '${nameLowerCase}')`, + ` where("${relationsNameLowerCase}Id", "==", ${relationsNameLowerCase}Id)`, + ` )\n`, + ` const colRef = await getDocs(queryRef.withConverter(${nameLowerCase}Converter)\n`, + ` return colRef.docs.map(doc => ({...doc.data()}))`, + ].join("\n") + }\n}` + + return [ + typeConverter, + getById, + getList, + relations != "" ? getListById : null + ].join("\n\n") + }) + .join("\n") + + return [ + converter, + generatedCode, + ].join("\n") +} + function useCodeGenRepo(dataSnap: IEditorDataSnapshot) { const { nodes } = dataSnap; - const [selectedType, setSelectedType] = useState( + const [selectedTable, setSelectedTable] = useState( undefined ); const [typeString, setTypeString] = useState(""); + const [functionString, setFunctionString] = useState(""); const typesList = nodes .filter(isTableNode) .map((node) => node.data.name || ""); useEffect(() => { - setTypeString(generateTypes(dataSnap, selectedType)); - }, [dataSnap, selectedType]); + setTypeString(generateTypes(dataSnap, selectedTable)); + setFunctionString(generateFunctions(dataSnap, selectedTable)); + }, [dataSnap, selectedTable]); - const selectType = (title: string | undefined) => { - setSelectedType(title); + const selectTable = (title: string | undefined) => { + setSelectedTable(title); }; return { typeString, + functionString, typesList, - selectType, + selectTable, hasInvalidFields, }; } diff --git a/src/data/repo/useToolbarRepo.ts b/src/data/repo/useToolbarRepo.ts new file mode 100644 index 0000000..afe47ef --- /dev/null +++ b/src/data/repo/useToolbarRepo.ts @@ -0,0 +1,26 @@ +import { ToolbarOptions, useToolbarStore } from "../../store/useToolbarStore"; + + +const useToolbarRepo = () => { + const setCurrentTool = useToolbarStore((state) => state.setCurrentTool); + + const changeTool = (tool: ToolbarOptions) => { + setCurrentTool(tool) + } + + const resetTool = () => { + setCurrentTool("select") + } + + const onTableClick = () => { + + } + + return { + changeTool, + resetTool + } +} + + +export default useToolbarRepo \ No newline at end of file diff --git a/src/layouts/BottomMiddleBar.tsx b/src/layouts/BottomMiddleBar.tsx index 0de5dfe..13d56f3 100644 --- a/src/layouts/BottomMiddleBar.tsx +++ b/src/layouts/BottomMiddleBar.tsx @@ -1,20 +1,23 @@ -import { ActionIcon, Box, Flex, Paper } from "@mantine/core"; +import { Box, Flex, Paper } from "@mantine/core"; import { IconNote, IconPointerFilled, IconTableFilled, } from "@tabler/icons-react"; import useIsDarkMode from "../hooks/useIsDarkMode"; -import useEditorRepo from "../data/repo/useEditorRepo"; import useProjectRepo from "../data/repo/useProjectRepo"; +import TooltipIconButton from "../components/ui/TooltipIconButton"; +import { useToolbarStore } from "../store/useToolbarStore"; +import useToolbarRepo from "../data/repo/useToolbarRepo"; function BottomMiddleBar() { const isDarkMode = useIsDarkMode(); - const { selectedProject } = useProjectRepo(); + const { selectedProject } = useProjectRepo(); const isButtonDisabled = !selectedProject; - const { addNode } = useEditorRepo(); + const currentTool = useToolbarStore(state => state.currentTool) + const { changeTool } = useToolbarRepo() return ( @@ -27,26 +30,27 @@ function BottomMiddleBar() { wrap="wrap" > - - - - addNode("table")} - disabled={isButtonDisabled} - > - - - } + variant="subtle" size="md" radius="sm" + active={currentTool == "select"} + onClick={() => changeTool("select")} + /> + } variant="subtle" size="md" radius="sm" - onClick={() => addNode("note")} disabled={isButtonDisabled} - > - changeTool("table")} + /> + - + } + variant="subtle" + size="md" + radius="sm" + disabled={isButtonDisabled} + active={currentTool == "note"} + onClick={() => changeTool("note")} + /> diff --git a/src/layouts/TopLeftBar.tsx b/src/layouts/TopLeftBar.tsx index c1422b3..3f06a74 100644 --- a/src/layouts/TopLeftBar.tsx +++ b/src/layouts/TopLeftBar.tsx @@ -14,10 +14,8 @@ import { useViewportSize, } from "@mantine/hooks"; import { modals } from "@mantine/modals"; -import { notifications } from "@mantine/notifications"; import { IconArrowBackUp, - IconCopy, IconDeviceFloppy, IconDots, IconEdit, @@ -28,7 +26,6 @@ import { IconX, } from "@tabler/icons-react"; import { useNavigate, useParams } from "react-router-dom"; -import { StatusIcon } from "../components/icons/StatusIcon"; import ConditionalHoverCard from "../components/ui/ConditionalHoverCard"; import useChangelogRepo from "../data/repo/useChangelogRepo"; import useHistoryRepo from "../data/repo/useHistoryRepo"; @@ -41,10 +38,9 @@ import { useEditorStore } from "../store/useEditorStore"; import { IProject } from "../types/ProjectTypes"; import { DrawerModalFormValues } from "../types/TopLeftBarTypes"; import { determineTitle } from "../utils/successHelpers"; -import useMemberRepo from "../data/repo/useMemberRepo"; -import { useMemberStore } from "../store/useMemberStore"; import CustomNotification from "../components/ui/CustomNotification"; import { APIResponse, SavedProject } from "../types/APITypes"; +import TooltipIconButton from "../components/ui/TooltipIconButton"; function TopLeftBar() { const [drawerLocalStorage, setDrawerLocalStorage] = useLocalStorage({ @@ -122,41 +118,47 @@ function ActionButtons({ direction={width < 900 ? "column" : "row"} wrap="wrap" > - - {openedDrawer ? : } - - : } + variant="subtle" size="lg" radius="xl" onClick={toggleDrawer} + /> + } variant="subtle" size="lg" radius="xl" onClick={onUndo} disabled={!canUndo} - > - - - + + } variant="subtle" size="lg" radius="xl" onClick={onRedo} disabled={!canRedo} - > - - - + : } variant="subtle" size="lg" radius="xl" onClick={() => handleSave()} disabled={!validateRole()} - > - {hasPendingChanges ? : } - - + } variant="subtle" size="lg" radius="xl" @@ -166,9 +168,7 @@ function ActionButtons({ innerProps: {}, }) } - > - - + /> ); } diff --git a/src/layouts/TopMiddleBar.tsx b/src/layouts/TopMiddleBar.tsx index a4b69a5..398cde6 100644 --- a/src/layouts/TopMiddleBar.tsx +++ b/src/layouts/TopMiddleBar.tsx @@ -1,30 +1,25 @@ import { - ActionIcon, Box, Divider, Flex, - HoverCard, Paper, rem, Text, - Tooltip, } from "@mantine/core"; import { IconCode, IconDownload, - IconSchool, IconShare, - IconTool, } from "@tabler/icons-react"; import { useProjectStore } from "../store/useProjectStore"; import useProjectIcon from "../hooks/useProjectIcon"; import ConditionalHoverCard from "../components/ui/ConditionalHoverCard"; import useIsTruncated from "../hooks/useIsTruncated"; -import { ReactNode } from "react"; import { modals } from "@mantine/modals"; import useCodeGenRepo from "../data/repo/useCodeGenRepo"; import { useEditorStore } from "../store/useEditorStore"; import CustomNotification from "../components/ui/CustomNotification"; +import TooltipIconButton from "../components/ui/TooltipIconButton"; function TopMiddleBar() { const { selectedProject } = useProjectStore(); @@ -72,7 +67,7 @@ function TopMiddleBar() { - } label="Initial Setup" disabled={isButtonDisabled} - /> + /> */} } label="Code Generation" @@ -135,30 +130,4 @@ function TopMiddleBar() { ); } -function TooltipIconButton({ - icon, - label, - disabled, - onClick, -}: { - icon: ReactNode; - label: string; - disabled: boolean; - onClick?: () => void; -}) { - return ( - - - {icon} - - - ); -} - export default TopMiddleBar; diff --git a/src/layouts/TopRightBar.tsx b/src/layouts/TopRightBar.tsx index de89451..8768137 100644 --- a/src/layouts/TopRightBar.tsx +++ b/src/layouts/TopRightBar.tsx @@ -1,5 +1,4 @@ import { - ActionIcon, Avatar, Box, Button, @@ -29,11 +28,10 @@ import useUserRepo from "../data/repo/useUserRepo"; import useChangelogRepo from "../data/repo/useChangelogRepo"; import { IChangelog, IMember } from "../store/useChangelogStore"; import useProjectRepo from "../data/repo/useProjectRepo"; -import useEditorRepo from "../data/repo/useEditorRepo"; -import { IProject } from "../types/ProjectTypes"; import { IEditorDataSnapshot } from "../types/EditorStoreTypes"; import { useNavigate } from "react-router-dom"; import ProfileAvatar from "../components/ui/ProfileAvatar"; +import TooltipIconButton from "../components/ui/TooltipIconButton"; function TopRightBar() { const [drawerOpen, setDrawerOpen] = useState(false); @@ -97,15 +95,15 @@ function ActionButtons({ direction={width < 900 ? "column" : "row"} wrap="wrap" > - : } variant="subtle" size="lg" radius="xl" onClick={toggleDrawer} disabled={isButtonDisabled} - > - {openedDrawer ? : } - + /> state.nodes); const edges = useEditorStore((state) => state.edges); + const currentTool = useToolbarStore(state => state.currentTool) + const { resetTool } = useToolbarRepo() + const onNodeDrag: OnNodeDrag = useCallback( (_, node) => { moveNode(node.id, node.position) @@ -87,11 +92,19 @@ function Editor() { const onPaneClick = useCallback((event: React.MouseEvent) => { const position = screenToFlowPosition({ - x: event.clientX, - y: event.clientY, + x: event.clientX + -50, + y: event.clientY + -50, }); - console.log(position); - }, [useEditorRepo()]); + + console.log(position) + if(currentTool == "table") { + addNode("table", position); + } else if (currentTool == "note") { + addNode("note", position); + } + + resetTool(); + }, [useEditorRepo(), currentTool]); return ( @@ -112,7 +125,7 @@ function Editor() { onPaneClick={onPaneClick} proOptions={proOptions} connectionMode={ConnectionMode.Loose} - className="-z-10" + className={`-z-10 ${currentTool !== "select" ? "cursor-cross": ""}`} nodesDraggable={validateRole()} nodesConnectable={validateRole()} nodesFocusable={validateRole()} diff --git a/src/pages/editor/nodes/TableNode.tsx b/src/pages/editor/nodes/TableNode.tsx index ac55c5d..6171cc8 100644 --- a/src/pages/editor/nodes/TableNode.tsx +++ b/src/pages/editor/nodes/TableNode.tsx @@ -10,18 +10,20 @@ import { } from "@mantine/core"; import { useDidUpdate } from "@mantine/hooks"; import { IconPlus, IconTrash } from "@tabler/icons-react"; -import { Handle, NodeProps, NodeResizer, Position } from "@xyflow/react"; +import { Handle, NodeProps, NodeResizer, Position, useNodeConnections, useNodesData } from "@xyflow/react"; import { useContextMenu } from "mantine-contextmenu"; import { ChangeEvent, memo, useCallback, useEffect, useState } from "react"; import TextSelect from "../../../components/ui/TextSelect"; import useEditorRepo from "../../../data/repo/useEditorRepo"; import useIsDarkMode from "../../../hooks/useIsDarkMode"; -import type { - TableField, - TableNode, - TableType, +import { + EditorNode, + type TableField, + type TableNode, + type TableType, } from "../../../types/EditorTypes"; import _ from "lodash"; +import { isTableNode } from "../../../data/repo/useCodeGenRepo"; export default memo(({ id, data, isConnectable }: NodeProps) => { const theme = useMantineTheme(); @@ -55,6 +57,16 @@ export default memo(({ id, data, isConnectable }: NodeProps) => { deleteNodeDataField, } = useEditorRepo(); + const relationshipConnections = useNodeConnections() + const relationshipData = useNodesData( + relationshipConnections.filter(conn => conn.source != id).map(conn => conn.source) + ) + + const foreignIds = relationshipData + .filter(node => isTableNode(node)) + .map(node => node.data.name) + + return ( <> ) => { - - id - - + + { + foreignIds.length > 0 && + foreignIds.map(foreignId => ( + + )) + } {data.fields.map((field, index) => ( ) => { ); }); + +function TableNodeReadonlyField({ + field, + options +}: { + field: TableField | { name: TableField["name"]; type: TableType }; + options?: string[] +}) { + + return( + + {field.name} + { + options ? + : + {field.type} + } + + ) +} + + function TableNodeField({ type, field, @@ -257,8 +306,8 @@ function TableNodeField({ gap={"xs"} className={ "px-2 py-1 rounded-md transition-colors " + - (!fieldName.trim() && "text-red-500 ") + - (type === "field" && "hover:bg-neutral-500 hover:bg-opacity-20") + ((!fieldName.trim()) ? "text-red-500 ": "") + + (type === "field" ? "hover:bg-neutral-500 hover:bg-opacity-20 " : "") } onContextMenu={onContextMenu} > diff --git a/src/store/useToolbarStore.ts b/src/store/useToolbarStore.ts new file mode 100644 index 0000000..c3606f6 --- /dev/null +++ b/src/store/useToolbarStore.ts @@ -0,0 +1,27 @@ +import { create } from "zustand"; +import { devtools } from "zustand/middleware"; + + +export type ToolbarOptions = "select" | "table" | "note" + +interface IToolbarState { + currentTool: ToolbarOptions; +} + +interface IToolbarActions { + setCurrentTool: (option: ToolbarOptions) => void; +} + + +export const useToolbarStore = create()( + devtools( + (set) => ({ + currentTool: "select", + + setCurrentTool: (option) => set(() => ({ currentTool: option })), + }), + { + name: "toolbarStore", + } + ) +);