Add graph type error diagnostics to the UI (#1535)

* Fontend input types

* Fix index of errors / types

* Bug fixes, styling improvements, and code review

* Improvements to the error box

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-12-29 08:38:45 +00:00 committed by GitHub
parent 96b5d7b520
commit 947a131a4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 566 additions and 170 deletions

View file

@ -101,22 +101,19 @@
--color-e-nearwhite-rgb: 238, 238, 238;
--color-f-white: #fff;
--color-f-white-rgb: 255, 255, 255;
--color-error-red: #d6536e;
--color-error-red-rgb: 214, 83, 110;
--color-data-general: #c5c5c5;
--color-data-general-dim: #767676;
--color-data-vector: #65bbe5;
--color-data-vector-dim: #4b778c;
--color-data-number: #cbbab4;
--color-data-number-dim: #87736b;
--color-data-raster: #e4bb72;
--color-data-raster-dim: #8b7752;
--color-data-mask: #8d85c7;
--color-data-number: #d6536e;
--color-data-number-dim: #803242;
--color-data-vec2: #cc00ff;
--color-data-vec2-dim: #71008d;
--color-data-color: #70a898;
--color-data-color-dim: #43645b;
--color-data-graphic: #e4bb72;
--color-data-graphic-dim: #8b7752;
--color-data-vector: #65bbe5;
--color-data-vector-dim: #4b778c;
--color-data-color: #dce472;
--color-data-color-dim: #898d55;
--color-data-artboard: #70a898;
--color-data-artboard-dim: #3a6156;

View file

@ -627,7 +627,6 @@
width: 24px;
font-size: 0;
overflow: hidden;
transition: background-color 0.5s ease;
div {
display: inline-block;
@ -636,6 +635,7 @@
// For the least jarring luminance conversion, these colors are derived by placing a black layer with the "desaturate" blend mode over the colors.
// We don't use the CSS `filter: grayscale(1);` property because it produces overly dark tones for bright colors with a noticeable jump on hover.
background: var(--pure-color-gray);
transition: background-color 0.2s ease;
}
&:hover div,

View file

@ -583,7 +583,7 @@
&[title^="Coming Soon"] {
opacity: 0.25;
transition: opacity 0.25s;
transition: opacity 0.2s;
&:hover {
opacity: 1;
@ -703,7 +703,7 @@
.graph-view {
pointer-events: none;
transition: opacity 0.1s ease-in-out;
transition: opacity 0.2s ease-in-out;
opacity: 0;
&.open {

View file

@ -1,11 +1,13 @@
<script lang="ts">
import { getContext, onMount, tick } from "svelte";
import { fade } from "svelte/transition";
import { FADE_TRANSITION } from "@graphite/consts";
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import type { IconName } from "@graphite/utility-functions/icons";
import type { Editor } from "@graphite/wasm-communication/editor";
import { UpdateNodeGraphSelection } from "@graphite/wasm-communication/messages";
import type { FrontendNodeLink, FrontendNodeType, FrontendNode, FrontendGraphDataType } from "@graphite/wasm-communication/messages";
import type { FrontendNodeLink, FrontendNodeType, FrontendNode, FrontendGraphInput, FrontendGraphOutput } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
@ -28,7 +30,7 @@
let nodesContainer: HTMLDivElement | undefined;
let nodeSearchInput: TextInput | undefined;
let transform = { scale: 1, x: 0, y: 0 };
let transform = { scale: 1, x: 1200, y: 0 };
let panning = false;
let selected: bigint[] = [];
let draggingNodes: { startX: number; startY: number; roundX: number; roundY: number } | undefined = undefined;
@ -292,6 +294,8 @@
function pointerDown(e: PointerEvent) {
const [lmb, rmb] = [e.button === 0, e.button === 2];
const nodeError = (e.target as SVGSVGElement).closest("[data-node-error]") as HTMLElement;
if (nodeError && lmb) return;
const port = (e.target as SVGSVGElement).closest("[data-port]") as SVGSVGElement;
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
const nodeId = node?.getAttribute("data-node") || undefined;
@ -606,9 +610,9 @@
return `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(" ")}`;
}
function dataTypeTooltip(dataType: FrontendGraphDataType): string {
const capitalized = dataType[0].toUpperCase() + dataType.slice(1);
return `${capitalized} Data`;
function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string {
const dataTypeCapitalized = `${value.dataType[0].toUpperCase()}${value.dataType.slice(1)}`;
return value.resolvedType ? `Resolved Data: ${value.resolvedType}` : `Unresolved Data: ${dataTypeCapitalized}`;
}
onMount(() => {
@ -674,7 +678,7 @@
<!-- Layers -->
{#each $nodeGraph.nodes.flatMap((node, nodeIndex) => (node.isLayer ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
{@const clipPathId = String(Math.random()).substring(2)}
{@const stackDatainput = node.exposedInputs[0]}
{@const stackDataInput = node.exposedInputs[0]}
<div
class="layer"
class:selected={selected.includes(node.id)}
@ -687,6 +691,10 @@
style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`}
data-node={node.id}
>
{#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
<span class="node-error hover" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
{/if}
<div class="node-chain" />
<!-- Layer input port (from left) -->
<div class="input ports">
@ -701,7 +709,7 @@
bind:this={inputs[nodeIndex][0]}
>
{#if node.primaryInput}
<title>{dataTypeTooltip(node.primaryInput.dataType)}</title>
<title>{dataTypeTooltip(node.primaryInput)}</title>
{/if}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
</svg>
@ -721,7 +729,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`}
bind:this={outputs[nodeIndex][0]}
>
<title>{dataTypeTooltip(node.primaryOutput.dataType)}</title>
<title>{dataTypeTooltip(node.primaryOutput)}</title>
<path d="M0,2.953,2.521,1.259a2.649,2.649,0,0,1,2.959,0L8,2.953V8H0Z" />
</svg>
{/if}
@ -730,12 +738,12 @@
viewBox="0 0 8 8"
class="port bottom"
data-port="input"
data-datatype={stackDatainput.dataType}
style:--data-color={`var(--color-data-${stackDatainput.dataType})`}
style:--data-color-dim={`var(--color-data-${stackDatainput.dataType}-dim)`}
data-datatype={stackDataInput.dataType}
style:--data-color={`var(--color-data-${stackDataInput.dataType})`}
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType}-dim)`}
bind:this={inputs[nodeIndex][1]}
>
<title>{dataTypeTooltip(stackDatainput.dataType)}</title>
<title>{dataTypeTooltip(stackDataInput)}</title>
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" />
</svg>
</div>
@ -769,6 +777,10 @@
style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`}
data-node={node.id}
>
{#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
<span class="node-error hover" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
{/if}
<!-- Primary row -->
<div class="primary" class:no-parameter-section={exposedInputsOutputs.length === 0}>
<IconLabel icon={nodeIcon(node.name)} />
@ -798,7 +810,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryInput?.dataType}-dim)`}
bind:this={inputs[nodeIndex][0]}
>
<title>{dataTypeTooltip(node.primaryInput.dataType)}</title>
<title>{dataTypeTooltip(node.primaryInput)}</title>
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
</svg>
{/if}
@ -814,7 +826,7 @@
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
bind:this={inputs[nodeIndex][index + 1]}
>
<title>{dataTypeTooltip(parameter.dataType)}</title>
<title>{dataTypeTooltip(parameter)}</title>
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
</svg>
{/if}
@ -833,7 +845,7 @@
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`}
bind:this={outputs[nodeIndex][0]}
>
<title>{dataTypeTooltip(node.primaryOutput.dataType)}</title>
<title>{dataTypeTooltip(node.primaryOutput)}</title>
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
</svg>
{/if}
@ -846,9 +858,9 @@
data-datatype={parameter.dataType}
style:--data-color={`var(--color-data-${parameter.dataType})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
bind:this={outputs[nodeIndex][outputIndex + 1]}
bind:this={outputs[nodeIndex][outputIndex + (node.primaryOutput ? 1 : 0)]}
>
<title>{dataTypeTooltip(parameter.dataType)}</title>
<title>{dataTypeTooltip(parameter)}</title>
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
</svg>
{/each}
@ -980,6 +992,68 @@
// backdrop-filter: blur(4px);
background: rgba(0, 0, 0, 0.33);
.node-error {
position: absolute;
width: max-content;
white-space: pre-wrap;
max-width: 600px;
line-height: 18px;
color: var(--color-2-mildblack);
background: var(--color-error-red);
padding: 8px;
border-radius: 4px;
bottom: calc(100% + 12px);
z-index: -1;
transition: opacity 0.2s ease-in-out;
opacity: 0.5;
// Tail
&::after {
content: "";
position: absolute;
left: 6px;
bottom: -8px;
width: 0;
height: 0;
border-style: solid;
border-width: 8px 6px 0 6px;
border-color: var(--color-error-red) transparent transparent transparent;
}
&.hover {
opacity: 0;
z-index: 1;
pointer-events: none;
}
&.faded:hover + .hover {
opacity: 1;
}
&.faded:hover {
z-index: 2;
opacity: 1;
-webkit-user-select: text;
user-select: text;
transition:
opacity 0.2s ease-in-out,
z-index 0s 0.2s;
&::selection {
background-color: var(--color-e-nearwhite);
// Target only Safari
@supports (background: -webkit-named-image(i)) {
& {
// Setting an alpha value opts out of Safari's "fancy" (but not visible on dark backgrounds) selection highlight rendering
// https://stackoverflow.com/a/71753552/775283
background-color: rgba(var(--color-e-nearwhite-rgb), calc(254 / 255));
}
}
}
}
}
&::after {
content: "";
position: absolute;

View file

@ -9,7 +9,7 @@
</script>
<LayoutRow class="parameter-expose-button">
<button class:exposed style:--data-type-color={`var(--color-data-${dataType})`} on:click={action} title={tooltip} tabindex="-1">
<button class:exposed style:--data-type-color={`var(--color-data-${dataType})`} style:--data-type-color-dim={`var(--color-data-${dataType}-dim)`} on:click={action} title={tooltip} tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
<path class="interior" d="M0,7.882c0,1.832,1.325,2.63,2.945,1.772L8.785,6.56c1.62-.858,1.62-2.262,0-3.12L2.945.345C1.325-.512,0,.285,0,2.118Z" />
<path
@ -53,25 +53,33 @@
}
&:not(.exposed) {
.outline {
fill: var(--data-type-color);
&:not(:hover) {
.outline {
fill: var(--data-type-color-dim);
}
}
&:hover {
.interior {
fill: var(--color-6-lowergray);
.outline {
fill: var(--data-type-color);
}
}
}
&.exposed {
.interior {
fill: var(--data-type-color);
&:not(:hover) {
.interior {
fill: var(--data-type-color);
}
}
&:hover {
.outline {
fill: var(--color-f-white);
fill: var(--data-type-color);
}
.interior {
fill: var(--data-type-color-dim);
}
}
}

2
frontend/src/consts.ts Normal file
View file

@ -0,0 +1,2 @@
import { cubicInOut } from "svelte/easing";
export const FADE_TRANSITION = { duration: 200, easing: cubicInOut };

View file

@ -82,18 +82,22 @@ export class FrontendDocumentDetails extends DocumentDetails {
readonly id!: bigint;
}
export type FrontendGraphDataType = "general" | "raster" | "color" | "vector" | "vec2" | "graphic" | "artboard";
export type FrontendGraphDataType = "general" | "number" | "raster" | "vector" | "color" | "artboard";
export class FrontendGraphInput {
readonly dataType!: FrontendGraphDataType;
readonly name!: string;
readonly resolvedType!: string | undefined;
}
export class FrontendGraphOutput {
readonly dataType!: FrontendGraphDataType;
readonly name!: string;
readonly resolvedType!: string | undefined;
}
export class FrontendNode {
@ -119,6 +123,8 @@ export class FrontendNode {
readonly previewed!: boolean;
readonly disabled!: boolean;
readonly errors!: string | undefined;
}
export class FrontendNodeLink {