fix(gate-b): Svelte 5 event directives + label/input association for Select Photos
- Add id='photo-file-input' to file input and for='photo-file-input' to label - Convert on:change → onchange, on:click → onclick, on:input → oninput (Svelte 5 requires native DOM event properties, not on: directives) - Add Playwright BVT suite confirming file selection → mask step advance - Test image: 100x60 PNG with green border (hue=66°) and white interior
This commit is contained in:
@@ -453,8 +453,8 @@
|
||||
<h2>Capture Photos</h2>
|
||||
<p class="instruction">Select 1–5 photos of the slab. Supported: JPG, PNG, WEBP.</p>
|
||||
<div class="drop-zone">
|
||||
<input type="file" multiple accept="image/*" on:change={(e) => handleFiles(e.target.files)} class="file-input" />
|
||||
<label class="file-label">
|
||||
<input type="file" id="photo-file-input" multiple accept="image/*" onchange={(e) => handleFiles(e.target.files)} class="file-input" />
|
||||
<label for="photo-file-input" class="file-label">
|
||||
<span class="camera-icon">📷</span>
|
||||
<span>Select Photos</span>
|
||||
</label>
|
||||
@@ -468,7 +468,7 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<button class="btn btn-primary" on:click={() => state.step = 'mask'} disabled={state.photos.length === 0}>
|
||||
<button class="btn btn-primary" onclick={() => state.step = 'mask'} disabled={state.photos.length === 0}>
|
||||
Process Photos →
|
||||
</button>
|
||||
{/if}
|
||||
@@ -484,7 +484,7 @@
|
||||
<div class="knobs-panel">
|
||||
<div class="knob-group">
|
||||
<label>Sheet Mode</label>
|
||||
<select value={getMaskKnobs().sheet} on:change={(e) => {
|
||||
<select value={getMaskKnobs().sheet} onchange={(e) => {
|
||||
const knobs = getMaskKnobs();
|
||||
applyMaskKnobs({ ...knobs, sheet: e.target.value as KnobSettings['sheet'] });
|
||||
maskCanvas && drawMaskOverlay(maskCanvas, currentPhoto()!);
|
||||
@@ -497,7 +497,7 @@
|
||||
<div class="knob-group">
|
||||
<label>Sensitivity: {getMaskKnobs().sensitivity.toFixed(2)}</label>
|
||||
<input type="range" min="0" max="1" step="0.01" value={getMaskKnobs().sensitivity}
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
const knobs = getMaskKnobs();
|
||||
applyMaskKnobs({ ...knobs, sensitivity: parseFloat(e.target.value) });
|
||||
maskCanvas && drawMaskOverlay(maskCanvas, currentPhoto()!);
|
||||
@@ -506,7 +506,7 @@
|
||||
<div class="knob-group">
|
||||
<label>Edge Offset: {getMaskKnobs().edgeOffset}</label>
|
||||
<input type="range" min="-20" max="20" step="1" value={getMaskKnobs().edgeOffset}
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
const knobs = getMaskKnobs();
|
||||
applyMaskKnobs({ ...knobs, edgeOffset: parseInt(e.target.value) });
|
||||
maskCanvas && drawMaskOverlay(maskCanvas, currentPhoto()!);
|
||||
@@ -515,7 +515,7 @@
|
||||
<div class="knob-group">
|
||||
<label>Feather: {getMaskKnobs().feather}</label>
|
||||
<input type="range" min="0" max="10" step="1" value={getMaskKnobs().feather}
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
const knobs = getMaskKnobs();
|
||||
applyMaskKnobs({ ...knobs, feather: parseInt(e.target.value) });
|
||||
maskCanvas && drawMaskOverlay(maskCanvas, currentPhoto()!);
|
||||
@@ -523,7 +523,7 @@
|
||||
</div>
|
||||
<div class="photo-selector">
|
||||
<label>Photo:</label>
|
||||
<select value={state.currentPhotoIndex} on:change={(e) => {
|
||||
<select value={state.currentPhotoIndex} onchange={(e) => {
|
||||
state.currentPhotoIndex = parseInt(e.target.value);
|
||||
maskCanvas && drawMaskOverlay(maskCanvas, currentPhoto()!);
|
||||
}}>
|
||||
@@ -535,8 +535,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-secondary" on:click={() => state.step = 'capture'}>← Back</button>
|
||||
<button class="btn btn-primary" on:click={confirmMaskStep}>Confirm Mask →</button>
|
||||
<button class="btn btn-secondary" onclick={() => state.step = 'capture'}>← Back</button>
|
||||
<button class="btn btn-primary" onclick={confirmMaskStep}>Confirm Mask →</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -551,8 +551,8 @@
|
||||
<div class="knob-group">
|
||||
<label>Rotation</label>
|
||||
<div class="rotation-controls">
|
||||
<button class="btn btn-small" on:click={() => { rotateAxisStep(-90); axisCanvas && drawAxisOverlay(axisCanvas, currentPhoto()!); }}>↺ −90°</button>
|
||||
<button class="btn btn-small" on:click={() => { rotateAxisStep(90); axisCanvas && drawAxisOverlay(axisCanvas, currentPhoto()!); }}>↻ +90°</button>
|
||||
<button class="btn btn-small" onclick={() => { rotateAxisStep(-90); axisCanvas && drawAxisOverlay(axisCanvas, currentPhoto()!); }}>↺ −90°</button>
|
||||
<button class="btn btn-small" onclick={() => { rotateAxisStep(90); axisCanvas && drawAxisOverlay(axisCanvas, currentPhoto()!); }}>↻ +90°</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="knob-group">
|
||||
@@ -561,7 +561,7 @@
|
||||
</div>
|
||||
<div class="photo-selector">
|
||||
<label>Photo:</label>
|
||||
<select value={state.currentPhotoIndex} on:change={(e) => {
|
||||
<select value={state.currentPhotoIndex} onchange={(e) => {
|
||||
state.currentPhotoIndex = parseInt(e.target.value);
|
||||
axisCanvas && drawAxisOverlay(axisCanvas, currentPhoto()!);
|
||||
}}>
|
||||
@@ -573,8 +573,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-secondary" on:click={() => state.step = 'mask'}>← Back</button>
|
||||
<button class="btn btn-primary" on:click={confirmAxisStep}>Confirm Axis →</button>
|
||||
<button class="btn btn-secondary" onclick={() => state.step = 'mask'}>← Back</button>
|
||||
<button class="btn btn-primary" onclick={confirmAxisStep}>Confirm Axis →</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -587,19 +587,19 @@
|
||||
<div class="form-group">
|
||||
<label>Length (inches)</label>
|
||||
<input type="number" step="0.125" min="0.125" value={state.lengthIn}
|
||||
on:input={(e) => updateLengthIn(e.target.value)} />
|
||||
oninput={(e) => updateLengthIn(e.target.value)} />
|
||||
<small>1/8" steps</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Thickness (inches)</label>
|
||||
<input type="number" step="0.125" min="0.125" value={state.thicknessIn}
|
||||
on:input={(e) => updateThicknessIn(e.target.value)} />
|
||||
oninput={(e) => updateThicknessIn(e.target.value)} />
|
||||
<small>1/8" steps</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>SKU</label>
|
||||
<input type="text" value={state.sku}
|
||||
on:input={(e) => updateSku(e.target.value)}
|
||||
oninput={(e) => updateSku(e.target.value)}
|
||||
class:invalid={!state.skuValidation.valid} />
|
||||
<small>{state.skuValidation.valid ? '✓ Valid' : state.skuValidation.errors.join(', ')}</small>
|
||||
</div>
|
||||
@@ -633,8 +633,8 @@
|
||||
<div class="warning">⚠️ Source image is too small for 1600px minimum output. Consider retaking photos.</div>
|
||||
{/if}
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-secondary" on:click={() => state.step = 'axis'}>← Back</button>
|
||||
<button class="btn btn-primary" on:click={continueToReview}
|
||||
<button class="btn btn-secondary" onclick={() => state.step = 'axis'}>← Back</button>
|
||||
<button class="btn btn-primary" onclick={continueToReview}
|
||||
disabled={!isSkuValid(state.sku) || state.lengthIn <= 0 || state.thicknessIn <= 0}>
|
||||
Continue →
|
||||
</button>
|
||||
@@ -652,13 +652,13 @@
|
||||
<h3>✓ Upload Successful</h3>
|
||||
<p>Slab ID: <code>{state.reviewData?.id}</code></p>
|
||||
<p>Server Rev: {state.reviewData?.serverRev}</p>
|
||||
<button class="btn btn-primary" on:click={goToCapture}>Start New Slab</button>
|
||||
<button class="btn btn-primary" onclick={goToCapture}>Start New Slab</button>
|
||||
</div>
|
||||
{:else if state.uploadError}
|
||||
<div class="error">
|
||||
<h3>✗ Upload Failed</h3>
|
||||
<p>{state.uploadError}</p>
|
||||
<button class="btn btn-secondary" on:click={() => state.uploadError = null}>Dismiss</button>
|
||||
<button class="btn btn-secondary" onclick={() => state.uploadError = null}>Dismiss</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="review-grid">
|
||||
@@ -681,8 +681,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-secondary" on:click={() => state.step = 'numbers'}>← Back</button>
|
||||
<button class="btn btn-primary" on:click={uploadDraft} disabled={state.uploading}>
|
||||
<button class="btn btn-secondary" onclick={() => state.step = 'numbers'}>← Back</button>
|
||||
<button class="btn btn-primary" onclick={uploadDraft} disabled={state.uploading}>
|
||||
{state.uploading ? 'Uploading...' : 'Upload Draft'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user