mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
Fix FieldInput and several other Svelte bugs
This commit is contained in:
parent
2e3495aa0e
commit
7ce9d6db05
9 changed files with 56 additions and 25 deletions
7
frontend-svelte/assets/LICENSE.md
Normal file
7
frontend-svelte/assets/LICENSE.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
Copyright (c) 2021-2023 Keavon Chambers
|
||||
|
||||
The design assets in this directory (including SVG code for icons and logos) are NOT licensed under the Apache 2.0 license terms applied to other Graphite source code files. This directory and its entire contents are excluded from the Apache 2.0 source code license, and copyrights are held by the author for the creative works contained as files herein.
|
||||
|
||||
Parties interested in using Graphite source code in a capacity that deploys the Graphite Editor reference frontend are advised to substitute all assets and "Graphite" branding or otherwise arrange written permission from the rightsholder. The recommended use case for adopting Graphite open source code is to develop one's own unique frontend user interface implementation that integrates Graphite's backend technology.
|
||||
|
||||
The author and rightsholder, Keavon Chambers, may be reached through the email address listed at https://graphite.rs/contact/ or https://keavon.com.
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path class="bright" d="M6,1C4.3,1,3,2.3,3,4v4h5V1H6z M7,6v1H4c0,0,0-0.6,0.2-1c0.3-0.6,0.8-1.1,1.3-1.4c1.1-0.9,0.8-1.9,0.1-1.7C5,3,4.8,3.6,4.9,4.1H4C3.9,2.9,4.7,2,5.8,2c1.1,0,1.5,1.2,1,2.3C6.4,5.1,5.5,5.5,5.4,6H7z" />
|
||||
<path class="dim" d="M10,1H9v1h1c1.1,0,2,0.9,2,2v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V9H3v1c0,2.76,2.24,5,5,5s5-2.24,5-5V4C13,2.35,11.65,1,10,1z" />
|
||||
</svg>
|
After Width: | Height: | Size: 432 B |
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path class="bright" d="M10,1H8v7h5V4C13,2.3,11.7,1,10,1z M12,7H9.1c0,0,0-0.6,0.2-1c0.2-0.6,0.8-1.1,1.3-1.5c0.9-0.8,0.4-1.8-0.4-1.6C9.6,3.1,9.9,4.1,9.9,4.1H9.1c-0.3-1,0.1-2.2,1.3-2.1c1,0.1,1.6,1.1,1.4,2.1C11.6,5,10.6,5.4,10.5,6H12V7z" />
|
||||
<path class="dim" d="M6,1h1v1H6C4.9,2,4,2.9,4,4v6c0,2.21,1.79,4,4,4s4-1.79,4-4V9h1v1c0,2.76-2.24,5-5,5s-5-2.24-5-5V4C3,2.35,4.35,1,6,1z" />
|
||||
</svg>
|
After Width: | Height: | Size: 448 B |
|
@ -320,7 +320,10 @@
|
|||
{/if}
|
||||
<NumberInput
|
||||
value={strength}
|
||||
on:value={({ detail }) => setColorRGB(channel, detail)}
|
||||
on:value={({ detail }) => {
|
||||
strength = detail;
|
||||
setColorRGB(channel, detail);
|
||||
}}
|
||||
min={0}
|
||||
max={255}
|
||||
minWidth={56}
|
||||
|
@ -341,7 +344,10 @@
|
|||
{/if}
|
||||
<NumberInput
|
||||
value={strength}
|
||||
on:value={({ detail }) => setColorHSV(channel, detail)}
|
||||
on:value={({ detail }) => {
|
||||
strength = detail;
|
||||
setColorHSV(channel, detail);
|
||||
}}
|
||||
min={0}
|
||||
max={channel === "h" ? 360 : 100}
|
||||
unit={channel === "h" ? "°" : "%"}
|
||||
|
@ -358,7 +364,10 @@
|
|||
<NumberInput
|
||||
label="Alpha"
|
||||
value={!isNone ? alpha * 100 : undefined}
|
||||
on:value={({ detail }) => setColorAlphaPercent(detail)}
|
||||
on:value={({ detail }) => {
|
||||
if (detail !== undefined) alpha = detail / 100;
|
||||
setColorAlphaPercent(detail);
|
||||
}}
|
||||
min={0}
|
||||
max={100}
|
||||
rangeMin={0}
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
return sparse;
|
||||
}
|
||||
|
||||
function buildNodeCategories(nodeTypes: FrontendNodeType[], searchTerm: string) {
|
||||
function buildNodeCategories(nodeTypes: FrontendNodeType[], searchTerm: string): [string, FrontendNodeType[]][] {
|
||||
const categories = new Map();
|
||||
nodeTypes.forEach((node) => {
|
||||
if (searchTerm.length > 0 && !node.name.toLowerCase().includes(searchTerm.toLowerCase()) && !node.category.toLowerCase().includes(searchTerm.toLowerCase())) {
|
||||
|
@ -488,10 +488,10 @@
|
|||
{#if nodeListLocation}
|
||||
<LayoutCol class="node-list" data-node-list styles={{ "margin-left": `${nodeListX}px`, "margin-top": `${nodeListY}px` }}>
|
||||
<TextInput placeholder="Search Nodes..." value={searchTerm} on:value={({ detail }) => (searchTerm = detail)} bind:this={nodeSearchInput} />
|
||||
{#each nodeCategories as nodeCategory (nodeCategory[0])}
|
||||
{#each nodeCategories as nodeCategory}
|
||||
<LayoutCol>
|
||||
<TextLabel>{nodeCategory[0]}</TextLabel>
|
||||
{#each nodeCategory[1] as nodeType (String(nodeType))}
|
||||
{#each nodeCategory[1] as nodeType}
|
||||
<TextButton label={nodeType.name} action={() => createNode(nodeType.name)} />
|
||||
{/each}
|
||||
</LayoutCol>
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
{#if numberInput}
|
||||
<NumberInput
|
||||
{...exclude(numberInput)}
|
||||
on:value={({ detail }) => debouncer(() => updateLayout(index, detail))}
|
||||
on:value={({ detail }) => debouncer((value) => updateLayout(index, value)).updateValue(detail)}
|
||||
incrementCallbackIncrease={() => updateLayout(index, "Increment")}
|
||||
incrementCallbackDecrease={() => updateLayout(index, "Decrement")}
|
||||
sharpRightCorners={nextIsSuffix}
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
let inputOrTextarea: HTMLInputElement | HTMLTextAreaElement;
|
||||
let id = `${Math.random()}`.substring(2);
|
||||
let macKeyboardLayout = platformIsMac();
|
||||
let inputValue = value;
|
||||
|
||||
$: inputValue = value;
|
||||
$: dispatch("value", inputValue);
|
||||
|
||||
// Select (highlight) all the text. For technical reasons, it is necessary to pass the current text.
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
export let incrementCallbackDecrease: (() => void) | undefined = undefined;
|
||||
|
||||
let self: FieldInput;
|
||||
let text = displayText(value);
|
||||
let text = displayText(value, displayDecimalPlaces, unit);
|
||||
let editing = false;
|
||||
// Stays in sync with a binding to the actual input range slider element.
|
||||
let rangeSliderValue = value !== undefined ? value : 0;
|
||||
|
@ -84,10 +84,10 @@
|
|||
if (typeof min === "number") sanitized = Math.max(sanitized, min);
|
||||
if (typeof max === "number") sanitized = Math.min(sanitized, max);
|
||||
|
||||
text = displayText(sanitized);
|
||||
text = displayText(sanitized, displayDecimalPlaces, unit);
|
||||
}
|
||||
|
||||
function sliderInput() {
|
||||
function onSliderInput() {
|
||||
// Keep only 4 digits after the decimal point
|
||||
const ROUNDING_EXPONENT = 4;
|
||||
const ROUNDING_MAGNITUDE = 10 ** ROUNDING_EXPONENT;
|
||||
|
@ -113,10 +113,10 @@
|
|||
|
||||
// If we're in a dragging state, we want to use the new slider value
|
||||
rangeSliderValueAsRendered = roundedValue;
|
||||
updateValue(roundedValue);
|
||||
updateValue(roundedValue, min, max, displayDecimalPlaces, unit);
|
||||
}
|
||||
|
||||
function sliderPointerDown() {
|
||||
function onSliderPointerDown() {
|
||||
// We want to render the fake slider thumb at the old position, which is still the number held by `value`
|
||||
rangeSliderValueAsRendered = value || 0;
|
||||
|
||||
|
@ -124,7 +124,7 @@
|
|||
// invocation will transition the state machine to `mousedown`. That's why we don't do it here.
|
||||
}
|
||||
|
||||
function sliderPointerUp() {
|
||||
function onSliderPointerUp() {
|
||||
// User clicked but didn't drag, so we focus the text input element
|
||||
if (rangeSliderClickDragState === "mousedown") {
|
||||
const inputElement = self.element().querySelector("[data-input-element]") as HTMLInputElement | undefined;
|
||||
|
@ -160,7 +160,7 @@
|
|||
const parsed = parseFloat(text);
|
||||
const newValue = Number.isNaN(parsed) ? undefined : parsed;
|
||||
|
||||
updateValue(newValue);
|
||||
updateValue(newValue, min, max, displayDecimalPlaces, unit);
|
||||
|
||||
editing = false;
|
||||
|
||||
|
@ -168,7 +168,7 @@
|
|||
}
|
||||
|
||||
function onCancelTextChange() {
|
||||
updateValue(undefined);
|
||||
updateValue(undefined, min, max, displayDecimalPlaces, unit);
|
||||
|
||||
editing = false;
|
||||
|
||||
|
@ -181,11 +181,11 @@
|
|||
const actions: Record<NumberInputIncrementBehavior, () => void> = {
|
||||
Add: () => {
|
||||
const directionAddend = direction === "Increase" ? step : -step;
|
||||
updateValue(value !== undefined ? value + directionAddend : undefined);
|
||||
updateValue(value !== undefined ? value + directionAddend : undefined, min, max, displayDecimalPlaces, unit);
|
||||
},
|
||||
Multiply: () => {
|
||||
const directionMultiplier = direction === "Increase" ? step : 1 / step;
|
||||
updateValue(value !== undefined ? value * directionMultiplier : undefined);
|
||||
updateValue(value !== undefined ? value * directionMultiplier : undefined, min, max, displayDecimalPlaces, unit);
|
||||
},
|
||||
Callback: () => {
|
||||
if (direction === "Increase") incrementCallbackIncrease?.();
|
||||
|
@ -197,20 +197,20 @@
|
|||
action();
|
||||
}
|
||||
|
||||
function updateValue(newValue: number | undefined) {
|
||||
function updateValue(newValue: number | undefined, min: number | undefined, max: number | undefined, displayDecimalPlaces: number, unit: string) {
|
||||
// Check if the new value is valid, otherwise we use the old value (rounded if it's an integer)
|
||||
const nowValid = value !== undefined && isInteger ? Math.round(value) : value;
|
||||
let cleaned = newValue !== undefined ? newValue : nowValid;
|
||||
|
||||
if (typeof min === "number" && !Number.isNaN(min) && cleaned !== undefined) cleaned = Math.max(cleaned, min);
|
||||
if (typeof max === "number" && !Number.isNaN(max) && cleaned !== undefined) cleaned = Math.min(cleaned, max);
|
||||
|
||||
// Required as the call to update:value can, not change the value
|
||||
text = displayText(value);
|
||||
text = displayText(cleaned, displayDecimalPlaces, unit);
|
||||
|
||||
if (newValue !== undefined) dispatch("value", cleaned);
|
||||
}
|
||||
|
||||
function displayText(value: number | undefined): string {
|
||||
function displayText(value: number | undefined, displayDecimalPlaces: number, unit: string): string {
|
||||
if (value === undefined) return "-";
|
||||
|
||||
// Find the amount of digits on the left side of the decimal
|
||||
|
@ -261,9 +261,9 @@
|
|||
max={rangeMax}
|
||||
step={sliderStepValue}
|
||||
{disabled}
|
||||
on:input={sliderInput}
|
||||
on:pointerdown={sliderPointerDown}
|
||||
on:pointerup={sliderPointerUp}
|
||||
on:input={onSliderInput}
|
||||
on:pointerdown={onSliderPointerDown}
|
||||
on:pointerup={onSliderPointerUp}
|
||||
tabindex="-1"
|
||||
/>
|
||||
{/if}
|
||||
|
|
7
frontend/assets/LICENSE.md
Normal file
7
frontend/assets/LICENSE.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
Copyright (c) 2021-2023 Keavon Chambers
|
||||
|
||||
The design assets in this directory (including SVG code for icons and logos) are NOT licensed under the Apache 2.0 license terms applied to other Graphite source code files. This directory and its entire contents are excluded from the Apache 2.0 source code license, and copyrights are held by the author for the creative works contained as files herein.
|
||||
|
||||
Parties interested in using Graphite source code in a capacity that deploys the Graphite Editor reference frontend are advised to substitute all assets and "Graphite" branding or otherwise arrange written permission from the rightsholder. The recommended use case for adopting Graphite open source code is to develop one's own unique frontend user interface implementation that integrates Graphite's backend technology.
|
||||
|
||||
The author and rightsholder, Keavon Chambers, may be reached through the email address listed at https://graphite.rs/contact/ or https://keavon.com.
|
Loading…
Add table
Add a link
Reference in a new issue