| <script lang="ts"> |
| import { getContext } from "svelte"; |
| |
| import type { Editor } from "@graphite/editor"; |
| import type { Widget, WidgetSpanColumn, WidgetSpanRow } from "@graphite/messages"; |
| import { narrowWidgetProps, isWidgetSpanColumn, isWidgetSpanRow } from "@graphite/messages"; |
| import { debouncer } from "@graphite/utility-functions/debounce"; |
| |
| import NodeCatalog from "@graphite/components/floating-menus/NodeCatalog.svelte"; |
| import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte"; |
| import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte"; |
| import ImageButton from "@graphite/components/widgets/buttons/ImageButton.svelte"; |
| import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte"; |
| import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.svelte"; |
| import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte"; |
| import CheckboxInput from "@graphite/components/widgets/inputs/CheckboxInput.svelte"; |
| import ColorInput from "@graphite/components/widgets/inputs/ColorInput.svelte"; |
| import CurveInput from "@graphite/components/widgets/inputs/CurveInput.svelte"; |
| import DropdownInput from "@graphite/components/widgets/inputs/DropdownInput.svelte"; |
| import FontInput from "@graphite/components/widgets/inputs/FontInput.svelte"; |
| import NumberInput from "@graphite/components/widgets/inputs/NumberInput.svelte"; |
| import RadioInput from "@graphite/components/widgets/inputs/RadioInput.svelte"; |
| import ReferencePointInput from "@graphite/components/widgets/inputs/ReferencePointInput.svelte"; |
| import TextAreaInput from "@graphite/components/widgets/inputs/TextAreaInput.svelte"; |
| import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte"; |
| import WorkingColorsInput from "@graphite/components/widgets/inputs/WorkingColorsInput.svelte"; |
| 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 WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; |
| |
| const editor = getContext<Editor>("editor"); |
| |
| export let widgetData: WidgetSpanRow | WidgetSpanColumn; |
| |
| 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); |
| |
| function watchDirection(widgetData: WidgetSpanRow | WidgetSpanColumn): "row" | "column" | undefined { |
| if (isWidgetSpanRow(widgetData)) return "row"; |
| if (isWidgetSpanColumn(widgetData)) return "column"; |
| } |
| |
| function watchWidgets(widgetData: WidgetSpanRow | WidgetSpanColumn): Widget[] { |
| let widgets: Widget[] = []; |
| if (isWidgetSpanRow(widgetData)) widgets = widgetData.rowWidgets; |
| else if (isWidgetSpanColumn(widgetData)) widgets = widgetData.columnWidgets; |
| return widgets; |
| } |
| |
| function widgetValueCommit(index: number, value: unknown) { |
| editor.handle.widgetValueCommit(layoutTarget, widgets[index].widgetId, value); |
| } |
| |
| function widgetValueUpdate(index: number, value: unknown) { |
| editor.handle.widgetValueUpdate(layoutTarget, widgets[index].widgetId, value); |
| } |
| |
| function widgetValueCommitAndUpdate(index: number, value: unknown) { |
| editor.handle.widgetValueCommitAndUpdate(layoutTarget, widgets[index].widgetId, value); |
| } |
| |
| |
| function exclude<T extends object>(props: T, additional?: (keyof T)[]): Omit<T, typeof additional extends Array<infer K> ? K : never> { |
| const exclusions = ["kind", ...(additional || [])]; |
| |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| return Object.fromEntries(Object.entries(props).filter((entry) => !exclusions.includes(entry[0]))) as any; |
| } |
| </script> |
| |
| {attributesObject} |
| |
| <div class={`widget-span ${className} ${extraClasses}`.trim()} class:row={direction === "row"} class:column={direction === "column"}> |
| {#each widgets as component, index} |
| {@const checkboxInput = narrowWidgetProps(component.props, "CheckboxInput")} |
| {#if checkboxInput} |
| <CheckboxInput {...exclude(checkboxInput)} on:checked={({ detail }) => widgetValueCommitAndUpdate(index, detail)} /> |
| {/if} |
| {@const colorInput = narrowWidgetProps(component.props, "ColorInput")} |
| {#if colorInput} |
| <ColorInput {...exclude(colorInput)} on:value={({ detail }) => widgetValueUpdate(index, detail)} on:startHistoryTransaction={() => widgetValueCommit(index, colorInput.value)} /> |
| {/if} |
| {@const curvesInput = narrowWidgetProps(component.props, "CurveInput")} |
| {#if curvesInput} |
| <CurveInput {...exclude(curvesInput)} on:value={({ detail }) => debouncer((value) => widgetValueCommitAndUpdate(index, value), { debounceTime: 120 }).debounceUpdateValue(detail)} /> |
| {/if} |
| {@const dropdownInput = narrowWidgetProps(component.props, "DropdownInput")} |
| {#if dropdownInput} |
| <DropdownInput |
| {...exclude(dropdownInput)} |
| on:hoverInEntry={({ detail }) => { |
| return widgetValueUpdate(index, detail); |
| }} |
| on:hoverOutEntry={({ detail }) => { |
| return widgetValueUpdate(index, detail); |
| }} |
| on:selectedIndex={({ detail }) => widgetValueCommitAndUpdate(index, detail)} |
| /> |
| {/if} |
| {@const fontInput = narrowWidgetProps(component.props, "FontInput")} |
| {#if fontInput} |
| <FontInput {...exclude(fontInput)} on:changeFont={({ detail }) => widgetValueCommitAndUpdate(index, detail)} /> |
| {/if} |
| {@const parameterExposeButton = narrowWidgetProps(component.props, "ParameterExposeButton")} |
| {#if parameterExposeButton} |
| <ParameterExposeButton {...exclude(parameterExposeButton)} action={() => widgetValueCommitAndUpdate(index, undefined)} /> |
| {/if} |
| {@const iconButton = narrowWidgetProps(component.props, "IconButton")} |
| {#if iconButton} |
| <IconButton {...exclude(iconButton)} action={() => widgetValueCommitAndUpdate(index, undefined)} /> |
| {/if} |
| {@const iconLabel = narrowWidgetProps(component.props, "IconLabel")} |
| {#if iconLabel} |
| <IconLabel {...exclude(iconLabel)} /> |
| {/if} |
| {@const imageButton = narrowWidgetProps(component.props, "ImageButton")} |
| {#if imageButton} |
| <ImageButton {...exclude(imageButton)} action={() => widgetValueCommitAndUpdate(index, undefined)} /> |
| {/if} |
| {@const nodeCatalog = narrowWidgetProps(component.props, "NodeCatalog")} |
| {#if nodeCatalog} |
| <NodeCatalog {...exclude(nodeCatalog)} on:selectNodeType={(e) => widgetValueCommitAndUpdate(index, e.detail)} /> |
| {/if} |
| {@const numberInput = narrowWidgetProps(component.props, "NumberInput")} |
| {#if numberInput} |
| <NumberInput |
| {...exclude(numberInput)} |
| on:value={({ detail }) => debouncer((value) => widgetValueUpdate(index, value)).debounceUpdateValue(detail)} |
| on:startHistoryTransaction={() => widgetValueCommit(index, numberInput.value)} |
| incrementCallbackIncrease={() => widgetValueCommitAndUpdate(index, "Increment")} |
| incrementCallbackDecrease={() => widgetValueCommitAndUpdate(index, "Decrement")} |
| /> |
| {/if} |
| {@const referencePointInput = narrowWidgetProps(component.props, "ReferencePointInput")} |
| {#if referencePointInput} |
| <ReferencePointInput {...exclude(referencePointInput)} on:value={({ detail }) => widgetValueCommitAndUpdate(index, detail)} /> |
| {/if} |
| {@const popoverButton = narrowWidgetProps(component.props, "PopoverButton")} |
| {#if popoverButton} |
| <PopoverButton {...exclude(popoverButton, ["popoverLayout"])}> |
| <WidgetLayout layout={{ layout: popoverButton.popoverLayout, layoutTarget: layoutTarget }} /> |
| </PopoverButton> |
| {/if} |
| {@const radioInput = narrowWidgetProps(component.props, "RadioInput")} |
| {#if radioInput} |
| <RadioInput {...exclude(radioInput)} on:selectedIndex={({ detail }) => widgetValueCommitAndUpdate(index, detail)} /> |
| {/if} |
| {@const separator = narrowWidgetProps(component.props, "Separator")} |
| {#if separator} |
| <Separator {...exclude(separator)} /> |
| {/if} |
| {@const workingColorsInput = narrowWidgetProps(component.props, "WorkingColorsInput")} |
| {#if workingColorsInput} |
| <WorkingColorsInput {...exclude(workingColorsInput)} /> |
| {/if} |
| {@const textAreaInput = narrowWidgetProps(component.props, "TextAreaInput")} |
| {#if textAreaInput} |
| <TextAreaInput {...exclude(textAreaInput)} on:commitText={({ detail }) => widgetValueCommitAndUpdate(index, detail)} /> |
| {/if} |
| {@const textButton = narrowWidgetProps(component.props, "TextButton")} |
| {#if textButton} |
| <TextButton {...exclude(textButton)} action={() => widgetValueCommitAndUpdate(index, undefined)} /> |
| {/if} |
| {@const breadcrumbTrailButtons = narrowWidgetProps(component.props, "BreadcrumbTrailButtons")} |
| {#if breadcrumbTrailButtons} |
| <BreadcrumbTrailButtons {...exclude(breadcrumbTrailButtons)} action={(breadcrumbIndex) => widgetValueCommitAndUpdate(index, breadcrumbIndex)} /> |
| {/if} |
| {@const textInput = narrowWidgetProps(component.props, "TextInput")} |
| {#if textInput} |
| <TextInput {...exclude(textInput)} on:commitText={({ detail }) => widgetValueCommitAndUpdate(index, detail)} /> |
| {/if} |
| {@const textLabel = narrowWidgetProps(component.props, "TextLabel")} |
| {#if textLabel} |
| <TextLabel {...exclude(textLabel, ["value"])}>{textLabel.value}</TextLabel> |
| {/if} |
| {/each} |
| </div> |
| |
| <style lang="scss" global> |
| .widget-span.column { |
| flex: 0 0 auto; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| .widget-span.row { |
| flex: 0 0 auto; |
| display: flex; |
| min-height: 32px; |
| |
| > * { |
| --widget-height: 24px; |
| margin: calc((24px - var(--widget-height)) / 2 + 4px) 0; |
| min-height: var(--widget-height); |
| |
| &:not(.multiline) { |
| line-height: var(--widget-height); |
| } |
| |
| &.icon-label.size-12 { |
| --widget-height: 12px; |
| } |
| |
| &.icon-label.size-16 { |
| --widget-height: 16px; |
| } |
| } |
| } |
| </style> |
| |