refactor: separate tool page for A\B testing

This commit is contained in:
2026-08-25 15:22:20 +05:00
parent 365af6ef95
commit d70cbad0d2
3 changed files with 181 additions and 81 deletions
+27 -77
View File
@@ -14,14 +14,12 @@
import { executeStep } from '$lib/tools/executor'; import { executeStep } from '$lib/tools/executor';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
import { toolDescription, toolTitle } from '$lib/i18n/tool-strings'; import { toolDescription, toolTitle } from '$lib/i18n/tool-strings';
import ParamsCard from './tool/ParamsCard.svelte'; import type { StageStatus } from './stage/stage-props';
import ResultCard from './tool/ResultCard.svelte'; import ToolStageClassic from './stage/ToolStageClassic.svelte';
import SourceCard from './tool/SourceCard.svelte';
import TextInputCard from './tool/TextInputCard.svelte';
let { tool, restoreChain = false }: { tool: ToolEntry; restoreChain?: boolean } = $props(); let { tool, restoreChain = false }: { tool: ToolEntry; restoreChain?: boolean } = $props();
type Status = 'idle' | 'loaded' | 'processing' | 'error'; type Status = StageStatus;
let status = $state<Status>('idle'); let status = $state<Status>('idle');
let source = $state<PixelImage | null>(null); let source = $state<PixelImage | null>(null);
@@ -267,61 +265,34 @@
<div class="error-banner" role="alert">{errorText}</div> <div class="error-banner" role="alert">{errorText}</div>
{/if} {/if}
<div class="panel tool-block"> <ToolStageClassic
<div class="tool-stage" class:single={isSourceless}>
{#if !isSourceless}
<span class="edge-legend source-legend" aria-hidden="true">{t('toolPage.legendSource')}</span>
<div class="cell">
{#if isTextSource && !source}
<TextInputCard onSubmit={handleTextSubmit} />
{:else}
<SourceCard
{source}
onFile={handleFile}
onError={(e) => (errorText = errorMessage(e))}
onReset={reset}
pipetteActive={!!pipetteTargetId}
onPickColor={handlePickColor}
/>
{/if}
</div>
{/if}
<span class="edge-legend result-legend" aria-hidden="true">
{isInfo ? t('toolPage.legendSummary') : t('toolPage.legendResult')}
</span>
<div class="cell">
<ResultCard
{tool}
sourceLoaded={isSourceless ? true : !!source}
{status}
{result}
displayImage={shownBase}
{info}
{isInfo}
params={sanitized}
textResult={textResult}
onChainToggle={canChainBase ? toggleChain : undefined}
hasChain={chain.length > 0}
onDownloadError={showError}
/>
</div>
</div>
{#if (source || isSourceless) && !isInfo && (tool.params.length > 0 || hasMask)}
<div class="params-sep">
<span class="edge-legend" aria-hidden="true">{t('toolPage.legendParams')}</span>
</div>
<ParamsCard
tool={tool} tool={tool}
params={tool.params} source={source}
bind:values result={result}
{pipetteTargetId} displayImage={shownBase}
onPipetteToggle={handlePipetteToggle} info={info}
status={status}
isInfo={isInfo}
isSourceless={isSourceless}
isTextSource={isTextSource}
textResult={textResult}
sanitized={sanitized}
canChainBase={canChainBase}
hasChain={chain.length > 0}
hasMask={hasMask} hasMask={hasMask}
bind:showMask bind:showMask
bind:values
pipetteTargetId={pipetteTargetId}
handleFile={handleFile}
onTextSubmit={handleTextSubmit}
onSourceError={(e) => (errorText = errorMessage(e))}
reset={reset}
toggleChain={toggleChain}
handlePipetteToggle={handlePipetteToggle}
handlePickColor={handlePickColor}
showError={showError}
errorMessage={errorMessage}
/> />
{/if}
</div>
<div class="chain-stack"> <div class="chain-stack">
{#each chain as step, index (step.id)} {#each chain as step, index (step.id)}
@@ -374,27 +345,6 @@
margin-bottom: var(--space-3); margin-bottom: var(--space-3);
} }
.tool-stage {
position: relative;
}
.source-legend {
left: 25%;
transform: translateX(-50%);
}
.result-legend {
left: 75%;
transform: translateX(-50%);
}
@media (max-width: 48rem) {
.source-legend,
.result-legend {
display: none;
}
}
.empty-slot { .empty-slot {
padding: var(--space-3); padding: var(--space-3);
} }
@@ -0,0 +1,116 @@
<script lang="ts">
import { t } from '$lib/i18n/t';
import ParamsCard from '../tool/ParamsCard.svelte';
import ResultCard from '../tool/ResultCard.svelte';
import SourceCard from '../tool/SourceCard.svelte';
import TextInputCard from '../tool/TextInputCard.svelte';
import type { StageProps } from './stage-props';
let {
tool,
source,
result,
displayImage,
info,
status,
isInfo,
isSourceless,
isTextSource,
textResult,
sanitized,
canChainBase,
hasChain,
hasMask,
showMask = $bindable(false),
values = $bindable(),
pipetteTargetId,
handleFile,
onTextSubmit,
onSourceError,
reset,
toggleChain,
handlePipetteToggle,
handlePickColor,
showError,
errorMessage
}: StageProps = $props();
</script>
<div class="panel tool-block">
<div class="tool-stage" class:single={isSourceless}>
{#if !isSourceless}
<span class="edge-legend source-legend" aria-hidden="true">{t('toolPage.legendSource')}</span>
<div class="cell">
{#if isTextSource && !source}
<TextInputCard onSubmit={onTextSubmit} />
{:else}
<SourceCard
{source}
onFile={handleFile}
onError={onSourceError}
onReset={reset}
pipetteActive={!!pipetteTargetId}
onPickColor={handlePickColor}
/>
{/if}
</div>
{/if}
<span class="edge-legend result-legend" aria-hidden="true">
{isInfo ? t('toolPage.legendSummary') : t('toolPage.legendResult')}
</span>
<div class="cell">
<ResultCard
{tool}
sourceLoaded={isSourceless ? true : !!source}
{status}
{result}
displayImage={displayImage}
{info}
{isInfo}
params={sanitized}
textResult={textResult}
onChainToggle={canChainBase ? toggleChain : undefined}
{hasChain}
onDownloadError={showError}
/>
</div>
</div>
{#if (source || isSourceless) && !isInfo && (tool.params.length > 0 || hasMask)}
<div class="params-sep">
<span class="edge-legend" aria-hidden="true">{t('toolPage.legendParams')}</span>
</div>
<ParamsCard
{tool}
params={tool.params}
bind:values
{pipetteTargetId}
onPipetteToggle={handlePipetteToggle}
{hasMask}
bind:showMask
/>
{/if}
</div>
<style>
.tool-stage {
position: relative;
}
.source-legend {
left: 25%;
transform: translateX(-50%);
}
.result-legend {
left: 75%;
transform: translateX(-50%);
}
@media (max-width: 48rem) {
.source-legend,
.result-legend {
display: none;
}
}
</style>
@@ -0,0 +1,34 @@
import type { ImageInfo } from '$lib/core/analyze';
import type { PixelImage } from '$lib/core/types';
import type { ToolEntry } from '$lib/registry';
export type StageStatus = 'idle' | 'loaded' | 'processing' | 'error';
export interface StageProps {
tool: ToolEntry;
source: PixelImage | null;
result: PixelImage | null;
displayImage: PixelImage | null;
info: ImageInfo | null;
status: StageStatus;
isInfo: boolean;
isSourceless: boolean;
isTextSource: boolean;
textResult: string | null;
sanitized: Record<string, any>;
canChainBase: boolean;
hasChain: boolean;
hasMask: boolean;
showMask: boolean;
values: Record<string, any>;
pipetteTargetId: string | null;
handleFile: (file: File) => Promise<void> | void;
onTextSubmit: (text: string) => Promise<void> | void;
onSourceError: (e: unknown) => void;
reset: () => void;
toggleChain: () => void;
handlePipetteToggle: (id: string) => void;
handlePickColor: (hex: string) => void;
showError: (e: unknown) => void;
errorMessage: (e: unknown) => string;
}