@pixerate/editor-svelte 0.108.0 → 0.109.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EditableTextNodeEditor.svelte +6 -0
- package/dist/canvas/createCanvasPlaybackSurface.svelte.d.ts +8 -2
- package/dist/canvas/createCanvasPlaybackSurface.svelte.js +13 -8
- package/dist/image-editor/AdjustmentLayersPanel.svelte +27 -26
- package/dist/image-editor/AdjustmentLayersPanel.svelte.d.ts +2 -0
- package/dist/image-editor/BrushOptionsBar.svelte +35 -34
- package/dist/image-editor/ColorProfilePanel.svelte +12 -12
- package/dist/image-editor/ControlSlider.svelte +6 -4
- package/dist/image-editor/ControlSlider.svelte.d.ts +2 -0
- package/dist/image-editor/CurvesEditor.svelte +6 -4
- package/dist/image-editor/CurvesEditor.svelte.d.ts +2 -0
- package/dist/image-editor/FreeTransformOptionsBar.svelte +22 -21
- package/dist/image-editor/Histogram.svelte +1 -1
- package/dist/image-editor/ImageEditor.svelte +127 -142
- package/dist/image-editor/ImageEffectControls.svelte +15 -11
- package/dist/image-editor/ImageEffectControls.svelte.d.ts +2 -0
- package/dist/image-editor/ImageProviderEffectsPanel.svelte +15 -15
- package/dist/image-editor/LayerEffectsPanel.svelte +22 -23
- package/dist/image-editor/LayersPanel.svelte +39 -39
- package/dist/image-editor/RefineEdgePanel.svelte +14 -13
- package/dist/image-editor/SelectionOptionsBar.svelte +12 -11
- package/dist/image-editor/ShapeOptionsBar.svelte +14 -13
- package/dist/image-editor/TypeOptionsBar.svelte +12 -11
- package/dist/image-editor/styles.d.ts +51 -0
- package/dist/image-editor/styles.js +51 -0
- package/dist/playback/playback.svelte.d.ts +7 -4
- package/dist/playback/playback.svelte.js +17 -9
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { imageDividerClass, imageEditorFrameClass, imageEditorHeaderClass, imageErrorBannerClass, imageHeaderButtonClass, imagePrimaryButtonClass, imageRangeClass, imageSectionClass, imageSectionTitleClass, imageSelectClass, imageSidebarClass, imageSmallButtonClass, imageSpinnerClass, imageTabClass, imageTabListClass } from './styles.js';
|
|
2
3
|
import type {
|
|
3
4
|
ExportOptions,
|
|
4
5
|
ImageEditorTool,
|
|
@@ -281,13 +282,13 @@
|
|
|
281
282
|
|
|
282
283
|
<div
|
|
283
284
|
bind:this={rootEl}
|
|
284
|
-
class="
|
|
285
|
+
class="{imageEditorFrameClass} {className}"
|
|
285
286
|
{style}
|
|
286
287
|
>
|
|
287
288
|
<!-- Top Action Header -->
|
|
288
|
-
<div class=
|
|
289
|
+
<div class={imageEditorHeaderClass}>
|
|
289
290
|
<!-- Navigation Tabs -->
|
|
290
|
-
<div
|
|
291
|
+
<div role="tablist" class={imageTabListClass}>
|
|
291
292
|
<button
|
|
292
293
|
type="button"
|
|
293
294
|
data-image-tab="crop"
|
|
@@ -295,9 +296,7 @@
|
|
|
295
296
|
activeTab = 'crop';
|
|
296
297
|
editor.setTool('crop');
|
|
297
298
|
}}
|
|
298
|
-
|
|
299
|
-
? 'bg-indigo-600 text-white shadow'
|
|
300
|
-
: 'text-slate-400 hover:text-slate-200'}"
|
|
299
|
+
role="tab" aria-selected={activeTab === 'crop'} class="{imageTabClass(activeTab === 'crop')}"
|
|
301
300
|
>
|
|
302
301
|
Transform & Crop
|
|
303
302
|
</button>
|
|
@@ -308,9 +307,7 @@
|
|
|
308
307
|
activeTab = 'adjust';
|
|
309
308
|
editor.setTool('select');
|
|
310
309
|
}}
|
|
311
|
-
|
|
312
|
-
? 'bg-indigo-600 text-white shadow'
|
|
313
|
-
: 'text-slate-400 hover:text-slate-200'}"
|
|
310
|
+
role="tab" aria-selected={activeTab === 'adjust'} class="{imageTabClass(activeTab === 'adjust')}"
|
|
314
311
|
>
|
|
315
312
|
Adjustments
|
|
316
313
|
</button>
|
|
@@ -321,9 +318,7 @@
|
|
|
321
318
|
activeTab = 'layers';
|
|
322
319
|
editor.setTool('select');
|
|
323
320
|
}}
|
|
324
|
-
|
|
325
|
-
? 'bg-indigo-600 text-white shadow'
|
|
326
|
-
: 'text-slate-400 hover:text-slate-200'}"
|
|
321
|
+
role="tab" aria-selected={activeTab === 'layers'} class="{imageTabClass(activeTab === 'layers')}"
|
|
327
322
|
>
|
|
328
323
|
Layers
|
|
329
324
|
</button>
|
|
@@ -334,9 +329,7 @@
|
|
|
334
329
|
activeTab = 'paint';
|
|
335
330
|
editor.setTool('brush');
|
|
336
331
|
}}
|
|
337
|
-
|
|
338
|
-
? 'bg-indigo-600 text-white shadow'
|
|
339
|
-
: 'text-slate-400 hover:text-slate-200'}"
|
|
332
|
+
role="tab" aria-selected={activeTab === 'paint'} class="{imageTabClass(activeTab === 'paint')}"
|
|
340
333
|
>
|
|
341
334
|
Paint
|
|
342
335
|
</button>
|
|
@@ -347,9 +340,7 @@
|
|
|
347
340
|
activeTab = 'select';
|
|
348
341
|
editor.setTool('select');
|
|
349
342
|
}}
|
|
350
|
-
|
|
351
|
-
? 'bg-indigo-600 text-white shadow'
|
|
352
|
-
: 'text-slate-400 hover:text-slate-200'}"
|
|
343
|
+
role="tab" aria-selected={activeTab === 'select'} class="{imageTabClass(activeTab === 'select')}"
|
|
353
344
|
>
|
|
354
345
|
Select & Mask
|
|
355
346
|
</button>
|
|
@@ -360,9 +351,7 @@
|
|
|
360
351
|
activeTab = 'annotate';
|
|
361
352
|
editor.setTool('pen');
|
|
362
353
|
}}
|
|
363
|
-
|
|
364
|
-
? 'bg-indigo-600 text-white shadow'
|
|
365
|
-
: 'text-slate-400 hover:text-slate-200'}"
|
|
354
|
+
role="tab" aria-selected={activeTab === 'annotate'} class="{imageTabClass(activeTab === 'annotate')}"
|
|
366
355
|
>
|
|
367
356
|
Annotations
|
|
368
357
|
</button>
|
|
@@ -373,9 +362,7 @@
|
|
|
373
362
|
activeTab = 'depth';
|
|
374
363
|
editor.setTool('select');
|
|
375
364
|
}}
|
|
376
|
-
|
|
377
|
-
? 'bg-indigo-600 text-white shadow'
|
|
378
|
-
: 'text-slate-400 hover:text-slate-200'}"
|
|
365
|
+
role="tab" aria-selected={activeTab === 'depth'} class="{imageTabClass(activeTab === 'depth')}"
|
|
379
366
|
>
|
|
380
367
|
Depth Mask
|
|
381
368
|
</button>
|
|
@@ -386,9 +373,7 @@
|
|
|
386
373
|
activeTab = 'ai';
|
|
387
374
|
editor.setTool('select');
|
|
388
375
|
}}
|
|
389
|
-
|
|
390
|
-
? 'bg-indigo-600 text-white shadow'
|
|
391
|
-
: 'text-slate-400 hover:text-slate-200'}"
|
|
376
|
+
role="tab" aria-selected={activeTab === 'ai'} class="{imageTabClass(activeTab === 'ai')} flex items-center gap-1.5"
|
|
392
377
|
>
|
|
393
378
|
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
394
379
|
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>
|
|
@@ -403,7 +388,7 @@
|
|
|
403
388
|
type="button"
|
|
404
389
|
disabled={!editor.canUndo}
|
|
405
390
|
onclick={() => editor.undo()}
|
|
406
|
-
class=
|
|
391
|
+
class={imageHeaderButtonClass}
|
|
407
392
|
title="Undo"
|
|
408
393
|
>
|
|
409
394
|
↩
|
|
@@ -412,7 +397,7 @@
|
|
|
412
397
|
type="button"
|
|
413
398
|
disabled={!editor.canRedo}
|
|
414
399
|
onclick={() => editor.redo()}
|
|
415
|
-
class=
|
|
400
|
+
class={imageHeaderButtonClass}
|
|
416
401
|
title="Redo"
|
|
417
402
|
>
|
|
418
403
|
↪
|
|
@@ -420,33 +405,33 @@
|
|
|
420
405
|
<button
|
|
421
406
|
type="button"
|
|
422
407
|
onclick={() => editor.reset()}
|
|
423
|
-
class="px-2
|
|
408
|
+
class="{imageHeaderButtonClass} px-2 text-muted-foreground"
|
|
424
409
|
>
|
|
425
410
|
Reset
|
|
426
411
|
</button>
|
|
427
412
|
|
|
428
|
-
<div class="h-4 w-px bg-
|
|
413
|
+
<div class="h-4 w-px bg-muted mx-1"></div>
|
|
429
414
|
|
|
430
415
|
<!-- Zoom -->
|
|
431
416
|
<button
|
|
432
417
|
type="button"
|
|
433
418
|
onclick={() => editor.zoomOut()}
|
|
434
|
-
class=
|
|
419
|
+
class={imageHeaderButtonClass}
|
|
435
420
|
title="Zoom Out"
|
|
436
421
|
>
|
|
437
422
|
-
|
|
438
423
|
</button>
|
|
439
|
-
<span class="font-mono text-
|
|
424
|
+
<span class="font-mono text-muted-foreground w-10 text-center">{Math.round(editor.state.zoom * 100)}%</span>
|
|
440
425
|
<button
|
|
441
426
|
type="button"
|
|
442
427
|
onclick={() => editor.zoomIn()}
|
|
443
|
-
class=
|
|
428
|
+
class={imageHeaderButtonClass}
|
|
444
429
|
title="Zoom In"
|
|
445
430
|
>
|
|
446
431
|
+
|
|
447
432
|
</button>
|
|
448
433
|
|
|
449
|
-
<div class="h-4 w-px bg-
|
|
434
|
+
<div class="h-4 w-px bg-muted mx-1"></div>
|
|
450
435
|
|
|
451
436
|
<!-- Live Compare -->
|
|
452
437
|
<button
|
|
@@ -454,7 +439,7 @@
|
|
|
454
439
|
onmousedown={() => editor.setComparing(true)}
|
|
455
440
|
onmouseup={() => editor.setComparing(false)}
|
|
456
441
|
onmouseleave={() => editor.setComparing(false)}
|
|
457
|
-
class="px-2.5 py-1.5 rounded-lg border border-
|
|
442
|
+
class="px-2.5 py-1.5 rounded-lg border border-border bg-background text-amber-600 hover:bg-muted select-none active:bg-amber-500/10"
|
|
458
443
|
>
|
|
459
444
|
Hold Compare
|
|
460
445
|
</button>
|
|
@@ -463,7 +448,7 @@
|
|
|
463
448
|
<button
|
|
464
449
|
type="button"
|
|
465
450
|
onclick={handleExport}
|
|
466
|
-
class="px-4 py-1.5
|
|
451
|
+
class="{imagePrimaryButtonClass} px-4 py-1.5"
|
|
467
452
|
>
|
|
468
453
|
Export Image
|
|
469
454
|
</button>
|
|
@@ -473,9 +458,9 @@
|
|
|
473
458
|
<!-- Main Viewport + Sidebar Layout -->
|
|
474
459
|
<!-- Free Transform options bar (Ctrl/Cmd+T), above the viewport while a transform is open -->
|
|
475
460
|
{#if transformActive}
|
|
476
|
-
<FreeTransformOptionsBar tool={transformTool} class="border-b border-
|
|
461
|
+
<FreeTransformOptionsBar tool={transformTool} class="border-b border-border bg-background/95 px-4 py-2" />
|
|
477
462
|
{:else if transformError}
|
|
478
|
-
<div role="alert" class=
|
|
463
|
+
<div role="alert" class={imageErrorBannerClass}>{transformError}</div>
|
|
479
464
|
{/if}
|
|
480
465
|
|
|
481
466
|
<div class="flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]">
|
|
@@ -498,15 +483,15 @@
|
|
|
498
483
|
{onplaybackreveal}
|
|
499
484
|
chromeRoot={() => rootEl}
|
|
500
485
|
open={openPanel}
|
|
501
|
-
class="flex-1 h-[360px] md:h-full min-h-[320px] bg-
|
|
486
|
+
class="flex-1 h-[360px] md:h-full min-h-[320px] bg-muted"
|
|
502
487
|
/>
|
|
503
488
|
|
|
504
489
|
<!-- Tab Controls Sidebar -->
|
|
505
|
-
<div class=
|
|
490
|
+
<div class={imageSidebarClass}>
|
|
506
491
|
<!-- TAB 1: CROP & TRANSFORM -->
|
|
507
492
|
{#if activeTab === 'crop'}
|
|
508
493
|
<div class="flex flex-col gap-4 text-xs">
|
|
509
|
-
<span class=
|
|
494
|
+
<span class={imageSectionTitleClass}>
|
|
510
495
|
Aspect Ratio Presets
|
|
511
496
|
</span>
|
|
512
497
|
<div class="grid grid-cols-3 gap-2">
|
|
@@ -517,8 +502,8 @@
|
|
|
517
502
|
editor.applyCropPreset(null);
|
|
518
503
|
}}
|
|
519
504
|
class="px-2.5 py-2 rounded-lg border font-medium {activeCropRatio === null && editor.state.crop
|
|
520
|
-
? 'bg-
|
|
521
|
-
: 'bg-
|
|
505
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
506
|
+
: 'bg-muted hover:bg-accent text-foreground border-border'}"
|
|
522
507
|
>
|
|
523
508
|
Freeform
|
|
524
509
|
</button>
|
|
@@ -529,8 +514,8 @@
|
|
|
529
514
|
editor.applyCropPreset(1);
|
|
530
515
|
}}
|
|
531
516
|
class="px-2.5 py-2 rounded-lg border font-medium {activeCropRatio === 1
|
|
532
|
-
? 'bg-
|
|
533
|
-
: 'bg-
|
|
517
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
518
|
+
: 'bg-muted hover:bg-accent text-foreground border-border'}"
|
|
534
519
|
>
|
|
535
520
|
1:1 Square
|
|
536
521
|
</button>
|
|
@@ -541,8 +526,8 @@
|
|
|
541
526
|
editor.applyCropPreset(4 / 3);
|
|
542
527
|
}}
|
|
543
528
|
class="px-2.5 py-2 rounded-lg border font-medium {activeCropRatio === 4 / 3
|
|
544
|
-
? 'bg-
|
|
545
|
-
: 'bg-
|
|
529
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
530
|
+
: 'bg-muted hover:bg-accent text-foreground border-border'}"
|
|
546
531
|
>
|
|
547
532
|
4:3 Standard
|
|
548
533
|
</button>
|
|
@@ -553,8 +538,8 @@
|
|
|
553
538
|
editor.applyCropPreset(16 / 9);
|
|
554
539
|
}}
|
|
555
540
|
class="px-2.5 py-2 rounded-lg border font-medium {activeCropRatio === 16 / 9
|
|
556
|
-
? 'bg-
|
|
557
|
-
: 'bg-
|
|
541
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
542
|
+
: 'bg-muted hover:bg-accent text-foreground border-border'}"
|
|
558
543
|
>
|
|
559
544
|
16:9 Wide
|
|
560
545
|
</button>
|
|
@@ -565,8 +550,8 @@
|
|
|
565
550
|
editor.applyCropPreset(3 / 2);
|
|
566
551
|
}}
|
|
567
552
|
class="px-2.5 py-2 rounded-lg border font-medium {activeCropRatio === 3 / 2
|
|
568
|
-
? 'bg-
|
|
569
|
-
: 'bg-
|
|
553
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
554
|
+
: 'bg-muted hover:bg-accent text-foreground border-border'}"
|
|
570
555
|
>
|
|
571
556
|
3:2 Photo
|
|
572
557
|
</button>
|
|
@@ -576,29 +561,29 @@
|
|
|
576
561
|
activeCropRatio = null;
|
|
577
562
|
editor.resetCrop();
|
|
578
563
|
}}
|
|
579
|
-
class="px-2.5 py-2 rounded-lg bg-
|
|
564
|
+
class="px-2.5 py-2 rounded-lg bg-destructive/10 hover:bg-destructive/20 text-destructive border border-destructive/40 font-medium"
|
|
580
565
|
>
|
|
581
566
|
Clear Crop
|
|
582
567
|
</button>
|
|
583
568
|
</div>
|
|
584
569
|
|
|
585
|
-
<div class=
|
|
570
|
+
<div class={imageDividerClass}></div>
|
|
586
571
|
|
|
587
|
-
<span class=
|
|
572
|
+
<span class={imageSectionTitleClass}>
|
|
588
573
|
Orientation & Flips
|
|
589
574
|
</span>
|
|
590
575
|
<div class="grid grid-cols-2 gap-2">
|
|
591
576
|
<button
|
|
592
577
|
type="button"
|
|
593
578
|
onclick={() => editor.rotate(-90)}
|
|
594
|
-
class="px-3 py-2 rounded-lg bg-
|
|
579
|
+
class="px-3 py-2 rounded-lg bg-muted hover:bg-accent text-foreground border border-border"
|
|
595
580
|
>
|
|
596
581
|
Rotate -90°
|
|
597
582
|
</button>
|
|
598
583
|
<button
|
|
599
584
|
type="button"
|
|
600
585
|
onclick={() => editor.rotate(90)}
|
|
601
|
-
class="px-3 py-2 rounded-lg bg-
|
|
586
|
+
class="px-3 py-2 rounded-lg bg-muted hover:bg-accent text-foreground border border-border"
|
|
602
587
|
>
|
|
603
588
|
Rotate +90°
|
|
604
589
|
</button>
|
|
@@ -606,8 +591,8 @@
|
|
|
606
591
|
type="button"
|
|
607
592
|
onclick={() => editor.flipHorizontal()}
|
|
608
593
|
class="px-3 py-2 rounded-lg border transition-colors {editor.state.transform.flipH
|
|
609
|
-
? 'bg-
|
|
610
|
-
: 'bg-
|
|
594
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
595
|
+
: 'bg-muted border-border text-foreground hover:bg-accent'}"
|
|
611
596
|
>
|
|
612
597
|
Flip Horizontal
|
|
613
598
|
</button>
|
|
@@ -615,8 +600,8 @@
|
|
|
615
600
|
type="button"
|
|
616
601
|
onclick={() => editor.flipVertical()}
|
|
617
602
|
class="px-3 py-2 rounded-lg border transition-colors {editor.state.transform.flipV
|
|
618
|
-
? 'bg-
|
|
619
|
-
: 'bg-
|
|
603
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
604
|
+
: 'bg-muted border-border text-foreground hover:bg-accent'}"
|
|
620
605
|
>
|
|
621
606
|
Flip Vertical
|
|
622
607
|
</button>
|
|
@@ -628,13 +613,13 @@
|
|
|
628
613
|
{#if activeTab === 'adjust'}
|
|
629
614
|
<div class="flex flex-col gap-3 text-xs">
|
|
630
615
|
<div class="flex items-center justify-between">
|
|
631
|
-
<span class=
|
|
616
|
+
<span class={imageSectionTitleClass}>
|
|
632
617
|
Color Adjustments
|
|
633
618
|
</span>
|
|
634
619
|
<button
|
|
635
620
|
type="button"
|
|
636
621
|
onclick={() => editor.resetAdjustments()}
|
|
637
|
-
class="text-[10px] text-
|
|
622
|
+
class="text-[10px] text-destructive hover:underline"
|
|
638
623
|
>
|
|
639
624
|
Reset Sliders
|
|
640
625
|
</button>
|
|
@@ -651,7 +636,7 @@
|
|
|
651
636
|
/>
|
|
652
637
|
{#if spec.id === 'tint' && legacyProcess}
|
|
653
638
|
<div
|
|
654
|
-
class="flex items-center justify-between gap-2 rounded border border-amber-500/30 bg-amber-500/10 px-2 py-1.5 text-[10px] text-amber-
|
|
639
|
+
class="flex items-center justify-between gap-2 rounded border border-amber-500/30 bg-amber-500/10 px-2 py-1.5 text-[10px] text-amber-600"
|
|
655
640
|
data-testid="image-editor-process-notice"
|
|
656
641
|
>
|
|
657
642
|
<span>Legacy rendering (process 1). Tint needs the current process.</span>
|
|
@@ -666,7 +651,7 @@
|
|
|
666
651
|
{/if}
|
|
667
652
|
{/each}
|
|
668
653
|
|
|
669
|
-
<div class=
|
|
654
|
+
<div class={imageSectionClass}>
|
|
670
655
|
<AdjustmentLayersPanel
|
|
671
656
|
controller={editor.controller}
|
|
672
657
|
layers={editor.state.adjustments.layers}
|
|
@@ -679,34 +664,34 @@
|
|
|
679
664
|
<!-- TAB 3: ANNOTATIONS -->
|
|
680
665
|
{#if activeTab === 'layers'}
|
|
681
666
|
<div class="flex flex-col gap-3 text-xs">
|
|
682
|
-
<span class=
|
|
667
|
+
<span class={imageSectionTitleClass}>Layers</span>
|
|
683
668
|
<button
|
|
684
669
|
type="button"
|
|
685
670
|
title="Edit › Free Transform (Ctrl/Cmd+T): the selected pixels, or the whole active layer"
|
|
686
|
-
class=
|
|
671
|
+
class={imageSmallButtonClass}
|
|
687
672
|
disabled={transformActive}
|
|
688
673
|
onclick={() => transformTool.begin()}>Free Transform</button>
|
|
689
674
|
<LayersPanel controller={editor.controller} />
|
|
690
|
-
{#if effectsShown}<ImageProviderEffectsPanel controller={editor.controller} class=
|
|
691
|
-
<span class=
|
|
675
|
+
{#if effectsShown}<ImageProviderEffectsPanel controller={editor.controller} class={imageSectionClass} />{/if}
|
|
676
|
+
<span class={imageSectionTitleClass}>Colour</span>
|
|
692
677
|
<ColorProfilePanel controller={editor.controller} />
|
|
693
678
|
</div>
|
|
694
679
|
{/if}
|
|
695
680
|
|
|
696
681
|
{#if activeTab === 'paint'}
|
|
697
682
|
<div class="flex flex-col gap-3 text-xs">
|
|
698
|
-
<span class=
|
|
683
|
+
<span class={imageSectionTitleClass}>Paint</span>
|
|
699
684
|
<BrushOptionsBar controller={editor.controller} />
|
|
700
685
|
<button
|
|
701
686
|
type="button"
|
|
702
687
|
title="Edit › Free Transform (Ctrl/Cmd+T): the selected pixels, or the whole active layer"
|
|
703
|
-
class=
|
|
688
|
+
class={imageSmallButtonClass}
|
|
704
689
|
disabled={transformActive}
|
|
705
690
|
onclick={() => transformTool.begin()}>Free Transform</button>
|
|
706
691
|
{#if paintError}
|
|
707
|
-
<p role="alert" class="text-
|
|
692
|
+
<p role="alert" class="text-destructive" data-testid="paint-error">{paintError}</p>
|
|
708
693
|
{/if}
|
|
709
|
-
<div class=
|
|
694
|
+
<div class={imageSectionClass}>
|
|
710
695
|
<LayersPanel controller={editor.controller} />
|
|
711
696
|
</div>
|
|
712
697
|
</div>
|
|
@@ -714,7 +699,7 @@
|
|
|
714
699
|
|
|
715
700
|
{#if activeTab === 'annotate'}
|
|
716
701
|
<div class="flex flex-col gap-3 text-xs">
|
|
717
|
-
<span class=
|
|
702
|
+
<span class={imageSectionTitleClass}>Shape & Type Layers</span>
|
|
718
703
|
<div class="grid grid-cols-2 gap-1.5">
|
|
719
704
|
{#each ['shape', 'type'] as const as t (t)}
|
|
720
705
|
<button
|
|
@@ -722,8 +707,8 @@
|
|
|
722
707
|
aria-pressed={editor.state.activeTool === t}
|
|
723
708
|
onclick={() => editor.setTool(t)}
|
|
724
709
|
class="py-1.5 px-2 rounded-lg font-medium border transition-colors {editor.state.activeTool === t
|
|
725
|
-
? 'bg-
|
|
726
|
-
: 'bg-
|
|
710
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
711
|
+
: 'bg-muted border-border text-foreground hover:bg-accent'}">{t === 'shape' ? 'Shape' : 'Type'}</button>
|
|
727
712
|
{/each}
|
|
728
713
|
</div>
|
|
729
714
|
{#if editor.state.activeTool === 'shape'}
|
|
@@ -732,15 +717,15 @@
|
|
|
732
717
|
{#if editor.state.activeTool === 'type'}
|
|
733
718
|
<TypeOptionsBar controller={editor.controller} options={typeOptions} onchange={(o) => (typeOptions = o)} session={typeSession} />
|
|
734
719
|
{#if typeSession}
|
|
735
|
-
<button type="button" onclick={finishTyping} class="self-start rounded bg-
|
|
720
|
+
<button type="button" onclick={finishTyping} class="self-start rounded bg-muted px-2 py-1 text-[11px] text-foreground hover:bg-accent">Done editing text</button>
|
|
736
721
|
{/if}
|
|
737
722
|
{/if}
|
|
738
723
|
{#if shapeTypeError}
|
|
739
|
-
<p role="alert" class="text-
|
|
724
|
+
<p role="alert" class="text-destructive" data-testid="shape-type-error">{shapeTypeError}</p>
|
|
740
725
|
{/if}
|
|
741
|
-
<div class=
|
|
726
|
+
<div class={imageDividerClass}></div>
|
|
742
727
|
|
|
743
|
-
<span class=
|
|
728
|
+
<span class={imageSectionTitleClass}>
|
|
744
729
|
Draw & Annotate
|
|
745
730
|
</span>
|
|
746
731
|
|
|
@@ -751,8 +736,8 @@
|
|
|
751
736
|
type="button"
|
|
752
737
|
onclick={() => editor.setTool(t)}
|
|
753
738
|
class="py-1.5 px-2 rounded-lg font-medium capitalize border transition-colors {editor.state.activeTool === t
|
|
754
|
-
? 'bg-
|
|
755
|
-
: 'bg-
|
|
739
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
740
|
+
: 'bg-muted border-border text-foreground hover:bg-accent'}"
|
|
756
741
|
>
|
|
757
742
|
{t}
|
|
758
743
|
</button>
|
|
@@ -761,14 +746,14 @@
|
|
|
761
746
|
|
|
762
747
|
<!-- Color & Size -->
|
|
763
748
|
<div class="flex items-center gap-2 mt-2">
|
|
764
|
-
<span class="text-
|
|
749
|
+
<span class="text-muted-foreground">Color:</span>
|
|
765
750
|
<input
|
|
766
751
|
type="color"
|
|
767
752
|
bind:value={activeColor}
|
|
768
|
-
class="w-8 h-8 rounded border border-
|
|
753
|
+
class="w-8 h-8 rounded border border-border cursor-pointer bg-transparent"
|
|
769
754
|
/>
|
|
770
755
|
<div class="flex-1 ml-2">
|
|
771
|
-
<div class="flex justify-between text-
|
|
756
|
+
<div class="flex justify-between text-muted-foreground mb-0.5">
|
|
772
757
|
<span>Width:</span>
|
|
773
758
|
<span class="font-mono">{strokeWidth}px</span>
|
|
774
759
|
</div>
|
|
@@ -777,32 +762,32 @@
|
|
|
777
762
|
min="1"
|
|
778
763
|
max="20"
|
|
779
764
|
bind:value={strokeWidth}
|
|
780
|
-
class=
|
|
765
|
+
class={imageRangeClass}
|
|
781
766
|
/>
|
|
782
767
|
</div>
|
|
783
768
|
</div>
|
|
784
769
|
|
|
785
|
-
<div class=
|
|
770
|
+
<div class={imageDividerClass}></div>
|
|
786
771
|
|
|
787
772
|
<!-- Text Tool -->
|
|
788
|
-
<span class=
|
|
773
|
+
<span class={imageSectionTitleClass}>Add Text</span>
|
|
789
774
|
<div class="flex gap-2">
|
|
790
775
|
<input
|
|
791
776
|
type="text"
|
|
792
777
|
placeholder="Type watermark or label..."
|
|
793
778
|
bind:value={textInput}
|
|
794
779
|
onkeydown={(e) => e.key === 'Enter' && handleAddText()}
|
|
795
|
-
class="flex-1 bg-
|
|
780
|
+
class="flex-1 bg-background border border-border rounded-lg px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary"
|
|
796
781
|
/>
|
|
797
782
|
<button
|
|
798
783
|
type="button"
|
|
799
784
|
onclick={handleAddText}
|
|
800
|
-
class="px-3 py-1.5 rounded-lg bg-
|
|
785
|
+
class="px-3 py-1.5 rounded-lg bg-primary hover:bg-primary/90 text-primary-foreground font-medium shadow"
|
|
801
786
|
>
|
|
802
787
|
Add
|
|
803
788
|
</button>
|
|
804
789
|
</div>
|
|
805
|
-
<div class="flex items-center justify-between text-
|
|
790
|
+
<div class="flex items-center justify-between text-muted-foreground">
|
|
806
791
|
<span>Font Size:</span>
|
|
807
792
|
<span class="font-mono">{fontSize}px</span>
|
|
808
793
|
</div>
|
|
@@ -811,18 +796,18 @@
|
|
|
811
796
|
min="12"
|
|
812
797
|
max="72"
|
|
813
798
|
bind:value={fontSize}
|
|
814
|
-
class=
|
|
799
|
+
class={imageRangeClass}
|
|
815
800
|
/>
|
|
816
801
|
|
|
817
802
|
<!-- Layers & Selected Annotation -->
|
|
818
|
-
<div class=
|
|
803
|
+
<div class={imageDividerClass}></div>
|
|
819
804
|
<div class="flex items-center justify-between">
|
|
820
|
-
<span class="text-
|
|
805
|
+
<span class="text-muted-foreground">Layers ({editor.state.annotations.length})</span>
|
|
821
806
|
{#if editor.state.selectedAnnotationId}
|
|
822
807
|
<button
|
|
823
808
|
type="button"
|
|
824
809
|
onclick={() => editor.removeAnnotation(editor.state.selectedAnnotationId!)}
|
|
825
|
-
class="text-
|
|
810
|
+
class="text-destructive hover:underline text-[11px]"
|
|
826
811
|
>
|
|
827
812
|
Delete Selected
|
|
828
813
|
</button>
|
|
@@ -831,7 +816,7 @@
|
|
|
831
816
|
<button
|
|
832
817
|
type="button"
|
|
833
818
|
onclick={() => editor.clearAnnotations()}
|
|
834
|
-
class="text-
|
|
819
|
+
class="text-muted-foreground hover:text-foreground text-[11px]"
|
|
835
820
|
>
|
|
836
821
|
Clear All
|
|
837
822
|
</button>
|
|
@@ -844,37 +829,37 @@
|
|
|
844
829
|
{#if activeTab === 'depth'}
|
|
845
830
|
<div class="flex flex-col gap-3 text-xs">
|
|
846
831
|
<div class="flex items-center justify-between">
|
|
847
|
-
<span class=
|
|
832
|
+
<span class={imageSectionTitleClass}>
|
|
848
833
|
Depth Masking
|
|
849
834
|
</span>
|
|
850
835
|
<button
|
|
851
836
|
type="button"
|
|
852
837
|
onclick={() => editor.resetDepthMask()}
|
|
853
|
-
class="text-[10px] text-
|
|
838
|
+
class="text-[10px] text-destructive hover:underline"
|
|
854
839
|
>
|
|
855
840
|
Reset Mask
|
|
856
841
|
</button>
|
|
857
842
|
</div>
|
|
858
843
|
|
|
859
|
-
<label class="flex items-center gap-2 cursor-pointer text-
|
|
844
|
+
<label class="flex items-center gap-2 cursor-pointer text-foreground">
|
|
860
845
|
<input
|
|
861
846
|
type="checkbox"
|
|
862
847
|
checked={editor.state.depthMask.enabled}
|
|
863
848
|
onchange={(e) => editor.setDepthMask({ enabled: (e.target as HTMLInputElement).checked })}
|
|
864
|
-
class="rounded accent-
|
|
849
|
+
class="rounded accent-primary"
|
|
865
850
|
/>
|
|
866
851
|
<span>Enable Depth / Alpha Mask</span>
|
|
867
852
|
</label>
|
|
868
853
|
|
|
869
854
|
{#if editor.state.depthMask.enabled}
|
|
870
855
|
<div>
|
|
871
|
-
<span class="text-
|
|
856
|
+
<span class="text-muted-foreground block mb-1">Mask Image URL:</span>
|
|
872
857
|
<input
|
|
873
858
|
type="text"
|
|
874
859
|
placeholder="https://.../depthmap.png"
|
|
875
860
|
value={editor.state.depthMask.maskSource || ''}
|
|
876
861
|
oninput={(e) => editor.setDepthMask({ maskSource: (e.target as HTMLInputElement).value || null })}
|
|
877
|
-
class="w-full bg-
|
|
862
|
+
class="w-full bg-background border border-border rounded-lg px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground"
|
|
878
863
|
/>
|
|
879
864
|
</div>
|
|
880
865
|
|
|
@@ -883,8 +868,8 @@
|
|
|
883
868
|
type="button"
|
|
884
869
|
onclick={() => editor.setDepthMask({ mode: 'threshold' })}
|
|
885
870
|
class="flex-1 py-1.5 rounded-lg border text-xs font-medium {editor.state.depthMask.mode === 'threshold'
|
|
886
|
-
? 'bg-
|
|
887
|
-
: 'bg-
|
|
871
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
872
|
+
: 'bg-muted border-border text-foreground'}"
|
|
888
873
|
>
|
|
889
874
|
Threshold Mode
|
|
890
875
|
</button>
|
|
@@ -892,15 +877,15 @@
|
|
|
892
877
|
type="button"
|
|
893
878
|
onclick={() => editor.setDepthMask({ mode: 'brightness' })}
|
|
894
879
|
class="flex-1 py-1.5 rounded-lg border text-xs font-medium {editor.state.depthMask.mode === 'brightness'
|
|
895
|
-
? 'bg-
|
|
896
|
-
: 'bg-
|
|
880
|
+
? 'bg-primary border-primary text-primary-foreground'
|
|
881
|
+
: 'bg-muted border-border text-foreground'}"
|
|
897
882
|
>
|
|
898
883
|
Brightness Mode
|
|
899
884
|
</button>
|
|
900
885
|
</div>
|
|
901
886
|
|
|
902
887
|
<div>
|
|
903
|
-
<div class="flex justify-between text-
|
|
888
|
+
<div class="flex justify-between text-muted-foreground mb-1">
|
|
904
889
|
<span>Depth Start (Lower):</span>
|
|
905
890
|
<span class="font-mono">{editor.state.depthMask.depthRange[0]}</span>
|
|
906
891
|
</div>
|
|
@@ -913,12 +898,12 @@
|
|
|
913
898
|
editor.setDepthMask({
|
|
914
899
|
depthRange: [Number((e.target as HTMLInputElement).value), editor.state.depthMask.depthRange[1]],
|
|
915
900
|
})}
|
|
916
|
-
class=
|
|
901
|
+
class={imageRangeClass}
|
|
917
902
|
/>
|
|
918
903
|
</div>
|
|
919
904
|
|
|
920
905
|
<div>
|
|
921
|
-
<div class="flex justify-between text-
|
|
906
|
+
<div class="flex justify-between text-muted-foreground mb-1">
|
|
922
907
|
<span>Depth Stop (Upper):</span>
|
|
923
908
|
<span class="font-mono">{editor.state.depthMask.depthRange[1]}</span>
|
|
924
909
|
</div>
|
|
@@ -931,12 +916,12 @@
|
|
|
931
916
|
editor.setDepthMask({
|
|
932
917
|
depthRange: [editor.state.depthMask.depthRange[0], Number((e.target as HTMLInputElement).value)],
|
|
933
918
|
})}
|
|
934
|
-
class=
|
|
919
|
+
class={imageRangeClass}
|
|
935
920
|
/>
|
|
936
921
|
</div>
|
|
937
922
|
|
|
938
923
|
<div>
|
|
939
|
-
<div class="flex justify-between text-
|
|
924
|
+
<div class="flex justify-between text-muted-foreground mb-1">
|
|
940
925
|
<span>Softness:</span>
|
|
941
926
|
<span class="font-mono">{editor.state.depthMask.softness}</span>
|
|
942
927
|
</div>
|
|
@@ -946,16 +931,16 @@
|
|
|
946
931
|
max="50"
|
|
947
932
|
value={editor.state.depthMask.softness}
|
|
948
933
|
oninput={(e) => editor.setDepthMask({ softness: Number((e.target as HTMLInputElement).value) })}
|
|
949
|
-
class=
|
|
934
|
+
class={imageRangeClass}
|
|
950
935
|
/>
|
|
951
936
|
</div>
|
|
952
937
|
|
|
953
|
-
<label class="flex items-center gap-2 cursor-pointer text-
|
|
938
|
+
<label class="flex items-center gap-2 cursor-pointer text-foreground mt-1">
|
|
954
939
|
<input
|
|
955
940
|
type="checkbox"
|
|
956
941
|
checked={editor.state.depthMask.invert}
|
|
957
942
|
onchange={(e) => editor.setDepthMask({ invert: (e.target as HTMLInputElement).checked })}
|
|
958
|
-
class="rounded accent-
|
|
943
|
+
class="rounded accent-primary"
|
|
959
944
|
/>
|
|
960
945
|
<span>Invert Mask</span>
|
|
961
946
|
</label>
|
|
@@ -974,19 +959,19 @@
|
|
|
974
959
|
quickMask={Boolean(editor.state.quickMask)}
|
|
975
960
|
/>
|
|
976
961
|
{#if selectionError}
|
|
977
|
-
<p role="alert" class="text-
|
|
962
|
+
<p role="alert" class="text-destructive">{selectionError}</p>
|
|
978
963
|
{/if}
|
|
979
964
|
<div class="flex gap-1">
|
|
980
965
|
<button
|
|
981
966
|
type="button"
|
|
982
967
|
title="Select › Transform Selection: the outline only"
|
|
983
|
-
class=
|
|
968
|
+
class={imageSmallButtonClass}
|
|
984
969
|
disabled={transformActive}
|
|
985
970
|
onclick={() => transformTool.begin({ content: 'selection-outline' })}>Transform Selection</button>
|
|
986
971
|
<button
|
|
987
972
|
type="button"
|
|
988
973
|
title="Edit › Free Transform: the selected pixels of the active layer"
|
|
989
|
-
class=
|
|
974
|
+
class={imageSmallButtonClass}
|
|
990
975
|
disabled={transformActive}
|
|
991
976
|
onclick={() => transformTool.begin({ content: 'selection' })}>Free Transform Pixels</button>
|
|
992
977
|
</div>
|
|
@@ -1001,12 +986,12 @@
|
|
|
1001
986
|
<!-- TAB 5: AI TOOLS / INPAINTING & BACKGROUND REMOVAL -->
|
|
1002
987
|
{#if activeTab === 'ai'}
|
|
1003
988
|
<div class="flex flex-col gap-4 text-xs">
|
|
1004
|
-
<label class="flex items-center justify-between gap-2 text-
|
|
989
|
+
<label class="flex items-center justify-between gap-2 text-foreground">
|
|
1005
990
|
<span>Results go to</span>
|
|
1006
991
|
<select
|
|
1007
992
|
aria-label="AI result target"
|
|
1008
993
|
bind:value={aiTarget}
|
|
1009
|
-
class=
|
|
994
|
+
class={imageSelectClass}
|
|
1010
995
|
>
|
|
1011
996
|
<option value="auto">{editor.controller.isMultiLayer ? 'Auto (new layer)' : 'Auto (replace image)'}</option>
|
|
1012
997
|
<option value="new-layer">New layer / mask</option>
|
|
@@ -1014,22 +999,22 @@
|
|
|
1014
999
|
</select>
|
|
1015
1000
|
</label>
|
|
1016
1001
|
<!-- Background Removal Section -->
|
|
1017
|
-
<div class="p-3 rounded-lg bg-
|
|
1002
|
+
<div class="p-3 rounded-lg bg-muted/40 border border-border flex flex-col gap-2.5">
|
|
1018
1003
|
<div class="flex items-center justify-between">
|
|
1019
|
-
<span class="font-semibold text-
|
|
1020
|
-
<svg class="w-3.5 h-3.5 text-
|
|
1004
|
+
<span class="font-semibold text-foreground uppercase tracking-wider text-[11px] flex items-center gap-1.5">
|
|
1005
|
+
<svg class="w-3.5 h-3.5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1021
1006
|
<path d="M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
|
1022
1007
|
</svg>
|
|
1023
1008
|
Background Removal
|
|
1024
1009
|
</span>
|
|
1025
1010
|
{#if isRemovingBg}
|
|
1026
|
-
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-
|
|
1011
|
+
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-primary/15 text-primary animate-pulse">
|
|
1027
1012
|
Removing...
|
|
1028
1013
|
</span>
|
|
1029
1014
|
{/if}
|
|
1030
1015
|
</div>
|
|
1031
1016
|
|
|
1032
|
-
<p class="text-
|
|
1017
|
+
<p class="text-muted-foreground text-[11px] leading-relaxed">
|
|
1033
1018
|
Isolate foreground subject with AI alpha transparency segmentation.
|
|
1034
1019
|
</p>
|
|
1035
1020
|
|
|
@@ -1037,10 +1022,10 @@
|
|
|
1037
1022
|
type="button"
|
|
1038
1023
|
onclick={handleRemoveBackground}
|
|
1039
1024
|
disabled={isRemovingBg || isInpainting}
|
|
1040
|
-
class="mt-1 px-3 py-2 rounded-lg bg-
|
|
1025
|
+
class="mt-1 px-3 py-2 rounded-lg bg-primary hover:bg-primary/90 disabled:opacity-50 text-primary-foreground font-medium shadow transition-colors flex items-center justify-center gap-2"
|
|
1041
1026
|
>
|
|
1042
1027
|
{#if isRemovingBg}
|
|
1043
|
-
<div class=
|
|
1028
|
+
<div class={imageSpinnerClass}></div>
|
|
1044
1029
|
Removing Background...
|
|
1045
1030
|
{:else}
|
|
1046
1031
|
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -1052,46 +1037,46 @@
|
|
|
1052
1037
|
</div>
|
|
1053
1038
|
|
|
1054
1039
|
<!-- AI Inpainting / Generative Fill Section -->
|
|
1055
|
-
<div class="p-3 rounded-lg bg-
|
|
1040
|
+
<div class="p-3 rounded-lg bg-muted/40 border border-border flex flex-col gap-2.5">
|
|
1056
1041
|
<div class="flex items-center justify-between">
|
|
1057
|
-
<span class="font-semibold text-
|
|
1058
|
-
<svg class="w-3.5 h-3.5 text-
|
|
1042
|
+
<span class="font-semibold text-foreground uppercase tracking-wider text-[11px] flex items-center gap-1.5">
|
|
1043
|
+
<svg class="w-3.5 h-3.5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1059
1044
|
<path d="m21.64 3.64-1.28-1.28a1.2 1.2 0 0 0-1.7 0l-9.72 9.72-1.94 4.86 4.86-1.94 9.72-9.72a1.2 1.2 0 0 0 .06-1.64z"/>
|
|
1060
1045
|
<path d="m14 7 3 3"/>
|
|
1061
1046
|
</svg>
|
|
1062
1047
|
Generative Inpainting
|
|
1063
1048
|
</span>
|
|
1064
1049
|
{#if isInpainting}
|
|
1065
|
-
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-
|
|
1050
|
+
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-primary/15 text-primary animate-pulse">
|
|
1066
1051
|
Inpainting...
|
|
1067
1052
|
</span>
|
|
1068
1053
|
{/if}
|
|
1069
1054
|
</div>
|
|
1070
1055
|
|
|
1071
|
-
<p class="text-
|
|
1056
|
+
<p class="text-muted-foreground text-[11px] leading-relaxed">
|
|
1072
1057
|
Replace, modify, or fill selected regions using prompt-guided generative inpainting.
|
|
1073
1058
|
</p>
|
|
1074
1059
|
|
|
1075
1060
|
<div class="flex flex-col gap-1">
|
|
1076
|
-
<label for="ai-inpaint-prompt" class="text-
|
|
1061
|
+
<label for="ai-inpaint-prompt" class="text-foreground text-[11px] font-medium">Prompt</label>
|
|
1077
1062
|
<textarea
|
|
1078
1063
|
id="ai-inpaint-prompt"
|
|
1079
1064
|
bind:value={inpaintPrompt}
|
|
1080
1065
|
placeholder="e.g. replace sunglasses with steampunk goggles, remove person in background..."
|
|
1081
1066
|
rows="3"
|
|
1082
|
-
class="w-full bg-
|
|
1067
|
+
class="w-full bg-background border border-border rounded-lg p-2 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary resize-none"
|
|
1083
1068
|
></textarea>
|
|
1084
1069
|
</div>
|
|
1085
1070
|
|
|
1086
1071
|
<div class="flex flex-col gap-1">
|
|
1087
|
-
<span class="text-
|
|
1072
|
+
<span class="text-foreground text-[11px] font-medium">Target Mask Region</span>
|
|
1088
1073
|
<div class="grid grid-cols-2 gap-1.5">
|
|
1089
1074
|
<button
|
|
1090
1075
|
type="button"
|
|
1091
1076
|
onclick={() => inpaintMaskMode = 'annotations'}
|
|
1092
1077
|
class="px-2 py-1.5 rounded border text-[11px] font-medium text-left transition-colors {inpaintMaskMode === 'annotations'
|
|
1093
|
-
? 'bg-
|
|
1094
|
-
: 'bg-
|
|
1078
|
+
? 'bg-primary/30 border-primary text-primary'
|
|
1079
|
+
: 'bg-background border-border text-muted-foreground hover:text-foreground'}"
|
|
1095
1080
|
>
|
|
1096
1081
|
Drawn Shapes ({editor.state.annotations.length})
|
|
1097
1082
|
</button>
|
|
@@ -1099,8 +1084,8 @@
|
|
|
1099
1084
|
type="button"
|
|
1100
1085
|
onclick={() => inpaintMaskMode = 'crop'}
|
|
1101
1086
|
class="px-2 py-1.5 rounded border text-[11px] font-medium text-left transition-colors {inpaintMaskMode === 'crop'
|
|
1102
|
-
? 'bg-
|
|
1103
|
-
: 'bg-
|
|
1087
|
+
? 'bg-primary/30 border-primary text-primary'
|
|
1088
|
+
: 'bg-background border-border text-muted-foreground hover:text-foreground'}"
|
|
1104
1089
|
>
|
|
1105
1090
|
Crop Box ({editor.state.crop ? 'Selected' : 'Full'})
|
|
1106
1091
|
</button>
|
|
@@ -1111,10 +1096,10 @@
|
|
|
1111
1096
|
type="button"
|
|
1112
1097
|
onclick={handleInpaint}
|
|
1113
1098
|
disabled={isInpainting || isRemovingBg || !inpaintPrompt.trim()}
|
|
1114
|
-
class="mt-1 px-3 py-2 rounded-lg bg-
|
|
1099
|
+
class="mt-1 px-3 py-2 rounded-lg bg-primary hover:bg-primary/90 disabled:opacity-50 text-primary-foreground font-medium shadow transition-colors flex items-center justify-center gap-2"
|
|
1115
1100
|
>
|
|
1116
1101
|
{#if isInpainting}
|
|
1117
|
-
<div class=
|
|
1102
|
+
<div class={imageSpinnerClass}></div>
|
|
1118
1103
|
Generating Inpaint...
|
|
1119
1104
|
{:else}
|
|
1120
1105
|
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -1126,13 +1111,13 @@
|
|
|
1126
1111
|
</div>
|
|
1127
1112
|
|
|
1128
1113
|
{#if aiError}
|
|
1129
|
-
<div class="p-2.5 rounded-lg bg-
|
|
1114
|
+
<div class="p-2.5 rounded-lg bg-destructive/10 border border-destructive/40 text-destructive text-[11px]">
|
|
1130
1115
|
{aiError}
|
|
1131
1116
|
</div>
|
|
1132
1117
|
{/if}
|
|
1133
1118
|
|
|
1134
1119
|
{#if aiSuccess}
|
|
1135
|
-
<div class="p-2.5 rounded-lg bg-emerald-
|
|
1120
|
+
<div class="p-2.5 rounded-lg bg-emerald-500/10 border border-emerald-500/30 text-emerald-600 text-[11px]">
|
|
1136
1121
|
{aiSuccess}
|
|
1137
1122
|
</div>
|
|
1138
1123
|
{/if}
|