|
<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> |
|
|