mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
phase 3: runes mode for all
This commit is contained in:
parent
c07f4b32af
commit
10d6b574c3
20 changed files with 243 additions and 181 deletions
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Editor from "@graphite/components/Editor.svelte";
|
||||
|
||||
let editor: GraphiteEditor | undefined = undefined;
|
||||
let editor: GraphiteEditor | undefined = $state(undefined);
|
||||
|
||||
onMount(async () => {
|
||||
await initWasm();
|
||||
|
|
|
@ -19,8 +19,13 @@
|
|||
|
||||
import MainWindow from "@graphite/components/window/MainWindow.svelte";
|
||||
|
||||
// Graphite WASM editor
|
||||
export let editor: Editor;
|
||||
|
||||
interface Props {
|
||||
// Graphite WASM editor
|
||||
editor: Editor;
|
||||
}
|
||||
|
||||
let { editor }: Props = $props();
|
||||
setContext("editor", editor);
|
||||
|
||||
// State provider systems
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script lang="ts" context="module">
|
||||
<script lang="ts" module>
|
||||
// Should be equal to the width and height of the zoom preview canvas in the CSS
|
||||
const ZOOM_WINDOW_DIMENSIONS_EXPANDED = 110;
|
||||
// Should be equal to the width and height of the `.pixel-outline` div in the CSS, and should be evenly divisible into the number above
|
||||
|
@ -8,24 +8,32 @@
|
|||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
|
||||
const temporaryCanvas = document.createElement("canvas");
|
||||
temporaryCanvas.width = ZOOM_WINDOW_DIMENSIONS;
|
||||
temporaryCanvas.height = ZOOM_WINDOW_DIMENSIONS;
|
||||
|
||||
let zoomPreviewCanvas: HTMLCanvasElement | undefined;
|
||||
let zoomPreviewCanvas: HTMLCanvasElement | undefined = $state();
|
||||
|
||||
export let imageData: ImageData | undefined = undefined;
|
||||
export let colorChoice: string;
|
||||
export let primaryColor: string;
|
||||
export let secondaryColor: string;
|
||||
export let x: number;
|
||||
export let y: number;
|
||||
interface Props {
|
||||
imageData?: ImageData | undefined;
|
||||
colorChoice: string;
|
||||
primaryColor: string;
|
||||
secondaryColor: string;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
let {
|
||||
imageData = undefined,
|
||||
colorChoice,
|
||||
primaryColor,
|
||||
secondaryColor,
|
||||
x,
|
||||
y
|
||||
}: Props = $props();
|
||||
|
||||
$: displayImageDataPreview(imageData);
|
||||
|
||||
function displayImageDataPreview(imageData: ImageData | undefined) {
|
||||
if (!zoomPreviewCanvas) return;
|
||||
|
@ -43,7 +51,7 @@
|
|||
context.drawImage(temporaryCanvas, 0, 0);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
$effect(() => {
|
||||
displayImageDataPreview(imageData);
|
||||
});
|
||||
</script>
|
||||
|
@ -56,8 +64,8 @@
|
|||
>
|
||||
<div class="ring">
|
||||
<div class="canvas-container">
|
||||
<canvas width={ZOOM_WINDOW_DIMENSIONS} height={ZOOM_WINDOW_DIMENSIONS} bind:this={zoomPreviewCanvas} />
|
||||
<div class="pixel-outline" />
|
||||
<canvas width={ZOOM_WINDOW_DIMENSIONS} height={ZOOM_WINDOW_DIMENSIONS} bind:this={zoomPreviewCanvas}></canvas>
|
||||
<div class="pixel-outline"></div>
|
||||
</div>
|
||||
</div>
|
||||
</FloatingMenu>
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext, onMount } from "svelte";
|
||||
import { createBubbler, stopPropagation, passive } from 'svelte/legacy';
|
||||
|
||||
const bubble = createBubbler();
|
||||
import { getContext, onMount } from "svelte";
|
||||
|
||||
import type { FrontendNodeType } from "@graphite/messages";
|
||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||
|
@ -8,16 +11,19 @@
|
|||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{ selectNodeType: string }>();
|
||||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||
|
||||
export let disabled = false;
|
||||
export let initialSearchTerm = "";
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
initialSearchTerm?: string;
|
||||
onselectNodeType?: (selectNodeType: string) => void;
|
||||
}
|
||||
|
||||
let nodeSearchInput: TextInput | undefined = undefined;
|
||||
let searchTerm = initialSearchTerm;
|
||||
let { disabled = false, initialSearchTerm = "", onselectNodeType }: Props = $props();
|
||||
|
||||
let nodeSearchInput: TextInput | undefined = $state(undefined);
|
||||
let searchTerm = $state(initialSearchTerm);
|
||||
|
||||
$: nodeCategories = buildNodeCategories($nodeGraph.nodeTypes, searchTerm);
|
||||
|
||||
type NodeCategoryDetails = {
|
||||
nodes: FrontendNodeType[];
|
||||
|
@ -107,18 +113,19 @@
|
|||
onMount(() => {
|
||||
setTimeout(() => nodeSearchInput?.focus(), 0);
|
||||
});
|
||||
let nodeCategories = $derived(buildNodeCategories($nodeGraph.nodeTypes, searchTerm));
|
||||
</script>
|
||||
|
||||
<div class="node-catalog">
|
||||
<TextInput placeholder="Search Nodes..." bind:value={searchTerm} bind:this={nodeSearchInput} />
|
||||
<div class="list-results" on:wheel|passive|stopPropagation>
|
||||
<div class="list-results" use:passive={['wheel', () => stopPropagation(bubble('wheel'))]}>
|
||||
{#each nodeCategories as nodeCategory}
|
||||
<details open={nodeCategory[1].open}>
|
||||
<summary>
|
||||
<TextLabel>{nodeCategory[0]}</TextLabel>
|
||||
</summary>
|
||||
{#each nodeCategory[1].nodes as nodeType}
|
||||
<TextButton {disabled} label={nodeType.name} tooltip={$nodeGraph.nodeDescriptions.get(nodeType.name)} action={() => dispatch("selectNodeType", nodeType.name)} />
|
||||
<TextButton {disabled} label={nodeType.name} tooltip={$nodeGraph.nodeDescriptions.get(nodeType.name)} action={() => onselectNodeType?.(nodeType.name)} />
|
||||
{/each}
|
||||
</details>
|
||||
{:else}
|
||||
|
|
|
@ -29,70 +29,70 @@
|
|||
import ScrollbarInput from "@graphite/components/widgets/inputs/ScrollbarInput.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
|
||||
let rulerHorizontal: RulerInput | undefined;
|
||||
let rulerVertical: RulerInput | undefined;
|
||||
let viewport: HTMLDivElement | undefined;
|
||||
let rulerHorizontal: RulerInput | undefined = $state();
|
||||
let rulerVertical: RulerInput | undefined = $state();
|
||||
let viewport: HTMLDivElement | undefined = $state();
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const document = getContext<DocumentState>("document");
|
||||
|
||||
// Interactive text editing
|
||||
let textInput: undefined | HTMLDivElement = undefined;
|
||||
let showTextInput: boolean;
|
||||
let textInputMatrix: number[];
|
||||
let textInput: undefined | HTMLDivElement = $state(undefined);
|
||||
let showTextInput: boolean = $state(false);
|
||||
let textInputMatrix: number[] = $state([]);
|
||||
|
||||
// Scrollbars
|
||||
let scrollbarPos: XY = { x: 0.5, y: 0.5 };
|
||||
let scrollbarSize: XY = { x: 0.5, y: 0.5 };
|
||||
let scrollbarPos: XY = $state({ x: 0.5, y: 0.5 });
|
||||
let scrollbarSize: XY = $state({ x: 0.5, y: 0.5 });
|
||||
let scrollbarMultiplier: XY = { x: 0, y: 0 };
|
||||
|
||||
// Rulers
|
||||
let rulerOrigin: XY = { x: 0, y: 0 };
|
||||
let rulerSpacing = 100;
|
||||
let rulerInterval = 100;
|
||||
let rulersVisible = true;
|
||||
let rulerOrigin: XY = $state({ x: 0, y: 0 });
|
||||
let rulerSpacing = $state(100);
|
||||
let rulerInterval = $state(100);
|
||||
let rulersVisible = $state(true);
|
||||
|
||||
// Rendered SVG viewport data
|
||||
let artworkSvg = "";
|
||||
let artworkSvg = $state("");
|
||||
|
||||
// Rasterized SVG viewport data, or none if it's not up-to-date
|
||||
let rasterizedCanvas: HTMLCanvasElement | undefined = undefined;
|
||||
let rasterizedContext: CanvasRenderingContext2D | undefined = undefined;
|
||||
|
||||
// Cursor icon to display while hovering over the canvas
|
||||
let canvasCursor = "default";
|
||||
let canvasCursor = $state("default");
|
||||
|
||||
// Cursor position for cursor floating menus like the Eyedropper tool zoom
|
||||
let cursorLeft = 0;
|
||||
let cursorTop = 0;
|
||||
let cursorEyedropper = false;
|
||||
let cursorEyedropperPreviewImageData: ImageData | undefined = undefined;
|
||||
let cursorEyedropperPreviewColorChoice = "";
|
||||
let cursorEyedropperPreviewColorPrimary = "";
|
||||
let cursorEyedropperPreviewColorSecondary = "";
|
||||
let cursorLeft = $state(0);
|
||||
let cursorTop = $state(0);
|
||||
let cursorEyedropper = $state(false);
|
||||
let cursorEyedropperPreviewImageData: ImageData | undefined = $state(undefined);
|
||||
let cursorEyedropperPreviewColorChoice = $state("");
|
||||
let cursorEyedropperPreviewColorPrimary = $state("");
|
||||
let cursorEyedropperPreviewColorSecondary = $state("");
|
||||
|
||||
// Canvas dimensions
|
||||
let canvasSvgWidth: number | undefined = undefined;
|
||||
let canvasSvgHeight: number | undefined = undefined;
|
||||
let canvasSvgWidth: number | undefined = $state(undefined);
|
||||
let canvasSvgHeight: number | undefined = $state(undefined);
|
||||
|
||||
let devicePixelRatio: number | undefined;
|
||||
let devicePixelRatio: number | undefined = $state();
|
||||
|
||||
// Dimension is rounded up to the nearest even number because resizing is centered, and dividing an odd number by 2 for centering causes antialiasing
|
||||
$: canvasWidthRoundedToEven = canvasSvgWidth && (canvasSvgWidth % 2 === 1 ? canvasSvgWidth + 1 : canvasSvgWidth);
|
||||
$: canvasHeightRoundedToEven = canvasSvgHeight && (canvasSvgHeight % 2 === 1 ? canvasSvgHeight + 1 : canvasSvgHeight);
|
||||
let canvasWidthRoundedToEven = $derived(canvasSvgWidth && (canvasSvgWidth % 2 === 1 ? canvasSvgWidth + 1 : canvasSvgWidth));
|
||||
let canvasHeightRoundedToEven = $derived(canvasSvgHeight && (canvasSvgHeight % 2 === 1 ? canvasSvgHeight + 1 : canvasSvgHeight));
|
||||
// Used to set the canvas element size on the page.
|
||||
// The value above in pixels, or if undefined, we fall back to 100% as a non-pixel-perfect backup that's hopefully short-lived
|
||||
$: canvasWidthCSS = canvasWidthRoundedToEven ? `${canvasWidthRoundedToEven}px` : "100%";
|
||||
$: canvasHeightCSS = canvasHeightRoundedToEven ? `${canvasHeightRoundedToEven}px` : "100%";
|
||||
let canvasWidthCSS = $derived(canvasWidthRoundedToEven ? `${canvasWidthRoundedToEven}px` : "100%");
|
||||
let canvasHeightCSS = $derived(canvasHeightRoundedToEven ? `${canvasHeightRoundedToEven}px` : "100%");
|
||||
|
||||
$: canvasWidthScaled = canvasSvgWidth && devicePixelRatio && Math.floor(canvasSvgWidth * devicePixelRatio);
|
||||
$: canvasHeightScaled = canvasSvgHeight && devicePixelRatio && Math.floor(canvasSvgHeight * devicePixelRatio);
|
||||
let canvasWidthScaled = $derived(canvasSvgWidth && devicePixelRatio && Math.floor(canvasSvgWidth * devicePixelRatio));
|
||||
let canvasHeightScaled = $derived(canvasSvgHeight && devicePixelRatio && Math.floor(canvasSvgHeight * devicePixelRatio));
|
||||
|
||||
// Used to set the canvas rendering dimensions.
|
||||
$: canvasWidthScaledRoundedToEven = canvasWidthScaled && (canvasWidthScaled % 2 === 1 ? canvasWidthScaled + 1 : canvasWidthScaled);
|
||||
$: canvasHeightScaledRoundedToEven = canvasHeightScaled && (canvasHeightScaled % 2 === 1 ? canvasHeightScaled + 1 : canvasHeightScaled);
|
||||
let canvasWidthScaledRoundedToEven = $derived(canvasWidthScaled && (canvasWidthScaled % 2 === 1 ? canvasWidthScaled + 1 : canvasWidthScaled));
|
||||
let canvasHeightScaledRoundedToEven = $derived(canvasHeightScaled && (canvasHeightScaled % 2 === 1 ? canvasHeightScaled + 1 : canvasHeightScaled));
|
||||
|
||||
$: toolShelfTotalToolsAndSeparators = ((layoutGroup) => {
|
||||
let toolShelfTotalToolsAndSeparators = $derived(((layoutGroup) => {
|
||||
if (!isWidgetSpanRow(layoutGroup)) return undefined;
|
||||
|
||||
let totalSeparators = 0;
|
||||
|
@ -124,7 +124,7 @@
|
|||
totalToolRowsFor2Columns,
|
||||
totalToolRowsFor3Columns,
|
||||
};
|
||||
})($document.toolShelfLayout.layout[0]);
|
||||
})($document.toolShelfLayout.layout[0]));
|
||||
|
||||
function dropFile(e: DragEvent) {
|
||||
const { dataTransfer } = e;
|
||||
|
@ -523,7 +523,7 @@
|
|||
</svg>
|
||||
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}>
|
||||
{#if showTextInput}
|
||||
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" onscroll={preventTextEditingScroll} />
|
||||
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" onscroll={preventTextEditingScroll}></div>
|
||||
{/if}
|
||||
</div>
|
||||
<canvas
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { handlers } from 'svelte/legacy';
|
||||
|
||||
import { getContext, onMount, onDestroy, tick } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
|
@ -43,26 +45,26 @@
|
|||
const editor = getContext<Editor>("editor");
|
||||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||
|
||||
let list: LayoutCol | undefined;
|
||||
let list: LayoutCol | undefined = $state();
|
||||
|
||||
// Layer data
|
||||
let layerCache = new Map<string, LayerPanelEntry>(); // 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) => {
|
||||
|
@ -530,7 +532,7 @@
|
|||
title={listing.entry.expanded
|
||||
? "Collapse (Click) / Collapse All (Alt Click)"
|
||||
: `Expand (Click) / Expand All (Alt Click)${listing.entry.ancestorOfSelected ? "\n(A selected layer is contained within)" : ""}`}
|
||||
on:click={(e) => handleExpandArrowClickWithModifiers(e, listing.entry.id)}
|
||||
onclick={(e) => handleExpandArrowClickWithModifiers(e, listing.entry.id)}
|
||||
tabindex="0"
|
||||
></button>
|
||||
{:else}
|
||||
|
@ -554,10 +556,12 @@
|
|||
value={listing.entry.alias}
|
||||
placeholder={listing.entry.name}
|
||||
disabled={!listing.editingName}
|
||||
on:blur={() => 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) => {
|
||||
e.key === "Escape" && onEditLayerNameDeselect(listing);
|
||||
e.key === "Enter" && onEditLayerNameChange(listing, e);
|
||||
}}
|
||||
onchange={(e) => onEditLayerNameChange(listing, e)}
|
||||
/>
|
||||
</LayoutRow>
|
||||
{#if !listing.entry.unlocked || !listing.entry.parentsUnlocked}
|
||||
|
@ -584,7 +588,7 @@
|
|||
{/each}
|
||||
</LayoutCol>
|
||||
{#if draggingData && !draggingData.highlightFolder && dragInPanel}
|
||||
<div class="insert-mark" style:left={`${4 + draggingData.insertDepth * 16}px`} style:top={`${draggingData.markerHeight}px`} />
|
||||
<div class="insert-mark" style:left={`${4 + draggingData.insertDepth * 16}px`} style:top={`${draggingData.markerHeight}px`}></div>
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
<LayoutRow class="bottom-bar" scrollableX={true}>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
let propertiesSectionsLayout = defaultWidgetLayout();
|
||||
let propertiesSectionsLayout = $state(defaultWidgetLayout());
|
||||
|
||||
onMount(() => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelSectionsLayout, (updatePropertyPanelSectionsLayout) => {
|
||||
|
|
|
@ -26,21 +26,16 @@
|
|||
const editor = getContext<Editor>("editor");
|
||||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||
|
||||
let graph: HTMLDivElement | undefined;
|
||||
let graph: HTMLDivElement | undefined = $state();
|
||||
|
||||
// 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 inputElement = $state<HTMLInputElement>();
|
||||
let hoveringImportIndex = $state<number>();
|
||||
let hoveringExportIndex = $state<number>()
|
||||
|
||||
let inputElement: HTMLInputElement;
|
||||
let hoveringImportIndex: number | undefined = undefined;
|
||||
let hoveringExportIndex: number | undefined = undefined;
|
||||
|
||||
let editingNameImportIndex: number | undefined = undefined;
|
||||
let editingNameExportIndex: number | undefined = undefined;
|
||||
let editingNameText = "";
|
||||
let editingNameImportIndex = $state<number>();
|
||||
let editingNameExportIndex = $state<number>();
|
||||
let editingNameText = $state<string>("");
|
||||
|
||||
function exportsToEdgeTextInputWidth() {
|
||||
let exportTextDivs = document.querySelectorAll(`[data-export-text-edge]`);
|
||||
|
@ -230,6 +225,11 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
// 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);
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -252,9 +252,9 @@
|
|||
}}
|
||||
>
|
||||
{#if typeof $nodeGraph.contextMenuInformation.contextMenuData === "string" && $nodeGraph.contextMenuInformation.contextMenuData === "CreateNode"}
|
||||
<NodeCatalog on:selectNodeType={(e) => createNode(e.detail)} />
|
||||
<NodeCatalog onselectNodeType={(e) => createNode(e)} />
|
||||
{:else if $nodeGraph.contextMenuInformation.contextMenuData && "compatibleType" in $nodeGraph.contextMenuInformation.contextMenuData}
|
||||
<NodeCatalog initialSearchTerm={$nodeGraph.contextMenuInformation.contextMenuData.compatibleType || ""} on:selectNodeType={(e) => createNode(e.detail)} />
|
||||
<NodeCatalog initialSearchTerm={$nodeGraph.contextMenuInformation.contextMenuData.compatibleType || ""} onselectNodeType={(e) => createNode(e)} />
|
||||
{:else}
|
||||
{@const contextMenuData = $nodeGraph.contextMenuInformation.contextMenuData}
|
||||
<LayoutRow class="toggle-layer-or-node">
|
||||
|
@ -356,8 +356,8 @@
|
|||
|
||||
<div
|
||||
class="edit-import-export import"
|
||||
on:pointerenter={() => (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 +368,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}
|
||||
<p class="import-text" on:dblclick={() => setEditingImportNameIndex(index, outputMetadata.name)}>{outputMetadata.name}</p>
|
||||
<p class="import-text" ondblclick={() => setEditingImportNameIndex(index, outputMetadata.name)}>{outputMetadata.name}</p>
|
||||
{/if}
|
||||
{#if hoveringImportIndex === index || editingNameImportIndex === index}
|
||||
<IconButton
|
||||
|
@ -394,7 +394,7 @@
|
|||
x: Number($nodeGraph.imports[0].position.x),
|
||||
y: Number($nodeGraph.imports[0].position.y) + Number($nodeGraph.reorderImportIndex) * 24,
|
||||
}}
|
||||
<div class="reorder-bar" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 4) / 24} />
|
||||
<div class="reorder-bar" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 4) / 24}></div>
|
||||
{/if}
|
||||
{#if $nodeGraph.addImport !== undefined}
|
||||
<div class="plus" style:--offset-left={$nodeGraph.addImport.x / 24} style:--offset-top={$nodeGraph.addImport.y / 24}>
|
||||
|
@ -428,8 +428,8 @@
|
|||
</svg>
|
||||
<div
|
||||
class="edit-import-export export"
|
||||
on:pointerenter={() => (hoveringExportIndex = index)}
|
||||
on:pointerleave={() => (hoveringExportIndex = undefined)}
|
||||
onpointerenter={() => (hoveringExportIndex = index)}
|
||||
onpointerleave={() => (hoveringExportIndex = undefined)}
|
||||
style:--offset-left={position.x / 24}
|
||||
style:--offset-top={position.y / 24}
|
||||
>
|
||||
|
@ -452,11 +452,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}
|
||||
<p class="export-text" on:dblclick={() => setEditingExportNameIndex(index, inputMetadata.name)}>{inputMetadata.name}</p>
|
||||
<p class="export-text" ondblclick={() => setEditingExportNameIndex(index, inputMetadata.name)}>{inputMetadata.name}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
@ -465,7 +465,7 @@
|
|||
x: Number($nodeGraph.exports[0].position.x),
|
||||
y: Number($nodeGraph.exports[0].position.y) + Number($nodeGraph.reorderExportIndex) * 24,
|
||||
}}
|
||||
<div class="reorder-bar" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 4) / 24} />
|
||||
<div class="reorder-bar" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 4) / 24}></div>
|
||||
{/if}
|
||||
{#if $nodeGraph.addExport !== undefined}
|
||||
<div class="plus" style:--offset-left={$nodeGraph.addExport.x / 24} style:--offset-top={$nodeGraph.addExport.y / 24}>
|
||||
|
|
|
@ -6,10 +6,14 @@
|
|||
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
|
||||
import WidgetTable from "@graphite/components/widgets/WidgetTable.svelte";
|
||||
|
||||
export let layout: WidgetLayout;
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
|
||||
interface Props {
|
||||
layout: WidgetLayout;
|
||||
class?: string;
|
||||
classes?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
let { layout, class: className = "", classes = {} }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#each layout.layout as layoutGroup}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<script lang="ts">
|
||||
import WidgetSection from './WidgetSection.svelte';
|
||||
import { stopPropagation } from 'svelte/legacy';
|
||||
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
|
@ -9,23 +12,33 @@
|
|||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
|
||||
|
||||
export let widgetData: WidgetSectionFromJsMessages;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export let layoutTarget: any; // TODO: Give type
|
||||
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
|
||||
interface Props {
|
||||
widgetData: WidgetSectionFromJsMessages;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
layoutTarget: any; // TODO: Give type
|
||||
class?: string;
|
||||
classes?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
let expanded = true;
|
||||
let {
|
||||
widgetData,
|
||||
layoutTarget,
|
||||
class: className = "",
|
||||
classes = {}
|
||||
}: Props = $props();
|
||||
|
||||
let expanded = $state(true);
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
</script>
|
||||
|
||||
<!-- TODO: Implement collapsable sections with properties system -->
|
||||
<LayoutCol class={`widget-section ${className}`.trim()} {classes}>
|
||||
<button class="header" class:expanded on:click|stopPropagation={() => (expanded = !expanded)} tabindex="0">
|
||||
<div class="expand-arrow" />
|
||||
<button class="header" class:expanded onclick={stopPropagation(() => (expanded = !expanded))} tabindex="0">
|
||||
<div class="expand-arrow"></div>
|
||||
<TextLabel tooltip={widgetData.description} bold={true}>{widgetData.name}</TextLabel>
|
||||
<IconButton
|
||||
icon={widgetData.pinned ? "PinActive" : "PinInactive"}
|
||||
|
@ -67,7 +80,7 @@
|
|||
{:else if isWidgetSpanColumn(layoutGroup)}
|
||||
<TextLabel styles={{ color: "#d6536e" }}>Error: The WidgetSpan used here should be a row not a column</TextLabel>
|
||||
{:else if isWidgetSection(layoutGroup)}
|
||||
<svelte:self widgetData={layoutGroup} {layoutTarget} />
|
||||
<WidgetSection widgetData={layoutGroup} {layoutTarget} />
|
||||
{:else}
|
||||
<TextLabel styles={{ color: "#d6536e" }}>Error: The widget that belongs here has an invalid layout group type</TextLabel>
|
||||
{/if}
|
||||
|
|
|
@ -30,21 +30,21 @@
|
|||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
interface Props {
|
||||
widgetData: WidgetSpanRow | WidgetSpanColumn;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
layoutTarget: any;
|
||||
class?: string;
|
||||
classes?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export let widgetData: WidgetSpanRow | WidgetSpanColumn;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export let layoutTarget: any;
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
|
||||
$: extraClasses = Object.entries(classes)
|
||||
.flatMap(([className, stateName]) => (stateName ? [className] : []))
|
||||
.join(" ");
|
||||
|
||||
$: direction = watchDirection(widgetData);
|
||||
$: widgets = watchWidgets(widgetData);
|
||||
let {
|
||||
widgetData,
|
||||
layoutTarget,
|
||||
class: className = "",
|
||||
classes = {}
|
||||
}: Props = $props();
|
||||
|
||||
function watchDirection(widgetData: WidgetSpanRow | WidgetSpanColumn): "row" | "column" | undefined {
|
||||
if (isWidgetSpanRow(widgetData)) return "row";
|
||||
|
@ -77,6 +77,11 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return Object.fromEntries(Object.entries(props).filter((entry) => !exclusions.includes(entry[0]))) as any;
|
||||
}
|
||||
let extraClasses = $derived(Object.entries(classes)
|
||||
.flatMap(([className, stateName]) => (stateName ? [className] : []))
|
||||
.join(" "));
|
||||
let direction = $derived(watchDirection(widgetData));
|
||||
let widgets = $derived(watchWidgets(widgetData));
|
||||
</script>
|
||||
|
||||
<!-- TODO: Refactor this component to use `<svelte:component this={attributesObject} />` to avoid all the separate conditional components -->
|
||||
|
@ -130,7 +135,7 @@
|
|||
{/if}
|
||||
{@const nodeCatalog = narrowWidgetProps(component.props, "NodeCatalog")}
|
||||
{#if nodeCatalog}
|
||||
<NodeCatalog {...exclude(nodeCatalog)} on:selectNodeType={(e) => widgetValueCommitAndUpdate(index, e.detail)} />
|
||||
<NodeCatalog {...exclude(nodeCatalog)} onselectNodeType={(e) => widgetValueCommitAndUpdate(index, e)} />
|
||||
{/if}
|
||||
{@const numberInput = narrowWidgetProps(component.props, "NumberInput")}
|
||||
{#if numberInput}
|
||||
|
|
|
@ -3,21 +3,28 @@
|
|||
|
||||
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
|
||||
|
||||
export let widgetData: WidgetTableFromJsMessages;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export let layoutTarget: any; // TODO: Give this a real type
|
||||
|
||||
interface Props {
|
||||
widgetData: WidgetTableFromJsMessages;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
layoutTarget: any; // TODO: Give this a real type
|
||||
}
|
||||
|
||||
let { widgetData, layoutTarget }: Props = $props();
|
||||
</script>
|
||||
|
||||
<table>
|
||||
{#each widgetData.tableWidgets as row}
|
||||
<tr>
|
||||
{#each row as cell}
|
||||
<td>
|
||||
<WidgetSpan widgetData={{ rowWidgets: [cell] }} {layoutTarget} />
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
<tbody>
|
||||
{#each widgetData.tableWidgets as row}
|
||||
<tr>
|
||||
{#each row as cell}
|
||||
<td>
|
||||
<WidgetSpan widgetData={{ rowWidgets: [cell] }} {layoutTarget} />
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
let hintData: HintData = [];
|
||||
let hintData: HintData = $state([]);
|
||||
|
||||
function inputKeysForPlatform(hint: HintInfo): LayoutKeysGroup[] {
|
||||
if (platformIsMac() && hint.keyGroupsMac) return hint.keyGroupsMac;
|
||||
|
|
|
@ -13,8 +13,12 @@
|
|||
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
|
||||
import WindowTitle from "@graphite/components/window/title-bar/WindowTitle.svelte";
|
||||
|
||||
export let platform: Graphite.Platform;
|
||||
export let maximized: boolean;
|
||||
interface Props {
|
||||
platform: Graphite.Platform;
|
||||
maximized: boolean;
|
||||
}
|
||||
|
||||
let { platform, maximized }: Props = $props();
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const portfolio = getContext<PortfolioState>("portfolio");
|
||||
|
@ -29,11 +33,11 @@
|
|||
[ACCEL_KEY, "Shift", "KeyT"],
|
||||
];
|
||||
|
||||
let entries: MenuListEntry[] = [];
|
||||
let entries: MenuListEntry[] = $state([]);
|
||||
|
||||
$: docIndex = $portfolio.activeDocumentIndex;
|
||||
$: displayName = $portfolio.documents[docIndex]?.displayName || "";
|
||||
$: windowTitle = `${displayName}${displayName && " - "}Graphite`;
|
||||
let docIndex = $derived($portfolio.activeDocumentIndex);
|
||||
let displayName = $derived($portfolio.documents[docIndex]?.displayName || "");
|
||||
let windowTitle = $derived(`${displayName}${displayName && " - "}Graphite`);
|
||||
|
||||
onMount(() => {
|
||||
const arraysEqual = (a: KeyRaw[], b: KeyRaw[]): boolean => a.length === b.length && a.every((aValue, i) => aValue === b[i]);
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
<script lang="ts">
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
|
||||
export let maximized = false;
|
||||
interface Props {
|
||||
maximized?: boolean;
|
||||
}
|
||||
|
||||
let { maximized = false }: Props = $props();
|
||||
</script>
|
||||
|
||||
<LayoutRow class="window-buttons-mac">
|
||||
<div class="close" title="Close" />
|
||||
<div class="minimize" title={maximized ? "Minimize" : "Maximize"} />
|
||||
<div class="zoom" title="Zoom" />
|
||||
<div class="close" title="Close"></div>
|
||||
<div class="minimize" title={maximized ? "Minimize" : "Maximize"}></div>
|
||||
<div class="zoom" title="Zoom"></div>
|
||||
</LayoutRow>
|
||||
|
||||
<style lang="scss" global>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
const fullscreen = getContext<FullscreenState>("fullscreen");
|
||||
|
||||
$: requestFullscreenHotkeys = fullscreen.keyboardLockApiSupported && !$fullscreen.keyboardLocked;
|
||||
let requestFullscreenHotkeys = $derived(fullscreen.keyboardLockApiSupported && !$fullscreen.keyboardLocked);
|
||||
|
||||
async function handleClick() {
|
||||
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
|
||||
export let maximized = false;
|
||||
interface Props {
|
||||
maximized?: boolean;
|
||||
}
|
||||
|
||||
let { maximized = false }: Props = $props();
|
||||
</script>
|
||||
|
||||
<LayoutRow class="window-button windows minimize" tooltip="Minimize">
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
|
||||
export let text: string;
|
||||
interface Props {
|
||||
text: string;
|
||||
}
|
||||
|
||||
let { text }: Props = $props();
|
||||
</script>
|
||||
|
||||
<LayoutRow class="window-title">
|
||||
|
|
|
@ -22,21 +22,12 @@
|
|||
/* └─ */ layers: 55,
|
||||
};
|
||||
|
||||
let panelSizes = PANEL_SIZES;
|
||||
let documentPanel: Panel | undefined;
|
||||
let panelSizes = $state(PANEL_SIZES);
|
||||
let documentPanel: Panel | undefined = $state();
|
||||
let gutterResizeRestore: [number, number] | undefined = undefined;
|
||||
let pointerCaptureId: number | undefined = undefined;
|
||||
|
||||
$: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex);
|
||||
|
||||
$: documentTabLabels = $portfolio.documents.map((doc: FrontendDocumentDetails) => {
|
||||
const name = doc.displayName;
|
||||
|
||||
if (!editor.handle.inDevelopmentMode()) return { name };
|
||||
|
||||
const tooltip = `Document ID: ${doc.id}`;
|
||||
return { name, tooltip };
|
||||
});
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const portfolio = getContext<PortfolioState>("portfolio");
|
||||
|
@ -130,6 +121,17 @@
|
|||
|
||||
addListeners();
|
||||
}
|
||||
$effect(() => {
|
||||
documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex);
|
||||
});
|
||||
let documentTabLabels = $derived($portfolio.documents.map((doc: FrontendDocumentDetails) => {
|
||||
const name = doc.displayName;
|
||||
|
||||
if (!editor.handle.inDevelopmentMode()) return { name };
|
||||
|
||||
const tooltip = `Document ID: ${doc.id}`;
|
||||
return { name, tooltip };
|
||||
}));
|
||||
</script>
|
||||
|
||||
<LayoutRow class="workspace" data-workspace>
|
||||
|
|
|
@ -34,16 +34,7 @@ const ALLOWED_LICENSES = [
|
|||
"NCSA",
|
||||
];
|
||||
|
||||
const runesGlobs = [
|
||||
"**/components/layout/*.svelte",
|
||||
"**/components/widgets/labels/*.svelte",
|
||||
"**/components/widgets/buttons/*.svelte",
|
||||
"**/components/widgets/inputs/*.svelte",
|
||||
"**/components/floating-menus/MenuList.svelte",
|
||||
"**/components/floating-menus/ColorPicker.svelte",
|
||||
"**/components/floating-menus/Dialog.svelte",
|
||||
"**/components/window/workspace/Panel.svelte",
|
||||
];
|
||||
const runesGlobs = ["**/*.svelte"];
|
||||
|
||||
function forceRunes(filePath: string): boolean {
|
||||
const relativePath = filePath.slice(filePath.indexOf("src"));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue