diff --git a/interface/src/Sidebar/Project/CustomTypes.tsx b/interface/src/Sidebar/Project/CustomTypes.tsx index 9da559e4..44c73a62 100644 --- a/interface/src/Sidebar/Project/CustomTypes.tsx +++ b/interface/src/Sidebar/Project/CustomTypes.tsx @@ -1,466 +1,693 @@ import { Tabs } from "@kobalte/core"; -import { For, Show, createMemo, createSignal } from "solid-js"; +import { For, Show, ValidComponent, createMemo, createSignal } from "solid-js"; import { SidebarSection } from "../../components/Sidebar"; import { TypeEditor } from "../../components/TypeEditor"; import { IconButton } from "../../components/ui"; import { useInterfaceContext } from "../../context"; import { createTokenisedSearchFilter, tokeniseString } from "../../util"; -import { InlineTextEditor } from "../InlineTextEditor"; +import { InlineTextEditor, InlineTextEditorContext } from "../InlineTextEditor"; import { SearchInput } from "../SearchInput"; +import { ContextMenu } from "@kobalte/core/context-menu"; +import { + ContextMenuContent, + ContextMenuItem, + ContextMenuRenameItem, +} from "../../components/Graph/ContextMenu"; export function CustomTypes() { - const [search, setSearch] = createSignal(""); - const interfaceCtx = useInterfaceContext(); + const [search, setSearch] = createSignal(""); + const interfaceCtx = useInterfaceContext(); - const tokenisedEvents = createMemo(() => - [...interfaceCtx.core.project.customEvents].map( - ([id, event]) => [tokeniseString(event.name), [id, event]] as const, - ), - ); + const tokenisedEvents = createMemo(() => + [...interfaceCtx.core.project.customEvents].map( + ([id, event]) => [tokeniseString(event.name), [id, event]] as const, + ), + ); - const filteredEvents = createTokenisedSearchFilter(search, tokenisedEvents); + const filteredEvents = createTokenisedSearchFilter(search, tokenisedEvents); - const tokenisedStructs = createMemo(() => - [...interfaceCtx.core.project.customStructs].map( - ([id, struct]) => [tokeniseString(struct.name), [id, struct]] as const, - ), - ); + const tokenisedStructs = createMemo(() => + [...interfaceCtx.core.project.customStructs].map( + ([id, struct]) => [tokeniseString(struct.name), [id, struct]] as const, + ), + ); - const filteredStructs = createTokenisedSearchFilter(search, tokenisedStructs); + const filteredStructs = createTokenisedSearchFilter(search, tokenisedStructs); - const tokenisedEnums = createMemo(() => - [...interfaceCtx.core.project.customEnums].map( - ([id, enm]) => [tokeniseString(enm.name), [id, enm]] as const, - ), - ); + const tokenisedEnums = createMemo(() => + [...interfaceCtx.core.project.customEnums].map( + ([id, enm]) => [tokeniseString(enm.name), [id, enm]] as const, + ), + ); - const filteredEnums = createTokenisedSearchFilter(search, tokenisedEnums); + const filteredEnums = createTokenisedSearchFilter(search, tokenisedEnums); - const [selected, setSelected] = createSignal<"events" | "structs" | "enums">( - "events", - ); + const [selected, setSelected] = createSignal<"events" | "structs" | "enums">( + "events", + ); - return ( - - { - setSelected(v as any); - setSearch(""); - }} - > - - - Events - - - Structs - - - Enums - - -
- - -
- { - e.stopPropagation(); - setSearch(e.currentTarget.value); - }} - /> - { - e.stopPropagation(); - switch (selected()) { - case "events": { - interfaceCtx.execute("createCustomEvent"); - return; - } - case "structs": { - interfaceCtx.execute("createCustomStruct"); - return; - } - case "enums": { - interfaceCtx.execute("createCustomEnum"); - return; - } - } - }} - > - - -
-
- - - {([id, event]) => ( -
  • - { - interfaceCtx.execute("setCustomEventName", { - eventId: id, - name: value, - }); - }} - > - { - e.stopPropagation(); + return ( + + { + setSelected(v as any); + setSearch(""); + }} + > + + + Events + + + Structs + + + Enums + + +
    + + +
    + { + e.stopPropagation(); + setSearch(e.currentTarget.value); + }} + /> + { + e.stopPropagation(); + switch (selected()) { + case "events": { + interfaceCtx.execute("createCustomEvent"); + return; + } + case "structs": { + interfaceCtx.execute("createCustomStruct"); + return; + } + case "enums": { + interfaceCtx.execute("createCustomEnum"); + return; + } + } + }} + > + + +
    +
    + + + {([id, event]) => ( +
  • + { + interfaceCtx.execute("setCustomEventName", { + eventId: id, + name: value, + }); + }} + > + { + e.stopPropagation(); - interfaceCtx.execute("createCustomEventField", { - eventId: id, - }); - }} - > - - + interfaceCtx.execute("createCustomEventField", { + eventId: id, + }); + }} + > + + - { - e.stopPropagation(); + { + e.stopPropagation(); - interfaceCtx.execute("deleteCustomEvent", { - eventId: id, - }); - }} - > - - - -
      - - {(field) => ( -
    • - { - interfaceCtx.execute("setCustomEventFieldName", { - eventId: id, - fieldId: field.id, - name: value, - }); - }} - > - { - e.stopPropagation(); + interfaceCtx.execute("deleteCustomEvent", { + eventId: id, + }); + }} + > + + + +
        + + {(field, i) => ( +
      • + + + + as={(asProps) => ( + + {...asProps} + as="span" + /> + )} + class="-mx-1" + value={field.name ?? field.id} + onChange={(value) => { + interfaceCtx.execute( + "setCustomEventFieldName", + { + eventId: id, + fieldId: field.id, + name: value, + }, + ); + }} + > + { + e.stopPropagation(); - interfaceCtx.execute("deleteCustomEventField", { - eventId: id, - fieldId: field.id, - }); - }} - > - - - + interfaceCtx.execute( + "deleteCustomEventField", + { + eventId: id, + fieldId: field.id, + }, + ); + }} + > + + + + + + + + interfaceCtx.execute( + "moveCustomEventFieldToIndex", + { + eventId: event.id, + fieldId: field.id, + currentIndex: i(), + newIndex: i() - 1, + }, + ) + } + > + + Move Up + + + + + interfaceCtx.execute( + "moveCustomEventFieldToIndex", + { + eventId: event.id, + fieldId: field.id, + currentIndex: i(), + newIndex: i() + 1, + }, + ) + } + > + + Move Down + + + + + +
        + { + interfaceCtx.execute( + "setCustomEventFieldType", + { + eventId: id, + fieldId: field.id, + type: type as any, + }, + ); + }} + /> +
        +
      • + )} +
        +
      +
    • + )} +
      + + + + {([id, struct]) => ( +
    • + { + interfaceCtx.execute("setCustomStructName", { + structId: id, + name: value, + }); + }} + > + { + e.stopPropagation(); -
      - { - interfaceCtx.execute( - "setCustomEventFieldType", - { - eventId: id, - fieldId: field.id, - type: type as any, - }, - ); - }} - /> -
      -
    • - )} -
      -
    -
  • - )} -
    -
    - - - {([id, struct]) => ( -
  • - { - interfaceCtx.execute("setCustomStructName", { - structId: id, - name: value, - }); - }} - > - { - e.stopPropagation(); + interfaceCtx.execute("createCustomStructField", { + structId: id, + }); + }} + > + + - interfaceCtx.execute("createCustomStructField", { - structId: id, - }); - }} - > - - + { + e.stopPropagation(); - { - e.stopPropagation(); + interfaceCtx.execute("deleteCustomStruct", { + structId: id, + }); + }} + > + + + +
      + struct.fields[id]) + .filter(Boolean)} + > + {(field, i) => ( +
    • + + + + as={(asProps) => ( + + {...asProps} + as="span" + /> + )} + class="-mx-1" + value={field.name ?? field.id} + onChange={(value) => { + interfaceCtx.execute( + "setCustomStructFieldName", + { + structId: id, + fieldId: field.id, + name: value, + }, + ); + }} + > + { + e.stopPropagation(); - interfaceCtx.execute("deleteCustomStruct", { - structId: id, - }); - }} - > - - - -
        - - {(field) => ( -
      • - { - interfaceCtx.execute("setCustomStructFieldName", { - structId: id, - fieldId: field.id, - name: value, - }); - }} - > - { - e.stopPropagation(); + interfaceCtx.execute( + "deleteCustomStructField", + { structId: id, fieldId: field.id }, + ); + }} + > + + + + + + + + interfaceCtx.execute( + "moveCustomStructFieldToIndex", + { + structId: struct.id, + fieldId: field.id, + currentIndex: i(), + newIndex: i() - 1, + }, + ) + } + > + + Move Up + + + + + interfaceCtx.execute( + "moveCustomStructFieldToIndex", + { + structId: struct.id, + fieldId: field.id, + currentIndex: i(), + newIndex: i() + 1, + }, + ) + } + > + + Move Down + + + + + - interfaceCtx.execute( - "deleteCustomStructField", - { structId: id, fieldId: field.id }, - ); - }} - > - - - +
        + { + interfaceCtx.execute( + "setCustomStructFieldType", + { structId: id, fieldId: field.id, type }, + ); + }} + /> +
        +
      • + )} +
        +
      +
    • + )} +
      + + + + {([id, enm]) => ( +
    • + { + interfaceCtx.execute("setCustomEnumName", { + enumId: id, + name: value, + }); + }} + > + { + e.stopPropagation(); -
      - { - interfaceCtx.execute( - "setCustomStructFieldType", - { structId: id, fieldId: field.id, type }, - ); - }} - /> -
      -
    • - )} -
      -
    -
  • - )} -
    -
    - - - {([id, enm]) => ( -
  • - { - interfaceCtx.execute("setCustomEnumName", { - enumId: id, - name: value, - }); - }} - > - { - e.stopPropagation(); + interfaceCtx.execute("createCustomEnumVariant", { + enumId: id, + }); + }} + > + + - interfaceCtx.execute("createCustomEnumVariant", { - enumId: id, - }); - }} - > - - + { + e.stopPropagation(); - { - e.stopPropagation(); + interfaceCtx.execute("deleteCustomEnum", { + enumId: id, + }); + }} + > + + + + +
      + + {(variant, i) => ( +
    • + + + + as={(asProps) => ( + + {...asProps} + as="span" + /> + )} + class="-mx-1" + value={variant.name ?? variant.id} + onChange={(value) => { + interfaceCtx.execute( + "setCustomEnumVariantName", + { + enumId: id, + variantId: variant.id, + name: value, + }, + ); + }} + > + { + e.stopPropagation(); - interfaceCtx.execute("deleteCustomEnum", { - enumId: id, - }); - }} - > - - - - -
        - - {(variant) => ( -
      • - { - interfaceCtx.execute( - "setCustomEnumVariantName", - { - enumId: id, - variantId: variant.id, - name: value, - }, - ); - }} - > - { - e.stopPropagation(); + interfaceCtx.execute( + "createCustomEnumVariantField", + { enumId: id, variantId: variant.id }, + ); + }} + > + + - interfaceCtx.execute( - "createCustomEnumVariantField", - { enumId: id, variantId: variant.id }, - ); - }} - > - - + { + e.stopPropagation(); - { - e.stopPropagation(); + interfaceCtx.execute( + "deleteCustomEnumVariant", + { enumId: id, variantId: variant.id }, + ); + }} + > + + + + + + + + interfaceCtx.execute( + "moveCustomEnumVariantToIndex", + { + enumId: enm.id, + variantId: variant.id, + currentIndex: i(), + newIndex: i() - 1, + }, + ) + } + > + + Move Up + + + + + interfaceCtx.execute( + "moveCustomEnumVariantToIndex", + { + enumId: enm.id, + variantId: variant.id, + currentIndex: i(), + newIndex: i() + 1, + }, + ) + } + > + + Move Down + + + + + - interfaceCtx.execute( - "deleteCustomEnumVariant", - { enumId: id, variantId: variant.id }, - ); - }} - > - - - + 0}> +
          + variant.fields[id]) + .filter(Boolean)} + > + {(field, i) => ( +
        • + + + + as={(asProps) => ( + + {...asProps} + as="span" + /> + )} + value={field.name ?? field.id} + onChange={(value) => { + interfaceCtx.execute( + "setCustomEnumVariantFieldName", + { + enumId: enm.id, + variantId: variant.id, + fieldId: field.id, + name: value, + }, + ); + }} + class="-mx-1" + > + { + e.stopPropagation(); - -
            - - {(field) => ( -
          • - { - interfaceCtx.execute( - "setCustomEnumVariantFieldName", - { - enumId: enm.id, - variantId: variant.id, - fieldId: field.id, - name: value, - }, - ); - }} - class="-mx-1" - > - { - e.stopPropagation(); + interfaceCtx.execute( + "deleteCustomEnumVariantField", + { + enumId: enm.id, + variantId: variant.id, + fieldId: field.id, + }, + ); + }} + > + + + + + + + + interfaceCtx.execute( + "moveCustomEnumVariantFieldToIndex", + { + enumId: enm.id, + variantId: variant.id, + fieldId: field.id, + currentIndex: i(), + newIndex: i() - 1, + }, + ) + } + > + + Move Up + + + + + interfaceCtx.execute( + "moveCustomEnumVariantFieldToIndex", + { + enumId: enm.id, + variantId: variant.id, + fieldId: field.id, + currentIndex: i(), + newIndex: i() + 1, + }, + ) + } + > + + Move Down + + + + + - interfaceCtx.execute( - "deleteCustomEnumVariantField", - { - enumId: enm.id, - variantId: variant.id, - fieldId: field.id, - }, - ); - }} - > - - - - -
            - { - interfaceCtx.execute( - "setCustomEnumVariantFieldType", - { - enumId: enm.id, - variantId: variant.id, - fieldId: field.id, - type, - }, - ); - }} - /> -
            -
          • - )} -
            -
          -
          -
        • - )} -
          -
        -
        -
      • - )} -
        - -
  • - - - ); +
    + { + interfaceCtx.execute( + "setCustomEnumVariantFieldType", + { + enumId: enm.id, + variantId: variant.id, + fieldId: field.id, + type, + }, + ); + }} + /> +
    + + )} + + + + + )} + + + + + )} + + +
    +
    +
    + ); } diff --git a/interface/src/Sidebar/Project/Graphs.tsx b/interface/src/Sidebar/Project/Graphs.tsx index d7b4c3f1..d9bd171f 100644 --- a/interface/src/Sidebar/Project/Graphs.tsx +++ b/interface/src/Sidebar/Project/Graphs.tsx @@ -19,6 +19,7 @@ import { import { ContextMenuContent, ContextMenuItem, + ContextMenuRenameItem, } from "../../components/Graph/ContextMenu"; import { SidebarSection } from "../../components/Sidebar"; import { IconButton } from "../../components/ui"; @@ -108,101 +109,78 @@ export function Graphs(props: Props) {
  • - - {(_) => { - const inlineEditorContext = useInlineTextEditorCtx()!; - - return ( - - - as={(asProps) => ( - - {...asProps} - as="button" - type="button" - onClick={() => props.onGraphClicked(graph)} - /> - )} - selected={props.currentGraph === graph.id} - value={graph.name} - onChange={(value) => { - interfaceCtx.execute("setGraphName", { + + + as={(asProps) => ( + + {...asProps} + as="button" + type="button" + onClick={() => props.onGraphClicked(graph)} + /> + )} + selected={props.currentGraph === graph.id} + value={graph.name} + onChange={(value) => { + interfaceCtx.execute("setGraphName", { + graphId: graph.id, + name: value, + }); + }} + /> + + + { + writeClipboardItemToClipboard( + graphToClipboardItem(graph), + ); + }} + > + + Copy + + + + + interfaceCtx.execute("moveGraphToIndex", { graphId: graph.id, - name: value, - }); - }} - /> - - - inlineEditorContext.setEditing(true) - } - > - Rename - - { - writeClipboardItemToClipboard( - graphToClipboardItem(graph), - ); - }} - > - - Copy - - - - - interfaceCtx.execute( - "moveGraphToIndex", - { - graphId: graph.id, - currentIndex: i(), - newIndex: i() - 1, - }, - ) - } - > - - Move Up - - - - - interfaceCtx.execute( - "moveGraphToIndex", - { - graphId: graph.id, - currentIndex: i(), - newIndex: i() + 1, - }, - ) - } - > - - Move Down - - - - - { - setDeleteOpen(true); - }} - > - - Delete - - - - ); - }} - + currentIndex: i(), + newIndex: i() - 1, + }) + } + > + + Move Up + + + + + interfaceCtx.execute("moveGraphToIndex", { + graphId: graph.id, + currentIndex: i(), + newIndex: i() + 1, + }) + } + > + + Move Down + + + + { + setDeleteOpen(true); + }} + > + + Delete + + + diff --git a/interface/src/actions.ts b/interface/src/actions.ts index b7b11e8a..0494a79f 100644 --- a/interface/src/actions.ts +++ b/interface/src/actions.ts @@ -22,6 +22,7 @@ import { splitIORef, } from "@macrograph/runtime"; import { + createDeferrer, deserializeCommentBox, deserializeConnections, deserializeCustomEnum, @@ -942,10 +943,13 @@ export const historyActions = (core: Core, editor: EditorState) => ({ core.project.customStructs.delete(entry.structId); }, rewind(entry) { + const deferrer = createDeferrer(); const struct = deserializeCustomStruct( core.project, v.parse(serde.CustomStruct, entry.data), + deferrer, ); + deferrer.run(); core.project.customStructs.set(entry.structId, struct); }, @@ -1087,9 +1091,11 @@ export const historyActions = (core: Core, editor: EditorState) => ({ core.project.customEnums.delete(entry.enumId); }, rewind(entry) { + const deferrer = createDeferrer(); const enm = deserializeCustomEnum( core.project, v.parse(serde.CustomEnum, entry.data), + deferrer, ); core.project.customEnums.set(entry.enumId, enm); @@ -2055,23 +2061,152 @@ export const historyActions = (core: Core, editor: EditorState) => ({ return input; }, perform(entry) { - if (entry.newIndex < entry.currentIndex) { - core.project.graphOrder.splice(entry.currentIndex, 1); - core.project.graphOrder.splice(entry.newIndex, 0, entry.graphId); - } else { - console.log(entry); - core.project.graphOrder.splice(entry.currentIndex, 1); - core.project.graphOrder.splice(entry.newIndex, 0, entry.graphId); - } + core.project.graphOrder.splice(entry.currentIndex, 1); + core.project.graphOrder.splice(entry.newIndex, 0, entry.graphId); }, rewind(entry) { - if (entry.newIndex < entry.currentIndex) { - core.project.graphOrder.splice(entry.newIndex, 0, entry.graphId); - core.project.graphOrder.splice(entry.currentIndex, 1); - } else { - core.project.graphOrder.splice(entry.currentIndex, 1); - core.project.graphOrder.splice(entry.newIndex, 0, entry.graphId); - } + core.project.graphOrder.splice(entry.newIndex, 1); + core.project.graphOrder.splice(entry.currentIndex, 0, entry.graphId); + }, + }), + moveCustomEventFieldToIndex: historyAction({ + prepare(input: { + eventId: number; + fieldId: string; + currentIndex: number; + newIndex: number; + }) { + const event = core.project.customEvents.get(input.eventId); + if (!event) return; + + const field = event.field(input.fieldId); + if (!field) return; + if (event.fields[input.currentIndex] !== field) return; + if (input.currentIndex === input.newIndex) return; + + return input; + }, + perform(entry) { + const event = core.project.customEvents.get(entry.eventId); + if (!event) return; + + const [field] = event.fields.splice(entry.currentIndex, 1); + event.fields.splice(entry.newIndex, 0, field!); + }, + rewind(entry) { + const event = core.project.customEvents.get(entry.eventId); + if (!event) return; + + const [field] = event.fields.splice(entry.currentIndex, 1); + event.fields.splice(entry.newIndex, 0, field!); + }, + }), + moveCustomStructFieldToIndex: historyAction({ + prepare(input: { + structId: number; + fieldId: string; + currentIndex: number; + newIndex: number; + }) { + const struct = core.project.customStructs.get(input.structId); + if (!struct) return; + + const field = struct.fields[input.fieldId]; + if (!field) return; + if (struct.fieldOrder[input.currentIndex] !== field.id) return; + if (input.currentIndex === input.newIndex) return; + + return input; + }, + perform(entry) { + const struct = core.project.customStructs.get(entry.structId); + if (!struct) return; + + struct.fieldOrder.splice(entry.currentIndex, 1); + struct.fieldOrder.splice(entry.newIndex, 0, entry.fieldId); + }, + rewind(entry) { + const struct = core.project.customStructs.get(entry.structId); + if (!struct) return; + + struct.fieldOrder.splice(entry.newIndex, 1); + struct.fieldOrder.splice(entry.currentIndex, 0, entry.fieldId); + }, + }), + moveCustomEnumVariantToIndex: historyAction({ + prepare(input: { + enumId: number; + variantId: string; + currentIndex: number; + newIndex: number; + }) { + if (input.currentIndex === input.newIndex) return; + + const enm = core.project.customEnums.get(input.enumId); + if (!enm) return; + + const variant = enm.variants[input.currentIndex]; + if (!variant) return; + if (variant.id !== input.variantId) return; + + return input; + }, + perform(entry) { + const enm = core.project.customEnums.get(entry.enumId); + if (!enm) return; + + const [variant] = enm.variants.splice(entry.currentIndex, 1); + enm.variants.splice(entry.newIndex, 0, variant!); + }, + rewind(entry) { + const enm = core.project.customEnums.get(entry.enumId); + if (!enm) return; + + const [variant] = enm.variants.splice(entry.newIndex, 1); + enm.variants.splice(entry.currentIndex, 0, variant!); + }, + }), + moveCustomEnumVariantFieldToIndex: historyAction({ + prepare(input: { + enumId: number; + variantId: string; + fieldId: string; + currentIndex: number; + newIndex: number; + }) { + const enm = core.project.customEnums.get(input.enumId); + if (!enm) return; + + const variant = enm.variant(input.variantId); + if (!variant) return; + + const field = variant.fields[input.fieldId]; + if (!field) return; + + if (variant.fieldOrder[input.currentIndex] !== field.id) return; + if (input.currentIndex === input.newIndex) return; + + return input; + }, + perform(entry) { + const enm = core.project.customEnums.get(entry.enumId); + if (!enm) return; + + const variant = enm.variant(entry.variantId); + if (!variant) return; + + variant.fieldOrder.splice(entry.currentIndex, 1); + variant.fieldOrder.splice(entry.newIndex, 0, entry.fieldId); + }, + rewind(entry) { + const enm = core.project.customEnums.get(entry.enumId); + if (!enm) return; + + const variant = enm.variant(entry.variantId); + if (!variant) return; + + variant.fieldOrder.splice(entry.newIndex, 1); + variant.fieldOrder.splice(entry.currentIndex, 0, entry.fieldId); }, }), }); diff --git a/interface/src/components/Graph/ContextMenu.tsx b/interface/src/components/Graph/ContextMenu.tsx index 29370408..e0aab0c6 100644 --- a/interface/src/components/Graph/ContextMenu.tsx +++ b/interface/src/components/Graph/ContextMenu.tsx @@ -1,13 +1,24 @@ import { ContextMenu } from "@kobalte/core"; import type { ComponentProps } from "solid-js"; - import clsx from "clsx"; + import { tw } from "../../util"; +import { useInlineTextEditorCtx } from "../../Sidebar/InlineTextEditor"; export const ContextMenuItem = tw( ContextMenu.Item, )`px-1.5 py-1.5 outline-none ui-highlighted:bg-white/10 rounded-sm flex flex-row items-center gap-2`; +export function ContextMenuRenameItem() { + const inlineEditorContext = useInlineTextEditorCtx()!; + + return ( + inlineEditorContext.setEditing(true)}> + Rename + + ); +} + export function ContextMenuContent( props: Omit>, "onKeyDown">, ) { diff --git a/packages/runtime-serde/src/deserialize.ts b/packages/runtime-serde/src/deserialize.ts index 9c5837a9..302991eb 100644 --- a/packages/runtime-serde/src/deserialize.ts +++ b/packages/runtime-serde/src/deserialize.ts @@ -152,6 +152,7 @@ export function deserializeCustomStruct( }); struct.fieldIdCounter = data.fieldIdCounter; + struct.fieldOrder = data.fields.map((f) => f.id); deferrer.defer(() => { for (const field of data.fields) { @@ -196,6 +197,7 @@ export function deserializeCustomEnumVariant( ); variant.fieldIdCounter = data.fieldIdCounter; + variant.fieldOrder = data.fields.map((f) => f.id); for (const field of data.fields) { variant.fields[field.id] = deserializeField(enm.project, field); diff --git a/packages/runtime-serde/src/serialize.ts b/packages/runtime-serde/src/serialize.ts index 357094e2..591f225d 100644 --- a/packages/runtime-serde/src/serialize.ts +++ b/packages/runtime-serde/src/serialize.ts @@ -118,7 +118,14 @@ export function serializeCustomStruct( return { id: s.id, name: s.name, - fields: Object.values(s.fields).map(serializeCustomStructField), + fields: s.fieldOrder + .map((id) => { + const field = s.fields[id]; + if (!field) return; + + return serializeCustomStructField(field); + }) + .filter(Boolean), fieldIdCounter: s.fieldIdCounter, }; } @@ -145,12 +152,19 @@ export function serializeCustomEnum( } export function serializeCustomEnumVariant( - v: EnumVariant, + v: runtime.CustomEnumVariant, ): v.InferInput { return { id: v.id, display: v.name, - fields: Object.values(v.fields ?? {}).map(serializeField), + fields: v.fieldOrder + .map((id) => { + const field = v.fields[id]; + if (!field) return; + + return serializeField(field); + }) + .filter(Boolean), fieldIdCounter: v.fieldIdCounter, }; } diff --git a/packages/runtime/src/models/CustomEnum.ts b/packages/runtime/src/models/CustomEnum.ts index 2d79e30b..5d6a58b1 100644 --- a/packages/runtime/src/models/CustomEnum.ts +++ b/packages/runtime/src/models/CustomEnum.ts @@ -1,89 +1,98 @@ import { - EnumBase, - EnumVariant, - type EnumVariantFields, - Field, - t, + EnumBase, + EnumVariant, + type EnumVariantFields, + Field, + t, } from "@macrograph/typesystem"; import { createMutable } from "solid-js/store"; import type { Project } from "./Project"; export type CustomEnumVariants = [ - one: CustomEnumVariant, - ...variant: CustomEnumVariant[], + one: CustomEnumVariant, + ...variant: CustomEnumVariant[], ]; export class CustomEnum extends EnumBase { - id: number; - name: string; - project: Project; - variants: CustomEnumVariants; - - variantIdCounter = 0; - - constructor(args: { - id: number; - project: Project; - name?: string; - variants?: CustomEnumVariants; - }) { - super(); - - this.id = args.id; - this.project = args.project; - this.name = args?.name ?? ""; - this.variants = createMutable( - args?.variants ?? [new CustomEnumVariant("New Variant", {})], - ); - this.source = { variant: "custom", id: this.id }; - - return createMutable(this); - } - - variant(id: string) { - return this.variants.find((variant) => variant.id === id); - } - - createVariant(args?: { id?: string }) { - const id = (args?.id ?? this.variantIdCounter++).toString(); - - this.variants.push(new CustomEnumVariant(id, {}, "New Variant")); - - return id; - } - - removeVariant(id: string) { - const index = this.variants.findIndex((variant) => variant.id === id); - if (index === -1) return; - - this.variants.splice(index, 1); - } + id: number; + name: string; + project: Project; + variants: CustomEnumVariants; + + variantIdCounter = 0; + + constructor(args: { + id: number; + project: Project; + name?: string; + variants?: CustomEnumVariants; + }) { + super(); + + this.id = args.id; + this.project = args.project; + this.name = args?.name ?? ""; + this.variants = createMutable( + args?.variants ?? [new CustomEnumVariant("New Variant", {})], + ); + this.source = { variant: "custom", id: this.id }; + + return createMutable(this); + } + + variant(id: string) { + return this.variants.find((variant) => variant.id === id); + } + + createVariant(args?: { id?: string }) { + const id = (args?.id ?? this.variantIdCounter++).toString(); + + this.variants.push(new CustomEnumVariant(id, {}, "New Variant")); + + return id; + } + + removeVariant(id: string) { + const index = this.variants.findIndex((variant) => variant.id === id); + if (index === -1) return; + + this.variants.splice(index, 1); + } } export class CustomEnumVariant< - Id extends string, - Fields extends EnumVariantFields, + Id extends string, + Fields extends EnumVariantFields, > extends EnumVariant { - field(id: string) { - return this.fields[id]; - } - - createField(args?: { id?: string }) { - const id = (args?.id ?? this.fieldIdCounter++).toString(); - - if (this.fields) - (this.fields[id] as any) = new Field(id, t.string(), "New Field"); - - return id; - } - - removeField(id: string) { - delete this.fields[id]; - } - - editFieldType(id: string, type: t.Any) { - const field = this.fields[id]; - if (!field) return; - field.type = type; - } + fieldOrder: Array = []; + + field(id: string) { + return this.fields[id]; + } + + createField(args?: { id?: string }) { + const id = (args?.id ?? this.fieldIdCounter++).toString(); + + if (this.fields) { + (this.fields[id] as any) = new Field(id, t.string(), "New Field"); + this.fieldOrder.push(id); + } + + return id; + } + + removeField(id: string) { + delete this.fields[id]; + const index = this.fieldOrder.indexOf(id); + if (index > -1) { + this.fieldOrder.splice(index, 1); + return index; + } + } + + editFieldType(id: string, type: t.Any) { + const field = this.fields[id]; + if (!field) return; + field.type = type; + } } diff --git a/packages/runtime/src/models/CustomEvent.ts b/packages/runtime/src/models/CustomEvent.ts index 57725a62..f13eb3aa 100644 --- a/packages/runtime/src/models/CustomEvent.ts +++ b/packages/runtime/src/models/CustomEvent.ts @@ -4,9 +4,9 @@ import { createMutable } from "solid-js/store"; import type { Project } from "./Project"; export interface EventArgs { - id: number; - name: string; - project: Project; + id: number; + name: string; + project: Project; } // const Source = z.discriminatedUnion("variant", [ @@ -15,51 +15,51 @@ export interface EventArgs { // ]); export class CustomEvent { - id: number; - name: string; - project: Project; + id: number; + name: string; + project: Project; - fields: Array = []; + fields: Array = []; - fieldIdCounter = 0; + fieldIdCounter = 0; - constructor(args: EventArgs) { - this.id = args.id; - this.name = args.name; - this.project = args.project; + constructor(args: EventArgs) { + this.id = args.id; + this.name = args.name; + this.project = args.project; - this.createField(); - return createMutable(this); - } + this.createField(); + return createMutable(this); + } - generateId() { - return this.fieldIdCounter++; - } + generateId() { + return this.fieldIdCounter++; + } - createField(args?: { id?: string }) { - const id = args?.id ?? this.generateId().toString(); - this.fields.push(new Field(id, t.string(), "New Field")); - } + createField(args?: { id?: string }) { + const id = args?.id ?? this.generateId().toString(); + this.fields.push(new Field(id, t.string(), "New Field")); + } - field(id: string) { - return this.fields.find((f) => f.id === id.toString()); - } + field(id: string) { + return this.fields.find((f) => f.id === id.toString()); + } - editFieldName(id: string, name?: string) { - const pin = this.fields.find((f) => f.id === id.toString()); - if (!pin) return; - pin.name = name; - } + editFieldName(id: string, name?: string) { + const pin = this.fields.find((f) => f.id === id.toString()); + if (!pin) return; + pin.name = name; + } - editFieldType(id: string, type: PrimitiveType) { - const pin = this.fields.find((f) => f.id === id.toString()); - if (!pin) return; - pin.type = type; - } + editFieldType(id: string, type: PrimitiveType) { + const pin = this.fields.find((f) => f.id === id.toString()); + if (!pin) return; + pin.type = type; + } - deleteField(id: string) { - const index = this.fields.findIndex((f) => f.id === id.toString()); - if (index === -1) return; - this.fields.splice(index, 1); - } + deleteField(id: string) { + const index = this.fields.findIndex((f) => f.id === id.toString()); + if (index === -1) return; + this.fields.splice(index, 1); + } } diff --git a/packages/runtime/src/models/CustomStruct.ts b/packages/runtime/src/models/CustomStruct.ts index f191f283..52796888 100644 --- a/packages/runtime/src/models/CustomStruct.ts +++ b/packages/runtime/src/models/CustomStruct.ts @@ -1,55 +1,58 @@ import { - Field, - StructBase, - type StructFields, - t, + Field, + StructBase, + type StructFields, + t, } from "@macrograph/typesystem"; import { createMutable } from "solid-js/store"; import type { Project } from "./Project"; export class CustomStruct extends StructBase { - id: number; - name: string; - project: Project; - fields: StructFields; - - fieldIdCounter = 0; - - constructor(args: { - id: number; - project: Project; - name?: string; - fields?: Record; - }) { - super(); - - this.id = args.id; - this.project = args.project; - this.name = args?.name ?? ""; - this.fields = createMutable(args?.fields ?? {}); - this.source = { variant: "custom", id: this.id }; - - this.createField(); - - return createMutable(this); - } - - createField(args?: { id?: string }) { - const id = (args?.id ?? this.fieldIdCounter++).toString(); - - this.fields[id] = new Field(id, t.string(), "New Field"); - - return id; - } - - removeField(id: string) { - delete this.fields[id]; - } - - editFieldType(id: string, type: t.Any) { - const field = this.fields[id]; - if (!field) return; - field.type = type; - } + id: number; + name: string; + project: Project; + fields: StructFields; + fieldOrder: Array = []; + + fieldIdCounter = 0; + + constructor(args: { + id: number; + project: Project; + name?: string; + fields?: Record; + }) { + super(); + + this.id = args.id; + this.project = args.project; + this.name = args?.name ?? ""; + this.fields = createMutable(args?.fields ?? {}); + this.source = { variant: "custom", id: this.id }; + + this.createField(); + + return createMutable(this); + } + + createField(args?: { id?: string }) { + const id = (args?.id ?? this.fieldIdCounter++).toString(); + + this.fields[id] = new Field(id, t.string(), "New Field"); + this.fieldOrder.push(id); + + return id; + } + + removeField(id: string) { + delete this.fields[id]; + this.fieldOrder.splice(this.fieldOrder.indexOf(id), 1); + } + + editFieldType(id: string, type: t.Any) { + const field = this.fields[id]; + if (!field) return; + field.type = type; + } } diff --git a/packages/runtime/src/models/Project.ts b/packages/runtime/src/models/Project.ts index bea34f31..d525e577 100644 --- a/packages/runtime/src/models/Project.ts +++ b/packages/runtime/src/models/Project.ts @@ -31,7 +31,10 @@ export type ProjectEvent = "modified"; export class Project { core: Core; + graphs = new ReactiveMap(); + graphOrder: Array = []; + customEvents = new ReactiveMap(); customStructs = new ReactiveMap(); customEnums = new ReactiveMap(); @@ -47,8 +50,6 @@ export class Project { customTypeIdCounter = 0; idCounter = 0; - graphOrder: Array = []; - constructor(args: ProjectArgs) { this.core = args.core; diff --git a/packages/typesystem/src/enum.ts b/packages/typesystem/src/enum.ts index f13ecc47..1ddd3dd0 100644 --- a/packages/typesystem/src/enum.ts +++ b/packages/typesystem/src/enum.ts @@ -6,208 +6,208 @@ import { BaseType } from "./base"; export type EnumVariantFields = Record; export class EnumVariant< - Id extends string, - Fields extends EnumVariantFields | null, + Id extends string, + Fields extends EnumVariantFields | null, > { - fieldIdCounter = 0; - - constructor( - public id: Id, - public fields: Fields, - public name?: string, - ) { - return createMutable(this); - } - - default() { - const data = this.fields; - - return data === null - ? { variant: this.id } - : { - variant: this.id, - data: Object.entries(data).reduce( - (acc, [name, type]) => - Object.assign(acc, { [name]: type.default() }), - {}, - ), - }; - } + fieldIdCounter = 0; + + constructor( + public id: Id, + public fields: Fields, + public name?: string, + ) { + return createMutable(this); + } + + default() { + const data = this.fields; + + return data === null + ? { variant: this.id } + : { + variant: this.id, + data: Object.entries(data).reduce( + (acc, [name, type]) => + Object.assign(acc, { [name]: type.default() }), + {}, + ), + }; + } } export type EnumVariants = [ - one: EnumVariant, - ...variant: EnumVariant[], + one: EnumVariant, + ...variant: EnumVariant[], ]; export class LazyEnumVariants { - constructor(public build: () => Variants) {} + constructor(public build: () => Variants) {} } export abstract class EnumBase { - source!: - | { variant: "package"; package: string } - | { variant: "custom"; id: number }; - abstract name: string; - abstract variants: Variants; + source!: + | { variant: "package"; package: string } + | { variant: "custom"; id: number }; + abstract name: string; + abstract variants: Variants; } export class Enum< - Variants extends EnumVariants = EnumVariants, + Variants extends EnumVariants = EnumVariants, > extends EnumBase { - constructor( - public name: string, - variants: Variants | LazyEnumVariants, - ) { - super(); - - if (variants instanceof LazyEnumVariants) - this._variants = { type: "lazy", variants }; - else this._variants = { type: "resolved", variants }; - - return createMutable(this); - } - - _variants: - | { type: "resolved"; variants: Variants } - | { type: "lazy"; variants: LazyEnumVariants }; - - get variants(): Variants { - let val = this._variants; - - if (val.type === "lazy") { - this._variants = val = { - type: "resolved", - variants: val.variants.build(), - }; - } - - return val.variants; - } - - variant( - val: InferEnumVariantData< - Extract["fields"] - > extends null - ? Id - : [ - Id, - InferEnumVariantData["fields"]>, - ], - ): InferEnumVariant> { - if (Array.isArray(val)) return { variant: val[0], data: val[1] } as any; - - return { variant: val } as any; - } + constructor( + public name: string, + variants: Variants | LazyEnumVariants, + ) { + super(); + + if (variants instanceof LazyEnumVariants) + this._variants = { type: "lazy", variants }; + else this._variants = { type: "resolved", variants }; + + return createMutable(this); + } + + _variants: + | { type: "resolved"; variants: Variants } + | { type: "lazy"; variants: LazyEnumVariants }; + + get variants(): Variants { + let val = this._variants; + + if (val.type === "lazy") { + this._variants = val = { + type: "resolved", + variants: val.variants.build(), + }; + } + + return val.variants; + } + + variant( + val: InferEnumVariantData< + Extract["fields"] + > extends null + ? Id + : [ + Id, + InferEnumVariantData["fields"]>, + ], + ): InferEnumVariant> { + if (Array.isArray(val)) return { variant: val[0], data: val[1] } as any; + + return { variant: val } as any; + } } export class EnumBuilder { - variant(name: Name): EnumVariant; - variant>( - name: Name, - fields: Fields, - ): EnumVariant>; - variant>( - name: Name, - fields?: Fields, - ): EnumVariant> { - let _fields: any; - - if (fields) { - _fields = {}; - for (const [key, value] of Object.entries(fields)) { - _fields[key] = new Field(key, value); - } - } - - return new EnumVariant(name, _fields ?? null) as any; - } - - lazy(fn: () => T) { - return new LazyEnumVariants(fn); - } + variant(name: Name): EnumVariant; + variant>( + name: Name, + fields: Fields, + ): EnumVariant>; + variant>( + name: Name, + fields?: Fields, + ): EnumVariant> { + let _fields: any; + + if (fields) { + _fields = {}; + for (const [key, value] of Object.entries(fields)) { + _fields[key] = new Field(key, value); + } + } + + return new EnumVariant(name, _fields ?? null) as any; + } + + lazy(fn: () => T) { + return new LazyEnumVariants(fn); + } } type VariantFieldFromTypes | null> = - T extends Record ? { [K in keyof T]: Field } : null; + T extends Record ? { [K in keyof T]: Field } : null; export class EnumType> extends BaseType< - InferEnum + InferEnum > { - constructor(public inner: TEnum) { - super(); - } - - default(): any { - return this.inner.variants[0].default(); - } - - variant(): TypeVariant { - return "enum"; - } - - toString(): string { - return `Enum(${this.inner.name})`; - } - - // asZodType(): z.ZodType> { - // return z.union( - // (this.inner.variants as EnumVariants).map((v) => - // z.object({ - // variant: z.literal(v.name), - // ...(v.data === null - // ? undefined - // : { - // data: z.object( - // Object.entries(v.data).reduce( - // (acc, [key, value]) => ({ - // ...acc, - // [key]: z.lazy(() => value.asZodType()), - // }), - // {} - // ) - // ), - // }), - // }) - // ) as any - // ); - // } - - getWildcards(): Wildcard[] { - return (this.inner.variants as EnumVariants).flatMap((v) => - v.fields - ? Object.values(v.fields).flatMap((d) => d.type.getWildcards()) - : [], - ); - } - - eq(other: t.Any): boolean { - return other instanceof t.Enum && this.inner === other.inner; - } - - serialize() { - return { - variant: "enum", - enum: { ...this.inner.source, name: this.inner.name }, - }; - } - - hasUnconnectedWildcard(): boolean { - return false; - } + constructor(public inner: TEnum) { + super(); + } + + default(): any { + return this.inner.variants[0].default(); + } + + variant(): TypeVariant { + return "enum"; + } + + toString(): string { + return `Enum(${this.inner.name})`; + } + + // asZodType(): z.ZodType> { + // return z.union( + // (this.inner.variants as EnumVariants).map((v) => + // z.object({ + // variant: z.literal(v.name), + // ...(v.data === null + // ? undefined + // : { + // data: z.object( + // Object.entries(v.data).reduce( + // (acc, [key, value]) => ({ + // ...acc, + // [key]: z.lazy(() => value.asZodType()), + // }), + // {} + // ) + // ), + // }), + // }) + // ) as any + // ); + // } + + getWildcards(): Wildcard[] { + return (this.inner.variants as EnumVariants).flatMap((v) => + v.fields + ? Object.values(v.fields).flatMap((d) => d.type.getWildcards()) + : [], + ); + } + + eq(other: t.Any): boolean { + return other instanceof t.Enum && this.inner === other.inner; + } + + serialize() { + return { + variant: "enum", + enum: { ...this.inner.source, name: this.inner.name }, + }; + } + + hasUnconnectedWildcard(): boolean { + return false; + } } export type InferEnum> = InferEnumVariant< - E["variants"][number] + E["variants"][number] >; export type InferEnumVariant> = - V extends EnumVariant - ? Data extends null - ? { variant: Name } - : { variant: Name; data: InferEnumVariantData } - : V; + V extends EnumVariant + ? Data extends null + ? { variant: Name } + : { variant: Name; data: InferEnumVariantData } + : V; export type InferEnumVariantData = D extends EnumVariantFields - ? { [K in keyof D]: D[K] extends Field ? t.infer : never } - : never; + ? { [K in keyof D]: D[K] extends Field ? t.infer : never } + : never;