diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index c71b3ff332..d8ae44eda4 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -19,7 +19,7 @@ module.exports = { parserOptions: { ecmaVersion: "latest", project: "./tsconfig.json", - extraFileExtensions: [".svelte"], + extraFileExtensions: [".svelte", ".svelte.ts"], }, ignorePatterns: [ // Ignore generated directories @@ -33,7 +33,7 @@ module.exports = { ], overrides: [ { - files: ["*.svelte"], + files: ["*.svelte", "*.svelte.ts"], parser: "svelte-eslint-parser", // Parse the ` - + - + {#if !isNone} - + {/if} {#if alignedAxis} + > {/if} - + {#if !isNone} - + {/if} - + {#if !isNone} - + {/if} @@ -447,19 +459,19 @@ { - gradient = gradient; - if (gradient) dispatch("colorOrGradient", gradient); + ongradient={(detail) => { + gradient = detail; + if (gradient) oncolorOrGradient?.(gradient); }} - on:activeMarkerIndexChange={gradientActiveMarkerIndexChange} + onactiveMarkerIndexChange={gradientActiveMarkerIndexChange} activeMarkerIndex={activeIndex} - on:dragging={({ detail }) => (gradientSpectrumDragging = detail)} + bind:drag={gradientSpectrumDragging} bind:this={gradientSpectrumInputWidget} /> {#if gradientSpectrumInputWidget && activeIndex !== undefined} { + onvalue={(detail) => { if (gradientSpectrumInputWidget && activeIndex !== undefined && detail !== undefined) gradientSpectrumInputWidget.setPosition(activeIndex, detail / 100); }} displayDecimalPlaces={0} @@ -480,7 +492,7 @@ > {#if !newColor.equals(oldColor)} - + {/if} {#if !newColor.equals(oldColor)} @@ -499,9 +511,9 @@ { - dispatch("startHistoryTransaction"); + value={newColor.toHexOptionalAlpha() ?? "-"} + oncommitText={(detail) => { + onstartHistoryTransaction?.(); setColorCode(detail); }} centered={true} @@ -514,19 +526,17 @@ RGB - {#each rgbChannels as [channel, strength], index} + {#each rgbChannels as [channel, _strength], index} {#if index > 0} {/if} { - strength = detail; + value={rgbChannels[index][1]} + onvalue={(detail) => { + rgbChannels[index][1] = detail; setColorRGB(channel, detail); }} - on:startHistoryTransaction={() => { - dispatch("startHistoryTransaction"); - }} + {onstartHistoryTransaction} min={0} max={255} minWidth={1} @@ -541,19 +551,17 @@ - {#each hsvChannels as [channel, strength], index} + {#each hsvChannels as [channel, _strength], index} {#if index > 0} {/if} { - strength = detail; + value={hsvChannels[index][1]} + onvalue={(detail) => { + hsvChannels[index][1] = detail; setColorHSV(channel, detail); }} - on:startHistoryTransaction={() => { - dispatch("startHistoryTransaction"); - }} + {onstartHistoryTransaction} min={0} max={channel === "h" ? 360 : 100} unit={channel === "h" ? "°" : "%"} @@ -573,13 +581,11 @@ { + onvalue={(detail) => { if (detail !== undefined) alpha = detail / 100; setColorAlphaPercent(detail); }} - on:startHistoryTransaction={() => { - dispatch("startHistoryTransaction"); - }} + {onstartHistoryTransaction} min={0} max={100} rangeMin={0} @@ -593,23 +599,23 @@ {#if allowNone && !gradient} - setColorPreset("none")} title="Set to no color" tabindex="0"> + setColorPreset("none")} title="Set to no color" tabindex="0"> {/if} - setColorPreset("black")} title="Set to black" tabindex="0"> + setColorPreset("black")} title="Set to black" tabindex="0"> - setColorPreset("white")} title="Set to white" tabindex="0"> + setColorPreset("white")} title="Set to white" tabindex="0"> - - - - - - - + + + + + + + - + diff --git a/frontend/src/components/floating-menus/Dialog.svelte b/frontend/src/components/floating-menus/Dialog.svelte index e9d0ba5fab..9157922271 100644 --- a/frontend/src/components/floating-menus/Dialog.svelte +++ b/frontend/src/components/floating-menus/Dialog.svelte @@ -16,7 +16,7 @@ const dialog = getContext("dialog"); - let self: FloatingMenu | undefined; + let self: FloatingMenu | undefined = $state(); onMount(() => { // Focus the button which is marked as emphasized, or otherwise the first button, in the popup @@ -41,14 +41,14 @@ The editor crashed — sorry about that Please report this by filing an issue on GitHub: - window.open(githubUrl($dialog.panicDetails), "_blank")} /> + window.open(githubUrl($dialog.panicDetails), "_blank")} /> Reload the editor to continue. If this occursimmediately on repeated reloads, clear storage: { + onclick={async () => { await wipeDocuments(); window.location.reload(); }} @@ -68,8 +68,8 @@ {/if} {#if $dialog.panicDetails} - navigator.clipboard.writeText($dialog.panicDetails)} /> - window.location.reload()} /> + navigator.clipboard.writeText($dialog.panicDetails)} /> + window.location.reload()} /> {/if} diff --git a/frontend/src/components/floating-menus/EyedropperPreview.svelte b/frontend/src/components/floating-menus/EyedropperPreview.svelte index 4ae69182c1..aafffbbde3 100644 --- a/frontend/src/components/floating-menus/EyedropperPreview.svelte +++ b/frontend/src/components/floating-menus/EyedropperPreview.svelte @@ -1,4 +1,4 @@ - @@ -56,8 +56,8 @@ > - - + + diff --git a/frontend/src/components/floating-menus/MenuList.svelte b/frontend/src/components/floating-menus/MenuList.svelte index 0695d38305..c0a182d40b 100644 --- a/frontend/src/components/floating-menus/MenuList.svelte +++ b/frontend/src/components/floating-menus/MenuList.svelte @@ -1,9 +1,5 @@ - - (open = detail)} - on:naturalWidth + bind:open type="Dropdown" windowEdgeMargin={0} escapeCloses={false} {direction} {minWidth} scrollableY={scrollableY && virtualScrollingEntryHeight === 0} + {onnaturalWidth} bind:this={self} > {#if search.length > 0} - (search = detail)} bind:this={searchTextInput}> + {/if} {#if virtualScrollingEntryHeight} @@ -424,14 +451,14 @@ classes={{ open: isEntryOpen(entry), active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }} styles={{ height: virtualScrollingEntryHeight || "20px" }} {tooltip} - on:click={() => !entry.disabled && onEntryClick(entry)} - on:pointerenter={() => !entry.disabled && onEntryPointerEnter(entry)} - on:pointerleave={() => !entry.disabled && onEntryPointerLeave(entry)} + onclick={() => !entry.disabled && onEntryClick(entry)} + onpointerenter={() => !entry.disabled && onEntryPointerEnter(entry)} + onpointerleave={() => !entry.disabled && onEntryPointerLeave(entry)} > {#if entry.icon && drawIcon} {:else if drawIcon} - + {/if} {#if entry.font} @@ -447,18 +474,13 @@ {#if entry.children?.length} {:else} - + {/if} {#if entry.children} { - // We do a manual dispatch here instead of just `on:naturalWidth` as a workaround for the - (searchTerm = detail)} bind:this={nodeSearchInput} /> - + + { + // onwheel events are passive by default + // https://svelte.dev/docs/svelte/v5-migration-guide#Breaking-changes-in-runes-mode-Touch-and-wheel-events-are-passive + event.stopPropagation(); + onwheel?.(event); + }} + > {#each nodeCategories as nodeCategory} {nodeCategory[0]} {#each nodeCategory[1].nodes as nodeType} - dispatch("selectNodeType", nodeType.name)} /> + onselectNodeType?.(nodeType.name)} /> {/each} {:else} diff --git a/frontend/src/components/layout/ConditionalWrapper.svelte b/frontend/src/components/layout/ConditionalWrapper.svelte index 46d5e80b04..c6ba7bac02 100644 --- a/frontend/src/components/layout/ConditionalWrapper.svelte +++ b/frontend/src/components/layout/ConditionalWrapper.svelte @@ -1,14 +1,21 @@ {#if condition} - + {@render children?.()} {:else} - + {@render children?.()} {/if} diff --git a/frontend/src/components/layout/FloatingMenu.svelte b/frontend/src/components/layout/FloatingMenu.svelte index e4070cb153..00fe3d147c 100644 --- a/frontend/src/components/layout/FloatingMenu.svelte +++ b/frontend/src/components/layout/FloatingMenu.svelte @@ -1,6 +1,4 @@ - - + {#if displayTail} - + {/if} {#if displayContainer} - + {@render children?.()} {/if} diff --git a/frontend/src/components/layout/LayoutCol.svelte b/frontend/src/components/layout/LayoutCol.svelte index b69b6bfabb..27a04063ca 100644 --- a/frontend/src/components/layout/LayoutCol.svelte +++ b/frontend/src/components/layout/LayoutCol.svelte @@ -1,23 +1,35 @@ - e.preventDefault()} on:drop={dropFile}> - - {#if !$document.graphViewOverlayOpen} - - + e.preventDefault()} ondrop={dropFile}> + + {#if !document.graphViewOverlayOpen} + + - + {:else} - + {/if} - {#if !$document.graphViewOverlayOpen} + {#if !document.graphViewOverlayOpen} - + {:else} {/if} - + @@ -517,13 +520,13 @@ y={cursorTop} /> {/if} - canvasPointerDown(e)} bind:this={viewport} data-viewport> + canvasPointerDown(e)} bind:this={viewport} data-viewport> {@html artworkSvg} {#if showTextInput} - + {/if} - + @@ -545,10 +548,10 @@ direction="Vertical" thumbLength={scrollbarSize.y} thumbPosition={scrollbarPos.y} - on:trackShift={({ detail }) => editor.handle.panCanvasByFraction(0, detail)} - on:thumbPosition={({ detail }) => panCanvasY(detail)} - on:thumbDragStart={() => editor.handle.panCanvasAbortPrepare(false)} - on:thumbDragAbort={() => editor.handle.panCanvasAbort(false)} + ontrackShift={(detail) => editor.handle.panCanvasByFraction(0, detail)} + onthumbPosition={(detail) => panCanvasY(detail)} + onthumbDragStart={() => editor.handle.panCanvasAbortPrepare(false)} + onthumbDragAbort={() => editor.handle.panCanvasAbort(false)} /> @@ -557,11 +560,11 @@ direction="Horizontal" thumbLength={scrollbarSize.x} thumbPosition={scrollbarPos.x} - on:trackShift={({ detail }) => editor.handle.panCanvasByFraction(detail, 0)} - on:thumbPosition={({ detail }) => panCanvasX(detail)} - on:thumbDragEnd={() => editor.handle.setGridAlignedEdges()} - on:thumbDragStart={() => editor.handle.panCanvasAbortPrepare(true)} - on:thumbDragAbort={() => editor.handle.panCanvasAbort(true)} + ontrackShift={(detail) => editor.handle.panCanvasByFraction(detail, 0)} + onthumbPosition={(detail) => panCanvasX(detail)} + onthumbDragEnd={() => editor.handle.setGridAlignedEdges()} + onthumbDragStart={() => editor.handle.panCanvasAbortPrepare(true)} + onthumbDragAbort={() => editor.handle.panCanvasAbort(true)} /> diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index 092a83fbac..d84b57c300 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -3,16 +3,7 @@ import type { Editor } from "@graphite/editor"; import { beginDraggingElement } from "@graphite/io-managers/drag"; - import { - defaultWidgetLayout, - patchWidgetLayout, - UpdateDocumentLayerDetails, - UpdateDocumentLayerStructureJs, - UpdateLayersPanelControlBarLeftLayout, - UpdateLayersPanelControlBarRightLayout, - UpdateLayersPanelBottomBarLayout, - } from "@graphite/messages"; - import type { DataBuffer, LayerPanelEntry } from "@graphite/messages"; + import type { NodeGraphState } from "@graphite/state-providers/node-graph"; import { platformIsMac } from "@graphite/utility-functions/platform"; import { extractPixelData } from "@graphite/utility-functions/rasterization"; @@ -23,6 +14,17 @@ import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import Separator from "@graphite/components/widgets/labels/Separator.svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; + import type { WidgetLayout as WidgetLayoutState } from "@graphite/messages.svelte"; + import type { DataBuffer, LayerPanelEntry } from "@graphite/messages.svelte"; + import { + defaultWidgetLayout, + patchWidgetLayout, + UpdateDocumentLayerDetails, + UpdateDocumentLayerStructureJs, + UpdateLayersPanelControlBarLeftLayout, + UpdateLayersPanelControlBarRightLayout, + UpdateLayersPanelBottomBarLayout, + } from "@graphite/messages.svelte"; type LayerListingInfo = { folderIndex: number; @@ -43,41 +45,38 @@ const editor = getContext("editor"); const nodeGraph = getContext("nodeGraph"); - let list: LayoutCol | undefined; + let list: LayoutCol | undefined = $state(); // Layer data let layerCache = new Map(); // TODO: replace with BigUint64Array as index - let layers: LayerListingInfo[] = []; + let layers: LayerListingInfo[] = $state([]); // Interactive dragging - let draggable = true; - let draggingData: undefined | DraggingData = undefined; - let fakeHighlightOfNotYetSelectedLayerBeingDragged: undefined | bigint = undefined; - let dragInPanel = false; + let draggable = $state(true); + let draggingData: undefined | DraggingData = $state(undefined); + let fakeHighlightOfNotYetSelectedLayerBeingDragged: undefined | bigint = $state(undefined); + let dragInPanel = $state(false); // Interactive clipping - let layerToClipUponClick: LayerListingInfo | undefined = undefined; - let layerToClipAltKeyPressed = false; + let layerToClipUponClick: LayerListingInfo | undefined = $state(undefined); + let layerToClipAltKeyPressed = $state(false); // Layouts - let layersPanelControlBarLeftLayout = defaultWidgetLayout(); - let layersPanelControlBarRightLayout = defaultWidgetLayout(); - let layersPanelBottomBarLayout = defaultWidgetLayout(); + let layersPanelControlBarLeftLayout = $state(defaultWidgetLayout()); + let layersPanelControlBarRightLayout = $state(defaultWidgetLayout()); + let layersPanelBottomBarLayout = $state(defaultWidgetLayout()); onMount(() => { editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarLeftLayout, (updateLayersPanelControlBarLeftLayout) => { patchWidgetLayout(layersPanelControlBarLeftLayout, updateLayersPanelControlBarLeftLayout); - layersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout; }); editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarRightLayout, (updateLayersPanelControlBarRightLayout) => { patchWidgetLayout(layersPanelControlBarRightLayout, updateLayersPanelControlBarRightLayout); - layersPanelControlBarRightLayout = layersPanelControlBarRightLayout; }); editor.subscriptions.subscribeJsMessage(UpdateLayersPanelBottomBarLayout, (updateLayersPanelBottomBarLayout) => { patchWidgetLayout(layersPanelBottomBarLayout, updateLayersPanelBottomBarLayout); - layersPanelBottomBarLayout = layersPanelBottomBarLayout; }); editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerStructureJs, (updateDocumentLayerStructure) => { @@ -182,7 +181,7 @@ draggable = false; listing.editingName = true; - layers = layers; + // layers = layers; await tick(); @@ -197,7 +196,7 @@ draggable = true; listing.editingName = false; - layers = layers; + // layers = layers; const name = (e.target instanceof HTMLInputElement && e.target.value) || ""; editor.handle.setLayerName(listing.entry.id, name); @@ -207,7 +206,7 @@ async function onEditLayerNameDeselect(listing: LayerListingInfo) { draggable = true; listing.editingName = false; - layers = layers; + // layers = layers; // Set it back to the original name if the user didn't enter a new name if (document.activeElement instanceof HTMLInputElement) document.activeElement.value = listing.entry.alias; @@ -472,7 +471,7 @@ }); }; recurse(updateDocumentLayerStructure); - layers = layers; + // layers = layers; } function updateLayerInTree(targetId: bigint, targetLayer: LayerPanelEntry) { @@ -481,12 +480,12 @@ const layer = layers.find((layer: LayerListingInfo) => layer.entry.id === targetId); if (layer) { layer.entry = targetLayer; - layers = layers; + // layers = layers; } } - (dragInPanel = false)}> + (dragInPanel = false)}> @@ -498,10 +497,10 @@ styles={{ cursor: layerToClipUponClick && layerToClipAltKeyPressed && layerToClipUponClick.entry.clippable ? "alias" : "auto" }} data-layer-panel bind:this={list} - on:click={() => deselectAllLayers()} - on:dragover={updateInsertLine} - on:dragend={drop} - on:drop={drop} + onclick={() => deselectAllLayers()} + ondragover={updateInsertLine} + ondragend={drop} + ondrop={drop} > {#each layers as listing, index} {@const selected = fakeHighlightOfNotYetSelectedLayerBeingDragged !== undefined ? fakeHighlightOfNotYetSelectedLayerBeingDragged === listing.entry.id : listing.entry.selected} @@ -519,8 +518,8 @@ data-index={index} tooltip={listing.entry.tooltip} {draggable} - on:dragstart={(e) => draggable && dragStart(e, listing)} - on:click={(e) => selectLayerWithModifiers(e, listing)} + ondragstart={(e) => draggable && dragStart(e, listing)} + onclick={(e) => selectLayerWithModifiers(e, listing)} > {#if listing.entry.childrenAllowed} handleExpandArrowClickWithModifiers(e, listing.entry.id)} + onclick={(e) => handleExpandArrowClickWithModifiers(e, listing.entry.id)} tabindex="0" > {:else} @@ -547,24 +546,26 @@ {#if listing.entry.name === "Artboard"} {/if} - onEditLayerName(listing)}> + onEditLayerName(listing)}> onEditLayerNameDeselect(listing)} - on:keydown={(e) => e.key === "Escape" && onEditLayerNameDeselect(listing)} - on:keydown={(e) => e.key === "Enter" && onEditLayerNameChange(listing, e)} - on:change={(e) => onEditLayerNameChange(listing, e)} + onblur={() => onEditLayerNameDeselect(listing)} + onkeydown={(e) => { + if (e.key === "Escape") onEditLayerNameDeselect(listing); + if (e.key === "Enter") onEditLayerNameChange(listing, e); + }} + onchange={(e) => onEditLayerNameChange(listing, e)} /> {#if !listing.entry.unlocked || !listing.entry.parentsUnlocked} (toggleLayerLock(listing.entry.id), e?.stopPropagation())} + onclick={(e) => (toggleLayerLock(listing.entry.id), e?.stopPropagation())} size={24} icon={listing.entry.unlocked ? "PadlockUnlocked" : "PadlockLocked"} hoverIcon={listing.entry.unlocked ? "PadlockLocked" : "PadlockUnlocked"} @@ -574,7 +575,7 @@ (toggleNodeVisibilityLayerPanel(listing.entry.id), e?.stopPropagation())} + onclick={(e) => (toggleNodeVisibilityLayerPanel(listing.entry.id), e?.stopPropagation())} size={24} icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"} hoverIcon={listing.entry.visible ? "EyeHide" : "EyeShow"} @@ -584,7 +585,7 @@ {/each} {#if draggingData && !draggingData.highlightFolder && dragInPanel} - + {/if} diff --git a/frontend/src/components/panels/Properties.svelte b/frontend/src/components/panels/Properties.svelte index 72607849a5..738b330a57 100644 --- a/frontend/src/components/panels/Properties.svelte +++ b/frontend/src/components/panels/Properties.svelte @@ -2,19 +2,19 @@ import { getContext, onMount } from "svelte"; import type { Editor } from "@graphite/editor"; - import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelSectionsLayout } from "@graphite/messages"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; + import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelSectionsLayout } from "@graphite/messages.svelte"; + import type { WidgetLayout as WidgetLayoutState } from "@graphite/messages.svelte"; const editor = getContext("editor"); - let propertiesSectionsLayout = defaultWidgetLayout(); + let propertiesSectionsLayout = $state(defaultWidgetLayout()); onMount(() => { editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelSectionsLayout, (updatePropertyPanelSectionsLayout) => { patchWidgetLayout(propertiesSectionsLayout, updatePropertyPanelSectionsLayout); - propertiesSectionsLayout = propertiesSectionsLayout; }); }); diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index eaedc3a6df..ff4a15dcb7 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -4,8 +4,7 @@ import { fade } from "svelte/transition"; import type { Editor } from "@graphite/editor"; - import type { Node } from "@graphite/messages"; - import type { FrontendNode, FrontendGraphInput, FrontendGraphOutput } from "@graphite/messages"; + import type { NodeGraphState } from "@graphite/state-providers/node-graph"; import type { IconName } from "@graphite/utility-functions/icons"; @@ -18,6 +17,8 @@ import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import Separator from "@graphite/components/widgets/labels/Separator.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; + import { type FrontendNode, type FrontendGraphInput, FrontendGraphOutput } from "@graphite/messages.svelte"; + import type { Node } from "@graphite/messages.svelte"; const GRID_COLLAPSE_SPACING = 10; const GRID_SIZE = 24; @@ -26,21 +27,16 @@ const editor = getContext("editor"); const nodeGraph = getContext("nodeGraph"); - let graph: HTMLDivElement | undefined; - - // Key value is node id + input/output index - // Imports/Export are stored at a key value of 0 - - $: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale); - $: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2; + let graph: HTMLDivElement | undefined = $state(); - let inputElement: HTMLInputElement; - let hoveringImportIndex: number | undefined = undefined; - let hoveringExportIndex: number | undefined = undefined; + let inputElement = $state(); + let hoveringImportIndex = $state(); + let hoveringExportIndex = $state(); - let editingNameImportIndex: number | undefined = undefined; - let editingNameExportIndex: number | undefined = undefined; - let editingNameText = ""; + let editingNameImportIndex = $state(); + let editingNameExportIndex = $state(); + let editingNameText = $state(""); + let nodeValues: FrontendNode[] = $state([]); function exportsToEdgeTextInputWidth() { let exportTextDivs = document.querySelectorAll(`[data-export-text-edge]`); @@ -204,7 +200,7 @@ } function primaryOutputConnectedToLayer(node: FrontendNode): boolean { - let firstConnectedNode = Array.from($nodeGraph.nodes.values()).find((n) => + let firstConnectedNode = nodeValues.find((n) => node.primaryOutput?.connectedTo.some((connector) => { if ((connector as Node).nodeId === undefined) return false; if (connector.index !== 0n) return false; @@ -215,7 +211,7 @@ } function primaryInputConnectedToLayer(node: FrontendNode): boolean { - const connectedNode = Array.from($nodeGraph.nodes.values()).find((n) => { + const connectedNode = nodeValues.find((n) => { if ((node.primaryInput?.connectedTo as Node) === undefined) return false; return n.id === (node.primaryInput?.connectedTo as Node).nodeId; }); @@ -230,6 +226,16 @@ } return result; } + + $effect.pre(() => { + nodeValues = Array.from($nodeGraph.nodes.values()); + }); + + // Key value is node id + input/output index + // Imports/Export are stored at a key value of 0 + + let gridSpacing = $derived(calculateGridSpacing($nodeGraph.transform.scale)); + let dotRadius = $derived(1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2); {#if typeof $nodeGraph.contextMenuInformation.contextMenuData === "string" && $nodeGraph.contextMenuInformation.contextMenuData === "CreateNode"} - createNode(e.detail)} /> + createNode(e)} /> {:else if $nodeGraph.contextMenuInformation.contextMenuData && "compatibleType" in $nodeGraph.contextMenuInformation.contextMenuData} - createNode(e.detail)} /> + createNode(e)} /> {:else} {@const contextMenuData = $nodeGraph.contextMenuInformation.contextMenuData} @@ -282,7 +288,7 @@ - editor.handle.mergeSelectedNodes()} /> + editor.handle.mergeSelectedNodes()} /> {/if} @@ -335,29 +341,11 @@ {#each $nodeGraph.imports as { outputMetadata, position }, index} - - {`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`} - {#if outputMetadata.connectedTo !== undefined} - - {:else} - - {/if} - - + {@render port("port", "output", outputMetadata, position.x / 24, position.y / 24)} (hoveringImportIndex = index)} - on:pointerleave={() => (hoveringImportIndex = undefined)} + onpointerenter={() => (hoveringImportIndex = index)} + onpointerleave={() => (hoveringImportIndex = undefined)} style:--offset-left={position.x / 24} style:--offset-top={position.y / 24} > @@ -368,11 +356,11 @@ style:width={importsToEdgeTextInputWidth()} bind:this={inputElement} bind:value={editingNameText} - on:blur={setEditingImportName} - on:keydown={(e) => e.key === "Enter" && setEditingImportName(e)} + onblur={setEditingImportName} + onkeydown={(e) => e.key === "Enter" && setEditingImportName(e)} /> {:else} - setEditingImportNameIndex(index, outputMetadata.name)}>{outputMetadata.name} + setEditingImportNameIndex(index, outputMetadata.name)}>{outputMetadata.name} {/if} {#if hoveringImportIndex === index || editingNameImportIndex === index} { + onclick={() => { /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ }} /> @@ -389,47 +377,31 @@ {/if} {/each} + {#if $nodeGraph.reorderImportIndex !== undefined} {@const position = { x: Number($nodeGraph.imports[0].position.x), y: Number($nodeGraph.imports[0].position.y) + Number($nodeGraph.reorderImportIndex) * 24, }} - + {/if} {#if $nodeGraph.addImport !== undefined} { + onclick={() => { /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ }} /> {/if} {#each $nodeGraph.exports as { inputMetadata, position }, index} - - {`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`} - {#if inputMetadata.connectedTo !== undefined} - - {:else} - - {/if} - + {@render port("port", "input", inputMetadata, position.x / 24, position.y / 24)} (hoveringExportIndex = index)} - on:pointerleave={() => (hoveringExportIndex = undefined)} + onpointerenter={() => (hoveringExportIndex = index)} + onpointerleave={() => (hoveringExportIndex = undefined)} style:--offset-left={position.x / 24} style:--offset-top={position.y / 24} > @@ -441,7 +413,7 @@ class="remove-button-export" data-index={index} data-export-text-edge - action={() => { + onclick={() => { /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ }} /> @@ -452,11 +424,11 @@ style:width={exportsToEdgeTextInputWidth()} bind:this={inputElement} bind:value={editingNameText} - on:blur={setEditingExportName} - on:keydown={(e) => e.key === "Enter" && setEditingExportName(e)} + onblur={setEditingExportName} + onkeydown={(e) => e.key === "Enter" && setEditingExportName(e)} /> {:else} - setEditingExportNameIndex(index, inputMetadata.name)}>{inputMetadata.name} + setEditingExportNameIndex(index, inputMetadata.name)}>{inputMetadata.name} {/if} {/each} @@ -465,14 +437,14 @@ x: Number($nodeGraph.exports[0].position.x), y: Number($nodeGraph.exports[0].position.y) + Number($nodeGraph.reorderExportIndex) * 24, }} - + {/if} {#if $nodeGraph.addExport !== undefined} { + onclick={() => { /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ }} /> @@ -564,22 +536,7 @@ {#if node.exposedInputs.length > 0} - - {`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`} - {#if stackDataInput.connectedTo !== undefined} - - {:else} - - {/if} - + {@render port("port", "input", stackDataInput)} {/if} @@ -592,7 +549,7 @@ data-visibility-button size={24} icon={node.visible ? "EyeVisible" : "EyeHidden"} - action={() => { + onclick={() => { /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ }} tooltip={node.visible ? "Visible" : "Hidden"} @@ -682,81 +639,19 @@ {#if node.primaryInput?.dataType} - - {`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`} - {#if node.primaryInput.connectedTo !== undefined} - - {:else} - - {/if} - + {@render port("port primary-port", "input", node.primaryInput)} {/if} {#each node.exposedInputs as secondary, index} {#if index < node.exposedInputs.length} - - {`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`} - {#if secondary.connectedTo !== undefined} - - {:else} - - {/if} - + {@render port("port", "input", secondary)} {/if} {/each} - {#if node.primaryOutput} - - {`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`} - {#if node.primaryOutput.connectedTo !== undefined} - - {:else} - - {/if} - - {/if} + {@render port("port primary-port", "output", node.primaryOutput)} {#each node.exposedOutputs as secondary} - - {`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`} - {#if secondary.connectedTo !== undefined} - - {:else} - - {/if} - + {@render port("port", "output", secondary)} {/each} @@ -786,6 +681,34 @@ > {/if} +{#snippet port(className: string, dataPort: string, node?: FrontendGraphInput | FrontendGraphOutput, offsetLeft?: number, offsetTop?: number)} + {#if node} + {@const color = node.dataType.toLowerCase()} + + {#if node instanceof FrontendGraphOutput} + {`${dataTypeTooltip(node)}\n\n${outputConnectedToText(node)}`} + {:else} + {`${dataTypeTooltip(node)}\n\n${validTypesText(node)}\n\n${inputConnectedToText(node)}`} + {/if} + {#if node.connectedTo !== undefined} + + {:else} + + {/if} + + {/if} +{/snippet} + diff --git a/frontend/src/components/widgets/inputs/TextInput.svelte b/frontend/src/components/widgets/inputs/TextInput.svelte index b9a794d140..de518bc01c 100644 --- a/frontend/src/components/widgets/inputs/TextInput.svelte +++ b/frontend/src/components/widgets/inputs/TextInput.svelte @@ -1,54 +1,48 @@ diff --git a/frontend/src/components/widgets/labels/Separator.svelte b/frontend/src/components/widgets/labels/Separator.svelte index 0c210b4d1d..58f943ee21 100644 --- a/frontend/src/components/widgets/labels/Separator.svelte +++ b/frontend/src/components/widgets/labels/Separator.svelte @@ -1,13 +1,17 @@ {#if type === "Section"} - + {/if} diff --git a/frontend/src/components/widgets/labels/TextLabel.svelte b/frontend/src/components/widgets/labels/TextLabel.svelte index 540aa16489..6b452cf738 100644 --- a/frontend/src/components/widgets/labels/TextLabel.svelte +++ b/frontend/src/components/widgets/labels/TextLabel.svelte @@ -1,26 +1,53 @@ - + {@render children?.()}
setEditingImportNameIndex(index, outputMetadata.name)}>{outputMetadata.name}
setEditingExportNameIndex(index, inputMetadata.name)}>{inputMetadata.name}