From 0fc0a62be0817e551d1f94229845080a6ab11c93 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Sun, 25 Aug 2024 22:46:13 +0800 Subject: [PATCH] variable 'move to project' button closes #497 --- interface/src/Sidebar/Graph/Variables.tsx | 72 +++++++++++++++++++++ interface/src/Sidebar/InlineTextEditor.tsx | 2 +- interface/src/Sidebar/Variables.tsx | 75 ++++++++++++++++------ interface/src/components/Graph/Node.tsx | 3 +- interface/src/index.tsx | 10 +-- packages/packages/src/variables.ts | 63 ++++++++++++++++-- packages/ui/src/auto-imports.d.ts | 1 + 7 files changed, 191 insertions(+), 35 deletions(-) diff --git a/interface/src/Sidebar/Graph/Variables.tsx b/interface/src/Sidebar/Graph/Variables.tsx index de8ef112..59b9ea69 100644 --- a/interface/src/Sidebar/Graph/Variables.tsx +++ b/interface/src/Sidebar/Graph/Variables.tsx @@ -1,5 +1,15 @@ import type { Graph } from "@macrograph/runtime"; +import { serde } from "@macrograph/runtime-serde"; +import * as v from "valibot"; +import { + deserializeNode, + deserializeVariable, + serializeNode, + serializeVariable, +} from "@macrograph/runtime-serde"; +import { batch } from "solid-js"; +import { ContextMenuItem } from "../../components/Graph/ContextMenu"; import { useInterfaceContext } from "../../context"; import { Variables as VariablesRoot } from "../Variables"; @@ -47,6 +57,68 @@ export function Variables(props: { graph: Graph }) { name, }); }} + contextMenu={(id) => ( + <> + { + const graph = props.graph; + const project = graph.project; + const variable = graph.variables.find((v) => v.id === id); + if (!variable) return; + + const serializedVariable = serializeVariable(variable); + serializedVariable.id = project.generateId(); + + batch(() => { + project.variables.push( + deserializeVariable(serializedVariable, project), + ); + for (const node of graph.nodes.values()) { + if ( + node.schema.package.name === "Variables" && + [ + "Get Graph Variable", + "Set Graph Variable", + "Graph Variable Changed", + ].includes(node.schema.name) && + node.schema.properties?.variable + ) { + const serialized = serializeNode(node); + serialized.properties!.variable = serializedVariable.id; + serialized.schema.id = serialized.schema.id.replace( + "Graph", + "Project", + ); + serialized.name = serialized.name.replace( + "Graph", + "Project", + ); + + const newNode = deserializeNode( + graph, + v.parse(serde.Node, serialized), + ); + if (newNode) { + graph.nodes.set(serialized.id, newNode); + node.dispose(); + } + } + } + + interfaceCtx.execute( + "deleteVariable", + { location: "graph", graphId: graph.id, variableId: id }, + { ephemeral: true }, + ); + + interfaceCtx.save(); + }); + }} + > + Move to project + + + )} /> ); } diff --git a/interface/src/Sidebar/InlineTextEditor.tsx b/interface/src/Sidebar/InlineTextEditor.tsx index 5b949195..49378319 100644 --- a/interface/src/Sidebar/InlineTextEditor.tsx +++ b/interface/src/Sidebar/InlineTextEditor.tsx @@ -87,7 +87,7 @@ export function InlineTextEditor( { diff --git a/interface/src/Sidebar/Variables.tsx b/interface/src/Sidebar/Variables.tsx index aa145f81..e05ce6bd 100644 --- a/interface/src/Sidebar/Variables.tsx +++ b/interface/src/Sidebar/Variables.tsx @@ -1,7 +1,20 @@ import type { Variable } from "@macrograph/runtime"; import { BasePrimitiveType, serializeValue, t } from "@macrograph/typesystem"; -import { For, Match, Switch, createMemo, createSignal } from "solid-js"; +import { + For, + type JSX, + Match, + Switch, + type ValidComponent, + createMemo, + createSignal, +} from "solid-js"; +import { ContextMenu } from "@kobalte/core/context-menu"; +import { + ContextMenuContent, + ContextMenuItem, +} from "../components/Graph/ContextMenu"; import { SidebarSection } from "../components/Sidebar"; import { TypeEditor } from "../components/TypeEditor"; import { @@ -12,7 +25,11 @@ import { TextInput, } from "../components/ui"; import { createTokenisedSearchFilter, tokeniseString } from "../util"; -import { InlineTextEditor } from "./InlineTextEditor"; +import { + InlineTextEditor, + InlineTextEditorContext, + useInlineTextEditorCtx, +} from "./InlineTextEditor"; import { SearchInput } from "./SearchInput"; export function Variables(props: { @@ -23,6 +40,7 @@ export function Variables(props: { onSetVariableValue(id: number, value: any): void; onSetVariableType(id: number, type: t.Any): void; onVariableNameChanged(id: number, name: string): void; + contextMenu?: (id: number) => JSX.Element; }) { const [search, setSearch] = createSignal(""); @@ -61,24 +79,42 @@ export function Variables(props: { {(variable) => (
  • - { - props.onVariableNameChanged(variable.id, value); - }} - > - { - e.stopPropagation(); + + {() => { + const inlineEditorContext = useInlineTextEditorCtx()!; - props.onRemoveVariable(variable.id); - }} - > - - - + return ( + + + as={(asProps) => } + value={variable.name} + onChange={(value) => { + props.onVariableNameChanged(variable.id, value); + }} + /> + + + inlineEditorContext.setEditing(true) + } + > + Rename + + {props.contextMenu?.(variable.id)} + { + props.onRemoveVariable(variable.id); + }} + > + + Delete + + + + ); + }} +
    - { // clientWidth doesn't update immediately and setTimeout causes flicker queueMicrotask(() => { if (ref) { - console.log("screen width", ref.clientWidth); const desired = Math.ceil(ref.clientWidth / GRID_SIZE) * GRID_SIZE; - console.log("desired width:", desired); + setMinWidth(desired); } }); diff --git a/interface/src/index.tsx b/interface/src/index.tsx index c4c6c7e3..899a167b 100644 --- a/interface/src/index.tsx +++ b/interface/src/index.tsx @@ -29,18 +29,12 @@ import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"; import "@total-typescript/ts-reset"; import * as Solid from "solid-js"; import { createStore, produce } from "solid-js/store"; +import { isDev } from "solid-js/web"; import { toast } from "solid-sonner"; import type * as v from "valibot"; - -import type { HistoryItemEntry } from "@macrograph/action-history"; -import { isDev } from "solid-js/web"; import { ActionHistory } from "./ActionHistory"; import * as Sidebars from "./Sidebar"; -import type { - CreateNodeInput, - GraphItemPositionInput, - HistoryActions, -} from "./actions"; +import type { CreateNodeInput, GraphItemPositionInput } from "./actions"; import { Graph } from "./components/Graph"; import { type GraphState, diff --git a/packages/packages/src/variables.ts b/packages/packages/src/variables.ts index d2056335..c40ab889 100644 --- a/packages/packages/src/variables.ts +++ b/packages/packages/src/variables.ts @@ -75,6 +75,57 @@ export function pkg() { }, }); + pkg.createSchema({ + name: "Graph Variable Changed", + type: "event", + properties: { variable: graphVariableProperty }, + createListener: ({ ctx, properties }) => { + const bus = createEventBus(); + + const variableId = ctx.getProperty(properties.variable); + const variable = ctx.graph.variables.find((v) => v.id === variableId); + if (!variable) return bus; + + createEffect( + on( + () => variable.value, + (value) => bus.emit(value), + { defer: true }, + ), + ); + + return bus; + }, + createIO({ io, ctx, properties }) { + const exec = io.execOutput({ id: "exec" }); + + const variableId = ctx.getProperty(properties.variable); + const variable = ctx.graph.variables.find((v) => v.id === variableId); + if (!variable) return; + + return { + exec, + variable, + output: io.dataOutput({ + id: "output", + name: variable.name, + type: variable.type, + }), + previousOutput: io.dataOutput({ + id: "previousOutput", + name: "Previous Value", + type: variable.type, + }), + }; + }, + run({ ctx, data, io }) { + if (!io || data.variableId !== io.variable.id) return; + + ctx.setOutput(io.output, data.value); + ctx.exec(io.exec); + }, + }); + const projectVariableProperty = { name: "Variable", source: ({ node }) => @@ -141,14 +192,16 @@ export function pkg() { }); pkg.createSchema({ - name: "Graph Variable Changed", + name: "Project Variable Changed", type: "event", - properties: { variable: graphVariableProperty }, + properties: { variable: projectVariableProperty }, createListener: ({ ctx, properties }) => { const bus = createEventBus(); const variableId = ctx.getProperty(properties.variable); - const variable = ctx.graph.variables.find((v) => v.id === variableId); + const variable = ctx.graph.project.variables.find( + (v) => v.id === variableId, + ); if (!variable) return bus; createEffect( @@ -165,7 +218,9 @@ export function pkg() { const exec = io.execOutput({ id: "exec" }); const variableId = ctx.getProperty(properties.variable); - const variable = ctx.graph.variables.find((v) => v.id === variableId); + const variable = ctx.graph.project.variables.find( + (v) => v.id === variableId, + ); if (!variable) return; return { diff --git a/packages/ui/src/auto-imports.d.ts b/packages/ui/src/auto-imports.d.ts index 95a7f8a3..274df786 100644 --- a/packages/ui/src/auto-imports.d.ts +++ b/packages/ui/src/auto-imports.d.ts @@ -28,6 +28,7 @@ declare global { const IconMdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal.jsx')['default'] const IconMdiGithub: typeof import('~icons/mdi/github.jsx')['default'] const IconMdiX: typeof import('~icons/mdi/x.jsx')['default'] + const IconOcticonProjectSymlink16: typeof import('~icons/octicon/project-symlink16.jsx')['default'] const IconPhExport: typeof import('~icons/ph/export.jsx')['default'] const IconPhShare: typeof import('~icons/ph/share.jsx')['default'] const IconRadixIconsExternalLink: typeof import('~icons/radix-icons/external-link.jsx')['default']