Skip to content

Commit

Permalink
variable 'move to project' button
Browse files Browse the repository at this point in the history
closes #497
  • Loading branch information
Brendonovich committed Aug 25, 2024
1 parent ba3418c commit 0fc0a62
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 35 deletions.
72 changes: 72 additions & 0 deletions interface/src/Sidebar/Graph/Variables.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -47,6 +57,68 @@ export function Variables(props: { graph: Graph }) {
name,
});
}}
contextMenu={(id) => (
<>
<ContextMenuItem
onSelect={() => {
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();
});
}}
>
<IconOcticonProjectSymlink16 class="size-3" /> Move to project
</ContextMenuItem>
</>
)}
/>
);
}
2 changes: 1 addition & 1 deletion interface/src/Sidebar/InlineTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function InlineTextEditor<T extends ValidComponent = "span">(
<Polymorphic
as={(props.as ?? "span") as any}
class={clsx(
"flex-1 hover:bg-white/10 rounded flex flex-row items-center justify-between py-0.5 px-1.5",
"flex-1 hover:bg-white/10 ui-expanded:bg-white/10 rounded flex flex-row items-center justify-between py-0.5 px-1.5",
props.selected && "bg-white/10",
)}
onDblClick={(e: MouseEvent) => {
Expand Down
75 changes: 55 additions & 20 deletions interface/src/Sidebar/Variables.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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: {
Expand All @@ -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("");

Expand Down Expand Up @@ -61,32 +79,49 @@ export function Variables(props: {
<For each={filteredVariables()}>
{(variable) => (
<li class="flex flex-col gap-1 flex-1 group/item py-2 pt-1">
<InlineTextEditor
value={variable.name}
onChange={(value) => {
props.onVariableNameChanged(variable.id, value);
}}
>
<IconButton
type="button"
class="opacity-0 focus:opacity-100 group-hover/item:opacity-100 p-0.5"
onClick={(e) => {
e.stopPropagation();
<InlineTextEditorContext>
{() => {
const inlineEditorContext = useInlineTextEditorCtx()!;

props.onRemoveVariable(variable.id);
}}
>
<IconAntDesignDeleteOutlined class="size-4" />
</IconButton>
</InlineTextEditor>
return (
<ContextMenu>
<InlineTextEditor<ValidComponent>
as={(asProps) => <ContextMenu.Trigger {...asProps} />}
value={variable.name}
onChange={(value) => {
props.onVariableNameChanged(variable.id, value);
}}
/>
<ContextMenuContent>
<ContextMenuItem
onSelect={() =>
inlineEditorContext.setEditing(true)
}
>
<IconAntDesignEditOutlined /> Rename
</ContextMenuItem>
{props.contextMenu?.(variable.id)}
<ContextMenuItem
class="text-red-500"
onSelect={() => {
props.onRemoveVariable(variable.id);
}}
>
<IconAntDesignDeleteOutlined />
Delete
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
}}
</InlineTextEditorContext>
<div class="ui-closed:animate-accordion-up ui-expanded:animate-accordion-down transition-all overflow-hidden space-y-2 bg-black/30 p-2 rounded-md">
<TypeEditor
type={variable.type}
onChange={(type) => {
props.onSetVariableType(variable.id, type);
}}
/>

<Switch>
<Match
when={
Expand Down
3 changes: 1 addition & 2 deletions interface/src/components/Graph/Node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,8 @@ export const Node = (props: Props) => {
// 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);
}
});
Expand Down
10 changes: 2 additions & 8 deletions interface/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
63 changes: 59 additions & 4 deletions packages/packages/src/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,57 @@ export function pkg() {
},
});

pkg.createSchema({
name: "Graph Variable Changed",
type: "event",
properties: { variable: graphVariableProperty },
createListener: ({ ctx, properties }) => {
const bus = createEventBus<any>();

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 }) =>
Expand Down Expand Up @@ -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<any>();

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(
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down

0 comments on commit 0fc0a62

Please sign in to comment.