mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
refactor: remove double logic, change names
This commit is contained in:
@@ -145,17 +145,19 @@ round-семантика `clampByte` и trunc-семантика `clampInt` со
|
|||||||
|
|
||||||
## Low
|
## Low
|
||||||
|
|
||||||
### L1. Флаг `running` общий для `run()` и `download()`
|
### L1. Флаг `running` общий для `run()` и `download()` — СДЕЛАНО (2026-10)
|
||||||
|
|
||||||
`SchemaToolView.svelte:124,433,469`: параллельный auto-run и клик по download
|
В `schema-tool-runner.svelte.ts` флаги разделены: `running` (run) и
|
||||||
гоняют один `$state`; индикация и `disabled` ведут себя некорректно.
|
`downloading` (download); `download()` больше не затирает индикацию
|
||||||
|
параллельного auto-run. Компонент передаёт в превью
|
||||||
|
`busy = running || downloading`.
|
||||||
|
|
||||||
### L2. Дублирование dismiss-логики поповеров
|
### L2. Дублирование dismiss-логики поповеров — СДЕЛАНО (2026-10)
|
||||||
|
|
||||||
`SchemaDownload.svelte:62-76` и `ToolPickerButton.svelte:25-39` — побуквенно
|
Общий хелпер `onDismiss(root, close)` в `web/src/lib/components/ui/dismiss.ts`
|
||||||
одинаковые ~15 строк: pointerdown-outside + Escape + подписка на `window` в
|
(pointerdown-outside + Escape + отписка); `SchemaDownload.svelte` и
|
||||||
`$effect`. Кандидат на shared action (`clickOutside`/`popoverDismiss`). Сами
|
`ToolPickerButton.svelte` переведены на него — по ~15 одинаковых строк удалено
|
||||||
эффекты корректны.
|
из каждого.
|
||||||
|
|
||||||
### L3. Несогласованный API `Toggle`
|
### L3. Несогласованный API `Toggle`
|
||||||
|
|
||||||
@@ -178,10 +180,11 @@ round-семантика `clampByte` и trunc-семантика `clampInt` со
|
|||||||
`["linear-gradient-png","resize-png","quantize-png"]` (kit — витрина, не
|
`["linear-gradient-png","resize-png","quantize-png"]` (kit — витрина, не
|
||||||
production).
|
production).
|
||||||
|
|
||||||
### L6. Дублирование CSS-значения превью-тайла
|
### L6. Дублирование CSS-значения превью-тайла — СДЕЛАНО (2026-10)
|
||||||
|
|
||||||
`clamp(var(--space-brand), 30vh, 60vh)` в `PreviewTile.svelte:96` и
|
Новый токен `--size-tile-canvas-min: clamp(var(--space-brand), 30vh, 60vh)` в
|
||||||
`SchemaResultTile.svelte:129`; магические `30vh/60vh` не в токенах.
|
`app.css`; `PreviewTile.svelte` и `SchemaResultTile.svelte` используют
|
||||||
|
`var(--size-tile-canvas-min)`.
|
||||||
|
|
||||||
### L7. `localStorage.setItem` без try/catch вне app.html — СДЕЛАНО (2026-10)
|
### L7. `localStorage.setItem` без try/catch вне app.html — СДЕЛАНО (2026-10)
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,7 @@
|
|||||||
|
|
||||||
--size-preview-canvas-min-height-s: 180px;
|
--size-preview-canvas-min-height-s: 180px;
|
||||||
--size-preview-canvas-min-height-l: 360px;
|
--size-preview-canvas-min-height-l: 360px;
|
||||||
|
--size-tile-canvas-min: clamp(var(--space-brand), 30vh, 60vh);
|
||||||
--size-panel-min-height: 220px;
|
--size-panel-min-height: 220px;
|
||||||
|
|
||||||
--size-tool-icon: 42px;
|
--size-tool-icon: 42px;
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: clamp(var(--space-brand), 30vh, 60vh);
|
min-height: var(--size-tile-canvas-min);
|
||||||
border: var(--size-border) solid var(--color-border);
|
border: var(--size-border) solid var(--color-border);
|
||||||
border-radius: var(--radius-s);
|
border-radius: var(--radius-s);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import RangeControl from "$lib/components/fields/schema/RangeControl.svelte";
|
import RangeControl from "$lib/components/fields/schema/RangeControl.svelte";
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
|
import { onDismiss } from "$lib/components/ui/dismiss";
|
||||||
import type { OutputMime } from "$lib/core/io";
|
import type { OutputMime } from "$lib/core/io";
|
||||||
import { OUTPUT_FORMATS, outputFormatByMime } from "$lib/output-formats";
|
import { OUTPUT_FORMATS, outputFormatByMime } from "$lib/output-formats";
|
||||||
import type { SliderSpec } from "$lib/registry-schema";
|
import type { SliderSpec } from "$lib/registry-schema";
|
||||||
@@ -58,18 +59,10 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
function onPointerDown(e: PointerEvent) {
|
return onDismiss(
|
||||||
if (!root?.contains(e.target as Node)) open = false;
|
() => root,
|
||||||
}
|
() => (open = false),
|
||||||
function onKey(e: KeyboardEvent) {
|
);
|
||||||
if (e.key === "Escape") open = false;
|
|
||||||
}
|
|
||||||
window.addEventListener("pointerdown", onPointerDown);
|
|
||||||
window.addEventListener("keydown", onKey);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("pointerdown", onPointerDown);
|
|
||||||
window.removeEventListener("keydown", onKey);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
ломал parts-grid в одну колонку. */
|
ломал parts-grid в одну колонку. */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
min-height: clamp(var(--space-brand), 30vh, 60vh);
|
min-height: var(--size-tile-canvas-min);
|
||||||
border: var(--size-border) solid var(--color-border);
|
border: var(--size-border) solid var(--color-border);
|
||||||
border-radius: var(--radius-s);
|
border-radius: var(--radius-s);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|||||||
@@ -142,6 +142,8 @@
|
|||||||
});
|
});
|
||||||
const { run, download, copyText, downloadText } = runner;
|
const { run, download, copyText, downloadText } = runner;
|
||||||
const running = $derived(runner.running);
|
const running = $derived(runner.running);
|
||||||
|
// Display-level busy: either a run or a download is in flight.
|
||||||
|
const busy = $derived(running || runner.downloading);
|
||||||
const result = $derived(runner.result);
|
const result = $derived(runner.result);
|
||||||
const fileResult = $derived(runner.fileResult);
|
const fileResult = $derived(runner.fileResult);
|
||||||
const textResult = $derived(runner.textResult);
|
const textResult = $derived(runner.textResult);
|
||||||
@@ -499,7 +501,7 @@
|
|||||||
toolId={lastTool.id}
|
toolId={lastTool.id}
|
||||||
{inputMode}
|
{inputMode}
|
||||||
resultKind={displayResultKind}
|
resultKind={displayResultKind}
|
||||||
{running}
|
running={busy}
|
||||||
error={errorText}
|
error={errorText}
|
||||||
{aligned}
|
{aligned}
|
||||||
ontogglealign={() => (aligned = !aligned)}
|
ontogglealign={() => (aligned = !aligned)}
|
||||||
@@ -561,7 +563,7 @@
|
|||||||
{source}
|
{source}
|
||||||
{sourceFiles}
|
{sourceFiles}
|
||||||
{textSource}
|
{textSource}
|
||||||
{running}
|
running={busy}
|
||||||
resultKind={displayResultKind}
|
resultKind={displayResultKind}
|
||||||
{result}
|
{result}
|
||||||
{resultNote}
|
{resultNote}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
|
import { onDismiss } from "$lib/components/ui/dismiss";
|
||||||
import { pageTitle } from "$lib/i18n/schema-tool-strings";
|
import { pageTitle } from "$lib/i18n/schema-tool-strings";
|
||||||
import { chainablePages } from "$lib/pipeline.svelte";
|
import { chainablePages } from "$lib/pipeline.svelte";
|
||||||
import type { Page } from "$lib/registry";
|
import type { Page } from "$lib/registry";
|
||||||
@@ -24,18 +25,10 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
function onPointerDown(e: PointerEvent) {
|
return onDismiss(
|
||||||
if (!root?.contains(e.target as Node)) open = false;
|
() => root,
|
||||||
}
|
() => (open = false),
|
||||||
function onKey(e: KeyboardEvent) {
|
);
|
||||||
if (e.key === "Escape") open = false;
|
|
||||||
}
|
|
||||||
window.addEventListener("pointerdown", onPointerDown);
|
|
||||||
window.addEventListener("keydown", onKey);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("pointerdown", onPointerDown);
|
|
||||||
window.removeEventListener("keydown", onKey);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,10 @@ export interface SchemaToolRunnerCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface RunnerState {
|
interface RunnerState {
|
||||||
|
// run() and download() have separate flags: a parallel auto-run and a
|
||||||
|
// download click must not clobber each other's indicator/disabled state.
|
||||||
running: boolean;
|
running: boolean;
|
||||||
|
downloading: boolean;
|
||||||
// Generation of the latest run(): a stale in-flight chain (params changed
|
// Generation of the latest run(): a stale in-flight chain (params changed
|
||||||
// while the worker was busy) must not overwrite newer results.
|
// while the worker was busy) must not overwrite newer results.
|
||||||
runGen: number;
|
runGen: number;
|
||||||
@@ -138,7 +141,7 @@ async function run(s: RunnerState, ctx: SchemaToolRunnerCtx) {
|
|||||||
|
|
||||||
async function download(s: RunnerState, ctx: SchemaToolRunnerCtx) {
|
async function download(s: RunnerState, ctx: SchemaToolRunnerCtx) {
|
||||||
if (!ctx.schema()) return;
|
if (!ctx.schema()) return;
|
||||||
s.running = true;
|
s.downloading = true;
|
||||||
s.displayError = null;
|
s.displayError = null;
|
||||||
try {
|
try {
|
||||||
if (s.fileResult) {
|
if (s.fileResult) {
|
||||||
@@ -157,7 +160,7 @@ async function download(s: RunnerState, ctx: SchemaToolRunnerCtx) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
s.displayError = toDisplayError(e);
|
s.displayError = toDisplayError(e);
|
||||||
} finally {
|
} finally {
|
||||||
s.running = false;
|
s.downloading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +201,7 @@ function clearResults(s: RunnerState) {
|
|||||||
export function createSchemaToolRunner(ctx: SchemaToolRunnerCtx) {
|
export function createSchemaToolRunner(ctx: SchemaToolRunnerCtx) {
|
||||||
const s = $state<RunnerState>({
|
const s = $state<RunnerState>({
|
||||||
running: false,
|
running: false,
|
||||||
|
downloading: false,
|
||||||
runGen: 0,
|
runGen: 0,
|
||||||
result: null,
|
result: null,
|
||||||
fileResult: null,
|
fileResult: null,
|
||||||
@@ -213,6 +217,9 @@ export function createSchemaToolRunner(ctx: SchemaToolRunnerCtx) {
|
|||||||
get running() {
|
get running() {
|
||||||
return s.running;
|
return s.running;
|
||||||
},
|
},
|
||||||
|
get downloading() {
|
||||||
|
return s.downloading;
|
||||||
|
},
|
||||||
get result() {
|
get result() {
|
||||||
return s.result;
|
return s.result;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Popover dismiss: close on pointerdown outside the root and on Escape.
|
||||||
|
// Subscribe inside `$effect` while the popover is open; returns unsubscribe.
|
||||||
|
export function onDismiss(
|
||||||
|
root: () => HTMLElement | null,
|
||||||
|
close: () => void,
|
||||||
|
): () => void {
|
||||||
|
function onPointerDown(e: PointerEvent) {
|
||||||
|
if (!root()?.contains(e.target as Node)) close();
|
||||||
|
}
|
||||||
|
function onKey(e: KeyboardEvent) {
|
||||||
|
if (e.key === "Escape") close();
|
||||||
|
}
|
||||||
|
window.addEventListener("pointerdown", onPointerDown);
|
||||||
|
window.addEventListener("keydown", onKey);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("pointerdown", onPointerDown);
|
||||||
|
window.removeEventListener("keydown", onKey);
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user