mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
feat: aligned mode for pipeline
This commit is contained in:
@@ -54,14 +54,6 @@
|
|||||||
браузере, порядок файлов, поведение many → 1 → many в одной цепочке.
|
браузере, порядок файлов, поведение many → 1 → many в одной цепочке.
|
||||||
Зависит от идеи 10 (остальные потребители 1 → many) и идеи 4 (batch по
|
Зависит от идеи 10 (остальные потребители 1 → many) и идеи 4 (batch по
|
||||||
архиву), `docs/roadmap.md` фаза 8 «batch-страница».
|
архиву), `docs/roadmap.md` фаза 8 «batch-страница».
|
||||||
- [ ] **Идея 17. Промежуточные результаты шагов + выравнивание рядов** — оценка
|
|
||||||
M. Два режима просмотра цепочки: - **без выравнивания (по умолчанию):** в
|
|
||||||
панели просмотра цепочка изображений по 2 в колонку «исходник → результат
|
|
||||||
1 → результат 2 → … → конечный результат»; - **с выравниванием (галочка
|
|
||||||
«выровнять инструменты и просмотр»):** у каждого инструмента показаны его
|
|
||||||
исходник и результат построчно «параметры — исходник — результат»;
|
|
||||||
промежуточные картинки при этом осознанно дублируются на экране —
|
|
||||||
результат шага является исходником следующего.
|
|
||||||
|
|
||||||
## Инструменты
|
## Инструменты
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
import SchemaActions from "./SchemaActions.svelte";
|
import SchemaActions from "./SchemaActions.svelte";
|
||||||
import SchemaResultTile from "./SchemaResultTile.svelte";
|
import SchemaResultTile from "./SchemaResultTile.svelte";
|
||||||
import SchemaSourceTile from "./SchemaSourceTile.svelte";
|
import SchemaSourceTile from "./SchemaSourceTile.svelte";
|
||||||
|
import PreviewTile from "./PreviewTile.svelte";
|
||||||
|
import Toggle from "$lib/components/ui/Toggle.svelte";
|
||||||
|
import { toDataUrl } from "$lib/core/io";
|
||||||
import type { PixelImage } from "$lib/core/types";
|
import type { PixelImage } from "$lib/core/types";
|
||||||
import type { OutputMime } from "$lib/core/io";
|
import type { OutputMime } from "$lib/core/io";
|
||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
@@ -26,6 +29,9 @@
|
|||||||
textVars?: Record<string, string | number>;
|
textVars?: Record<string, string | number>;
|
||||||
running?: boolean;
|
running?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
stepResults?: (PixelImage | null)[];
|
||||||
|
aligned?: boolean;
|
||||||
|
ontogglealign?: () => void;
|
||||||
format?: OutputMime;
|
format?: OutputMime;
|
||||||
quality?: number;
|
quality?: number;
|
||||||
alphaLoss?: boolean;
|
alphaLoss?: boolean;
|
||||||
@@ -51,6 +57,9 @@
|
|||||||
textVars = undefined,
|
textVars = undefined,
|
||||||
running = false,
|
running = false,
|
||||||
error = "",
|
error = "",
|
||||||
|
stepResults = [],
|
||||||
|
aligned = false,
|
||||||
|
ontogglealign,
|
||||||
format = "image/png",
|
format = "image/png",
|
||||||
quality = undefined,
|
quality = undefined,
|
||||||
alphaLoss = false,
|
alphaLoss = false,
|
||||||
@@ -71,6 +80,16 @@
|
|||||||
|
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<span class="label">{t("resultCard.previewPanel")}</span>
|
<span class="label">{t("resultCard.previewPanel")}</span>
|
||||||
|
{#if stepResults.length > 1}
|
||||||
|
<label class="align-toggle">
|
||||||
|
<Toggle
|
||||||
|
checked={aligned}
|
||||||
|
label={t("chain.alignToggle")}
|
||||||
|
onchange={() => ontogglealign?.()}
|
||||||
|
/>
|
||||||
|
<span>{t("chain.alignToggle")}</span>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
<SchemaActions
|
<SchemaActions
|
||||||
{inputMode}
|
{inputMode}
|
||||||
{resultKind}
|
{resultKind}
|
||||||
@@ -100,6 +119,17 @@
|
|||||||
{onrendertext}
|
{onrendertext}
|
||||||
{onupload}
|
{onupload}
|
||||||
/>
|
/>
|
||||||
|
{#each stepResults.slice(0, -1) as img, i (i)}
|
||||||
|
{#if img}
|
||||||
|
<PreviewTile
|
||||||
|
label={t("chain.stepResult", { n: i + 1 })}
|
||||||
|
viewMode="image"
|
||||||
|
dims={`${img.width} × ${img.height}`}
|
||||||
|
>
|
||||||
|
<img src={toDataUrl(img)} alt="" />
|
||||||
|
</PreviewTile>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
<SchemaResultTile
|
<SchemaResultTile
|
||||||
{resultKind}
|
{resultKind}
|
||||||
{result}
|
{result}
|
||||||
@@ -118,7 +148,7 @@
|
|||||||
.panel-head {
|
.panel-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
gap: var(--space-m);
|
gap: var(--space-m);
|
||||||
padding-bottom: var(--space-l);
|
padding-bottom: var(--space-l);
|
||||||
border-bottom: var(--size-border) solid var(--color-border);
|
border-bottom: var(--size-border) solid var(--color-border);
|
||||||
@@ -130,6 +160,15 @@
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--color-main);
|
color: var(--color-main);
|
||||||
}
|
}
|
||||||
|
.align-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
margin-right: auto;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
.error {
|
.error {
|
||||||
margin: 0 0 var(--space-l);
|
margin: 0 0 var(--space-l);
|
||||||
color: var(--color-danger);
|
color: var(--color-danger);
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AddStepButton from "./AddStepButton.svelte";
|
|
||||||
import SchemaFields from "./SchemaFields.svelte";
|
|
||||||
import SchemaPreview from "./SchemaPreview.svelte";
|
|
||||||
import ToolPickerButton from "./ToolPickerButton.svelte";
|
|
||||||
import StepCard from "$lib/components/display/StepCard.svelte";
|
import StepCard from "$lib/components/display/StepCard.svelte";
|
||||||
|
import Toggle from "$lib/components/ui/Toggle.svelte";
|
||||||
import { hasTransparency } from "$lib/core/analyze";
|
import { hasTransparency } from "$lib/core/analyze";
|
||||||
import { debounce } from "$lib/core/debounce";
|
import { debounce } from "$lib/core/debounce";
|
||||||
import { ToolError, type ErrorVars } from "$lib/core/errors";
|
import { ToolError, type ErrorVars } from "$lib/core/errors";
|
||||||
@@ -12,6 +9,7 @@
|
|||||||
downloadBlob,
|
downloadBlob,
|
||||||
encode,
|
encode,
|
||||||
outputFormatByMime,
|
outputFormatByMime,
|
||||||
|
toDataUrl,
|
||||||
type OutputMime,
|
type OutputMime,
|
||||||
} from "$lib/core/io";
|
} from "$lib/core/io";
|
||||||
import type { PixelImage } from "$lib/core/types";
|
import type { PixelImage } from "$lib/core/types";
|
||||||
@@ -51,6 +49,14 @@
|
|||||||
import { downloadZip } from "$lib/zip";
|
import { downloadZip } from "$lib/zip";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { SvelteMap, SvelteSet } from "svelte/reactivity";
|
import { SvelteMap, SvelteSet } from "svelte/reactivity";
|
||||||
|
import AddStepButton from "./AddStepButton.svelte";
|
||||||
|
import PreviewTile from "./PreviewTile.svelte";
|
||||||
|
import SchemaActions from "./SchemaActions.svelte";
|
||||||
|
import SchemaFields from "./SchemaFields.svelte";
|
||||||
|
import SchemaPreview from "./SchemaPreview.svelte";
|
||||||
|
import SchemaResultTile from "./SchemaResultTile.svelte";
|
||||||
|
import SchemaSourceTile from "./SchemaSourceTile.svelte";
|
||||||
|
import ToolPickerButton from "./ToolPickerButton.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
page: Page;
|
page: Page;
|
||||||
@@ -74,6 +80,8 @@
|
|||||||
// Последняя правленая ось dimension-поля — ведущая при lockAspect.
|
// Последняя правленая ось dimension-поля — ведущая при lockAspect.
|
||||||
const lastAxis: Record<string, "width" | "height"> = {};
|
const lastAxis: Record<string, "width" | "height"> = {};
|
||||||
let stepDims = $state<(Dimension | undefined)[]>([]);
|
let stepDims = $state<(Dimension | undefined)[]>([]);
|
||||||
|
let stepResults = $state<(PixelImage | null)[]>([]);
|
||||||
|
let aligned = $state(false);
|
||||||
let source = $state<PixelImage | null>(null);
|
let source = $state<PixelImage | null>(null);
|
||||||
let result = $state<PixelImage | null>(null);
|
let result = $state<PixelImage | null>(null);
|
||||||
let fileResult = $state<FileResult | null>(null);
|
let fileResult = $state<FileResult | null>(null);
|
||||||
@@ -97,6 +105,9 @@
|
|||||||
const resultKind = $derived(lastTool.result ?? "image");
|
const resultKind = $derived(lastTool.result ?? "image");
|
||||||
// Цепочку можно продолжить, только если последний шаг отдаёт картинку.
|
// Цепочку можно продолжить, только если последний шаг отдаёт картинку.
|
||||||
const canExtend = $derived((lastTool.result ?? "image") === "image");
|
const canExtend = $derived((lastTool.result ?? "image") === "image");
|
||||||
|
const alignedMode = $derived(
|
||||||
|
aligned && resultKind === "image" && steps.length > 1,
|
||||||
|
);
|
||||||
|
|
||||||
const alphaLoss = $derived(
|
const alphaLoss = $derived(
|
||||||
resultKind === "image" &&
|
resultKind === "image" &&
|
||||||
@@ -327,6 +338,7 @@
|
|||||||
try {
|
try {
|
||||||
let current: PixelImage | undefined = source ?? undefined;
|
let current: PixelImage | undefined = source ?? undefined;
|
||||||
const dims: (Dimension | undefined)[] = [];
|
const dims: (Dimension | undefined)[] = [];
|
||||||
|
const results: (PixelImage | null)[] = [];
|
||||||
let out: ToolResult = "";
|
let out: ToolResult = "";
|
||||||
for (let i = 0; i < steps.length; i++) {
|
for (let i = 0; i < steps.length; i++) {
|
||||||
const stepTool = getTool(steps[i].id);
|
const stepTool = getTool(steps[i].id);
|
||||||
@@ -353,11 +365,14 @@
|
|||||||
if (out && typeof out === "object" && "data" in out) {
|
if (out && typeof out === "object" && "data" in out) {
|
||||||
current = out as PixelImage;
|
current = out as PixelImage;
|
||||||
dims[i] = { width: current.width, height: current.height };
|
dims[i] = { width: current.width, height: current.height };
|
||||||
|
results[i] = current;
|
||||||
} else {
|
} else {
|
||||||
current = undefined;
|
current = undefined;
|
||||||
|
results[i] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stepDims = dims;
|
stepDims = dims;
|
||||||
|
stepResults = results;
|
||||||
assignResult(out);
|
assignResult(out);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
displayError = toDisplayError(e);
|
displayError = toDisplayError(e);
|
||||||
@@ -470,87 +485,204 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="workspace">
|
{#snippet stepCard(step: ChainStep, i: number)}
|
||||||
<section class="settings">
|
<StepCard
|
||||||
|
index={i + 1}
|
||||||
|
title={stepTitle(step.id)}
|
||||||
|
draggable
|
||||||
|
collapsed={step.collapsed ?? false}
|
||||||
|
ontoggle={() => toggleStep(i)}
|
||||||
|
onremove={steps.length > 1 ? () => removeStepAt(step.key) : undefined}
|
||||||
|
ondragstart={(e) => {
|
||||||
|
dragFrom = i;
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.dataTransfer.effectAllowed = "move";
|
||||||
|
e.dataTransfer.setData("text/plain", String(i));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ondragover={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
||||||
|
}}
|
||||||
|
ondrop={(e) => onStepDrop(e, i)}
|
||||||
|
ondragend={() => (dragFrom = null)}
|
||||||
|
>
|
||||||
|
{#snippet tools()}
|
||||||
|
<ToolPickerButton
|
||||||
|
label={t("chain.changeTool")}
|
||||||
|
iconOnly
|
||||||
|
onadd={(id) => replaceStepTool(i, id)}
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
<SchemaFields
|
||||||
|
schema={toolSchemaOf(step)}
|
||||||
|
values={step.params}
|
||||||
|
toolId={step.id}
|
||||||
|
sourceDims={i === 0 ? (source ?? undefined) : stepDims[i - 1]}
|
||||||
|
onchange={(id, v, axis) => setStepValue(i, id, v, axis)}
|
||||||
|
onreset={() => resetStep(i)}
|
||||||
|
/>
|
||||||
|
</StepCard>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#if !alignedMode}
|
||||||
|
<div class="workspace">
|
||||||
|
<section class="settings">
|
||||||
|
{#if canExtend}
|
||||||
|
<AddStepButton onadd={(id) => addStepAt(0, id)} />
|
||||||
|
{/if}
|
||||||
|
{#each steps as step, i (step.key)}
|
||||||
|
{@render stepCard(step, i)}
|
||||||
|
{#if canExtend}
|
||||||
|
<AddStepButton onadd={(id) => addStepAt(i + 1, id)} />
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<SchemaPreview
|
||||||
|
toolId={lastTool.id}
|
||||||
|
{source}
|
||||||
|
{result}
|
||||||
|
{resultNote}
|
||||||
|
{fileResult}
|
||||||
|
{textSource}
|
||||||
|
{textResult}
|
||||||
|
textVars={verdictVars}
|
||||||
|
{inputMode}
|
||||||
|
{resultKind}
|
||||||
|
{running}
|
||||||
|
error={errorText}
|
||||||
|
{format}
|
||||||
|
quality={currentQuality}
|
||||||
|
{alphaLoss}
|
||||||
|
{stepResults}
|
||||||
|
{aligned}
|
||||||
|
ontogglealign={() => (aligned = !aligned)}
|
||||||
|
onformat={(v) => (format = v)}
|
||||||
|
onquality={setFormatQuality}
|
||||||
|
onupload={handleFile}
|
||||||
|
ontextsource={(textValue) => {
|
||||||
|
textSource = textValue;
|
||||||
|
}}
|
||||||
|
onrendertext={run}
|
||||||
|
oncopytext={copyText}
|
||||||
|
ondownloadtxt={downloadText}
|
||||||
|
ondownload={download}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="aligned">
|
||||||
|
<div class="aligned-row">
|
||||||
|
<div></div>
|
||||||
|
<div class="panel-segment head-segment">
|
||||||
|
<span class="label">{t("resultCard.previewPanel")}</span>
|
||||||
|
<label class="align-toggle">
|
||||||
|
<Toggle bind:checked={aligned} label={t("chain.alignToggle")} />
|
||||||
|
<span>{t("chain.alignToggle")}</span>
|
||||||
|
</label>
|
||||||
|
<SchemaActions
|
||||||
|
{inputMode}
|
||||||
|
{resultKind}
|
||||||
|
canDownload={result !== null}
|
||||||
|
{running}
|
||||||
|
{format}
|
||||||
|
quality={currentQuality}
|
||||||
|
{alphaLoss}
|
||||||
|
onupload={handleFile}
|
||||||
|
ondownload={download}
|
||||||
|
onformat={(v) => (format = v)}
|
||||||
|
onquality={setFormatQuality}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if errorText}
|
||||||
|
<p class="error" role="alert">{errorText}</p>
|
||||||
|
{/if}
|
||||||
{#if canExtend}
|
{#if canExtend}
|
||||||
<AddStepButton onadd={(id) => addStepAt(0, id)} />
|
<div class="aligned-row">
|
||||||
|
<AddStepButton onadd={(id) => addStepAt(0, id)} />
|
||||||
|
<div class="panel-segment connect-segment"></div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each steps as step, i (step.key)}
|
{#each steps as step, i (step.key)}
|
||||||
<StepCard
|
{@const input = i === 0 ? null : stepResults[i - 1]}
|
||||||
index={i + 1}
|
{@const out = stepResults[i]}
|
||||||
title={stepTitle(step.id)}
|
{#if i > 0}
|
||||||
draggable
|
<div class="group-divider">
|
||||||
collapsed={step.collapsed ?? false}
|
<div class="gd-cell"></div>
|
||||||
ontoggle={() => toggleStep(i)}
|
</div>
|
||||||
onremove={steps.length > 1
|
{/if}
|
||||||
? () => removeStepAt(step.key)
|
<div class="aligned-row">
|
||||||
: undefined}
|
{@render stepCard(step, i)}
|
||||||
ondragstart={(e) => {
|
<div
|
||||||
dragFrom = i;
|
class="panel-segment pair-segment"
|
||||||
if (e.dataTransfer) {
|
class:last-segment={i === steps.length - 1 && !canExtend}
|
||||||
e.dataTransfer.effectAllowed = "move";
|
>
|
||||||
e.dataTransfer.setData("text/plain", String(i));
|
{#if i === 0}
|
||||||
}
|
<SchemaSourceTile
|
||||||
}}
|
mode={inputMode}
|
||||||
ondragover={(e) => {
|
{source}
|
||||||
e.preventDefault();
|
{textSource}
|
||||||
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
{running}
|
||||||
}}
|
ontextinput={(v) => (textSource = v)}
|
||||||
ondrop={(e) => onStepDrop(e, i)}
|
onrendertext={run}
|
||||||
ondragend={() => (dragFrom = null)}
|
onupload={handleFile}
|
||||||
>
|
/>
|
||||||
{#snippet tools()}
|
{:else}
|
||||||
<ToolPickerButton
|
<PreviewTile
|
||||||
label={t("chain.changeTool")}
|
label={t("chain.inputLegend")}
|
||||||
iconOnly
|
viewMode="image"
|
||||||
onadd={(id) => replaceStepTool(i, id)}
|
dims={input ? `${input.width} × ${input.height}` : undefined}
|
||||||
/>
|
>
|
||||||
{/snippet}
|
{#if input}
|
||||||
<SchemaFields
|
<img src={toDataUrl(input)} alt="" />
|
||||||
schema={toolSchemaOf(step)}
|
{:else}
|
||||||
values={step.params}
|
<span class="empty">{t("resultCard.noResult")}</span>
|
||||||
toolId={step.id}
|
{/if}
|
||||||
sourceDims={i === 0 ? (source ?? undefined) : stepDims[i - 1]}
|
</PreviewTile>
|
||||||
onchange={(id, v, axis) => setStepValue(i, id, v, axis)}
|
{/if}
|
||||||
onreset={() => resetStep(i)}
|
{#if i === steps.length - 1}
|
||||||
/>
|
<SchemaResultTile
|
||||||
</StepCard>
|
{resultKind}
|
||||||
|
result={out}
|
||||||
|
{resultNote}
|
||||||
|
{fileResult}
|
||||||
|
{textResult}
|
||||||
|
textVars={verdictVars}
|
||||||
|
toolId={lastTool.id}
|
||||||
|
{running}
|
||||||
|
oncopy={copyText}
|
||||||
|
ondownloadtxt={downloadText}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<PreviewTile
|
||||||
|
label={t("chain.stepResult", { n: i + 1 })}
|
||||||
|
viewMode="image"
|
||||||
|
dims={out ? `${out.width} × ${out.height}` : undefined}
|
||||||
|
>
|
||||||
|
{#if out}
|
||||||
|
<img src={toDataUrl(out)} alt="" />
|
||||||
|
{:else}
|
||||||
|
<span class="empty">{t("resultCard.noResult")}</span>
|
||||||
|
{/if}
|
||||||
|
</PreviewTile>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{#if canExtend}
|
{#if canExtend}
|
||||||
<AddStepButton onadd={(id) => addStepAt(i + 1, id)} />
|
<div class="aligned-row">
|
||||||
|
<AddStepButton onadd={(id) => addStepAt(i + 1, id)} />
|
||||||
|
<div
|
||||||
|
class="panel-segment connect-segment"
|
||||||
|
class:add-last={i === steps.length - 1}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</section>
|
</div>
|
||||||
|
{/if}
|
||||||
<section class="panel">
|
|
||||||
<SchemaPreview
|
|
||||||
toolId={lastTool.id}
|
|
||||||
{source}
|
|
||||||
{result}
|
|
||||||
{resultNote}
|
|
||||||
{fileResult}
|
|
||||||
{textSource}
|
|
||||||
{textResult}
|
|
||||||
textVars={verdictVars}
|
|
||||||
{inputMode}
|
|
||||||
{resultKind}
|
|
||||||
{running}
|
|
||||||
error={errorText}
|
|
||||||
{format}
|
|
||||||
quality={currentQuality}
|
|
||||||
{alphaLoss}
|
|
||||||
onformat={(v) => (format = v)}
|
|
||||||
onquality={setFormatQuality}
|
|
||||||
onupload={handleFile}
|
|
||||||
ontextsource={(textValue) => {
|
|
||||||
textSource = textValue;
|
|
||||||
}}
|
|
||||||
onrendertext={run}
|
|
||||||
oncopytext={copyText}
|
|
||||||
ondownloadtxt={downloadText}
|
|
||||||
ondownload={download}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="no-schema">{t("paramsCard.noSchema")}</p>
|
<p class="no-schema">{t("paramsCard.noSchema")}</p>
|
||||||
@@ -599,9 +731,100 @@
|
|||||||
.no-schema {
|
.no-schema {
|
||||||
font: var(--font-size-s) var(--font-mono);
|
font: var(--font-size-s) var(--font-mono);
|
||||||
}
|
}
|
||||||
|
.aligned {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
.aligned-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(var(--size-workspace-min), 1fr) 2fr;
|
||||||
|
gap: var(--space-xxl);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
/* Сегменты справа образуют «одну панель»: общий фон, боковые границы,
|
||||||
|
верх у шапки и низ у последней строки. */
|
||||||
|
.panel-segment {
|
||||||
|
background: var(--color-panel);
|
||||||
|
border-left: var(--size-border) solid var(--color-border);
|
||||||
|
border-right: var(--size-border) solid var(--color-border);
|
||||||
|
padding: var(--space-xl);
|
||||||
|
}
|
||||||
|
.head-segment {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xl);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
border-top: var(--size-border) solid var(--color-border);
|
||||||
|
border-bottom: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: var(--radius-m) var(--radius-m) 0 0;
|
||||||
|
}
|
||||||
|
.head-segment .label {
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
letter-spacing: var(--space-text-l);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-main);
|
||||||
|
}
|
||||||
|
.pair-segment {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--space-l);
|
||||||
|
}
|
||||||
|
/* Коннектор в строках «Add step»: продолжает боковые границы панели. */
|
||||||
|
.connect-segment {
|
||||||
|
align-self: stretch;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.connect-segment.add-last {
|
||||||
|
border-bottom: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: 0 0 var(--radius-m) var(--radius-m);
|
||||||
|
}
|
||||||
|
/* Полоса-разделитель между группами: ячейки с фоном и боковыми границами
|
||||||
|
(как у карточки слева и панели справа), сверху — линия через всю ширину. */
|
||||||
|
.group-divider {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(var(--size-workspace-min), 1fr) 2fr;
|
||||||
|
gap: var(--space-xxl);
|
||||||
|
}
|
||||||
|
.gd-cell {
|
||||||
|
grid-column: 2;
|
||||||
|
background: var(--color-panel);
|
||||||
|
border-left: var(--size-border) solid var(--color-border);
|
||||||
|
border-right: var(--size-border) solid var(--color-border);
|
||||||
|
padding: var(--space-s) 0;
|
||||||
|
}
|
||||||
|
.group-divider::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 auto;
|
||||||
|
border-top: var(--size-border) solid var(--color-border);
|
||||||
|
}
|
||||||
|
.last-segment {
|
||||||
|
border-bottom: var(--size-border) solid var(--color-border);
|
||||||
|
border-radius: 0 0 var(--radius-m) var(--radius-m);
|
||||||
|
}
|
||||||
|
.align-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-m);
|
||||||
|
margin-right: auto;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--color-danger);
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
}
|
||||||
|
.empty {
|
||||||
|
font: var(--font-size-s) var(--font-mono);
|
||||||
|
}
|
||||||
@media (--bp-tablet) {
|
@media (--bp-tablet) {
|
||||||
.workspace {
|
.workspace {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
.aligned-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ export const en: Dict = {
|
|||||||
changeTool: "Change tool",
|
changeTool: "Change tool",
|
||||||
collapseStep: "Collapse step",
|
collapseStep: "Collapse step",
|
||||||
expandStep: "Expand step",
|
expandStep: "Expand step",
|
||||||
|
stepResult: "Result {n}",
|
||||||
|
alignToggle: "Align tools and preview",
|
||||||
inputLegend: "Input",
|
inputLegend: "Input",
|
||||||
resultLegend: "Result",
|
resultLegend: "Result",
|
||||||
paramsLegend: "Parameters",
|
paramsLegend: "Parameters",
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ export const ru: Dict = {
|
|||||||
changeTool: "Поменять инструмент",
|
changeTool: "Поменять инструмент",
|
||||||
collapseStep: "Свернуть шаг",
|
collapseStep: "Свернуть шаг",
|
||||||
expandStep: "Развернуть шаг",
|
expandStep: "Развернуть шаг",
|
||||||
|
stepResult: "Результат {n}",
|
||||||
|
alignToggle: "Выровнять инструменты и просмотр",
|
||||||
inputLegend: "Вход",
|
inputLegend: "Вход",
|
||||||
resultLegend: "Результат",
|
resultLegend: "Результат",
|
||||||
paramsLegend: "Параметры",
|
paramsLegend: "Параметры",
|
||||||
|
|||||||
Reference in New Issue
Block a user