@pixerate/editor-svelte 0.163.0 → 0.164.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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import SelectionRecipePanel from './SelectionRecipePanel.svelte';
|
|
2
3
|
import ImageCheckbox from './controls/ImageCheckbox.svelte';
|
|
3
4
|
import ImageRange from './controls/ImageRange.svelte';
|
|
4
5
|
import ImageSelect from './controls/ImageSelect.svelte';
|
|
@@ -590,4 +591,5 @@
|
|
|
590
591
|
<p role="status" class="text-muted-foreground">{colorStatus}</p>
|
|
591
592
|
{/if}
|
|
592
593
|
<p data-testid="selection-status" class="text-muted-foreground">{describeSelection(sel)}</p>
|
|
594
|
+
<SelectionRecipePanel {controller} selection={sel} />
|
|
593
595
|
</div>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ImageEditorController, SelectionMask, SelectionRecipeStep } from '@pixerate/editor/image-editor';
|
|
3
|
+
import { describeRecipeStep, recipeStepSetting } from '@pixerate/editor/image-editor';
|
|
4
|
+
import { untrack } from 'svelte';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Live selection: the steps that made the selection, editable afterwards (each change replays
|
|
8
|
+
* them as one undo step), as the `*_selection_step` tools do. Freeze keeps the selection and drops
|
|
9
|
+
* the steps. Hidden while the selection is frozen or empty.
|
|
10
|
+
*/
|
|
11
|
+
interface Props {
|
|
12
|
+
controller: ImageEditorController;
|
|
13
|
+
/** The current selection (`state.selection`), so the panel re-renders with it. */
|
|
14
|
+
selection?: SelectionMask | null;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { controller, selection, class: className = '' }: Props = $props();
|
|
19
|
+
|
|
20
|
+
const MODES = ['replace', 'add', 'subtract', 'intersect'] as const;
|
|
21
|
+
const button =
|
|
22
|
+
'px-1.5 py-0.5 rounded border border-border bg-background text-[11px] text-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-40';
|
|
23
|
+
let tick = $state(0);
|
|
24
|
+
$effect(() => {
|
|
25
|
+
const c = controller;
|
|
26
|
+
return untrack(() => c.subscribe(() => tick++));
|
|
27
|
+
});
|
|
28
|
+
let error = $state<string | null>(null);
|
|
29
|
+
const recipe = $derived.by(() => {
|
|
30
|
+
void tick;
|
|
31
|
+
void selection;
|
|
32
|
+
return controller.getSelectionRecipe();
|
|
33
|
+
});
|
|
34
|
+
const isShape = (step: { op: string }) => ['rect', 'ellipse', 'polygon', 'magic-wand', 'color-range', 'color-range-preset'].includes(step.op);
|
|
35
|
+
|
|
36
|
+
function run(p: Promise<unknown> | unknown) {
|
|
37
|
+
error = null;
|
|
38
|
+
Promise.resolve(p).catch((err) => (error = err instanceof Error ? err.message : String(err)));
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
{#if recipe.live}
|
|
43
|
+
<div data-testid="selection-recipe" class="flex flex-col gap-1 text-xs {className}">
|
|
44
|
+
<div class="flex items-center justify-between">
|
|
45
|
+
<span class="text-muted-foreground">Selection steps</span>
|
|
46
|
+
<button type="button" class={button} title="Keep the selection, drop its steps" onclick={() => run(controller.freezeSelection())}>Freeze</button>
|
|
47
|
+
</div>
|
|
48
|
+
<ol class="flex flex-col gap-1">
|
|
49
|
+
{#each recipe.steps as step (`${step.index}:${JSON.stringify(step)}`)}
|
|
50
|
+
{@const setting = recipeStepSetting(step)}
|
|
51
|
+
<li data-testid="selection-step" class="flex flex-wrap items-center gap-1">
|
|
52
|
+
<span class="flex-1 min-w-0 truncate text-foreground">{describeRecipeStep(step as SelectionRecipeStep)}</span>
|
|
53
|
+
{#if isShape(step) && step.index > 0}
|
|
54
|
+
<select
|
|
55
|
+
aria-label="Step {step.index + 1} mode"
|
|
56
|
+
value={(step as { mode?: string }).mode ?? 'replace'}
|
|
57
|
+
onchange={(e) => run(controller.editSelectionStep(step.index, { mode: (e.currentTarget as HTMLSelectElement).value }))}
|
|
58
|
+
class="bg-background border border-border rounded px-1 py-0.5 text-foreground"
|
|
59
|
+
>
|
|
60
|
+
{#each MODES as m (m)}
|
|
61
|
+
<option value={m}>{m}</option>
|
|
62
|
+
{/each}
|
|
63
|
+
</select>
|
|
64
|
+
{/if}
|
|
65
|
+
{#if setting}
|
|
66
|
+
<label class="flex items-center gap-1 text-muted-foreground">
|
|
67
|
+
{setting.label}
|
|
68
|
+
<input
|
|
69
|
+
type="number"
|
|
70
|
+
min={setting.min}
|
|
71
|
+
max={setting.max}
|
|
72
|
+
aria-label="Step {step.index + 1} {setting.label}"
|
|
73
|
+
value={setting.value}
|
|
74
|
+
onblur={(e) => {
|
|
75
|
+
const v = Number((e.currentTarget as HTMLInputElement).value);
|
|
76
|
+
if (v !== setting.value) run(controller.editSelectionStep(step.index, setting.change(v)));
|
|
77
|
+
}}
|
|
78
|
+
onkeydown={(e) => e.key === 'Enter' && (e.currentTarget as HTMLInputElement).blur()}
|
|
79
|
+
class="w-12 bg-background border border-border rounded px-1 py-0.5 text-foreground"
|
|
80
|
+
/>
|
|
81
|
+
</label>
|
|
82
|
+
{/if}
|
|
83
|
+
<button type="button" class={button} aria-label="Remove step {step.index + 1}" disabled={recipe.steps.length === 1} onclick={() => run(controller.removeSelectionStep(step.index))}>×</button>
|
|
84
|
+
</li>
|
|
85
|
+
{/each}
|
|
86
|
+
</ol>
|
|
87
|
+
{#if error}
|
|
88
|
+
<p role="alert" class="text-destructive">{error}</p>
|
|
89
|
+
{/if}
|
|
90
|
+
</div>
|
|
91
|
+
{/if}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ImageEditorController, SelectionMask } from '@pixerate/editor/image-editor';
|
|
2
|
+
/**
|
|
3
|
+
* Live selection: the steps that made the selection, editable afterwards (each change replays
|
|
4
|
+
* them as one undo step), as the `*_selection_step` tools do. Freeze keeps the selection and drops
|
|
5
|
+
* the steps. Hidden while the selection is frozen or empty.
|
|
6
|
+
*/
|
|
7
|
+
interface Props {
|
|
8
|
+
controller: ImageEditorController;
|
|
9
|
+
/** The current selection (`state.selection`), so the panel re-renders with it. */
|
|
10
|
+
selection?: SelectionMask | null;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
declare const SelectionRecipePanel: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type SelectionRecipePanel = ReturnType<typeof SelectionRecipePanel>;
|
|
15
|
+
export default SelectionRecipePanel;
|
|
@@ -11,6 +11,7 @@ import type RefineEdgePanelComponent from "./RefineEdgePanel.svelte";
|
|
|
11
11
|
import type MaskEdgeCheckComponent from "./MaskEdgeCheck.svelte";
|
|
12
12
|
import type SelectionChangeCheckComponent from "./SelectionChangeCheck.svelte";
|
|
13
13
|
import type SeparateSubjectPanelComponent from "./SeparateSubjectPanel.svelte";
|
|
14
|
+
import type SelectionRecipePanelComponent from "./SelectionRecipePanel.svelte";
|
|
14
15
|
import type PathsPanelComponent from "./PathsPanel.svelte";
|
|
15
16
|
import type SelectAndMaskWorkspaceComponent from "./SelectAndMaskWorkspace.svelte";
|
|
16
17
|
import type ContentAwareFillWorkspaceComponent from "./ContentAwareFillWorkspace.svelte";
|
|
@@ -43,6 +44,7 @@ export { default as RefineEdgePanel } from './RefineEdgePanel.svelte';
|
|
|
43
44
|
export { default as MaskEdgeCheck } from './MaskEdgeCheck.svelte';
|
|
44
45
|
export { default as SelectionChangeCheck } from './SelectionChangeCheck.svelte';
|
|
45
46
|
export { default as SeparateSubjectPanel } from './SeparateSubjectPanel.svelte';
|
|
47
|
+
export { default as SelectionRecipePanel } from './SelectionRecipePanel.svelte';
|
|
46
48
|
export { default as PathsPanel } from './PathsPanel.svelte';
|
|
47
49
|
export { default as SelectAndMaskWorkspace } from './SelectAndMaskWorkspace.svelte';
|
|
48
50
|
export { default as ContentAwareFillWorkspace } from './ContentAwareFillWorkspace.svelte';
|
|
@@ -77,6 +79,7 @@ export type RefineEdgePanelProps = ComponentProps<typeof RefineEdgePanelComponen
|
|
|
77
79
|
export type MaskEdgeCheckProps = ComponentProps<typeof MaskEdgeCheckComponent>;
|
|
78
80
|
export type SelectionChangeCheckProps = ComponentProps<typeof SelectionChangeCheckComponent>;
|
|
79
81
|
export type SeparateSubjectPanelProps = ComponentProps<typeof SeparateSubjectPanelComponent>;
|
|
82
|
+
export type SelectionRecipePanelProps = ComponentProps<typeof SelectionRecipePanelComponent>;
|
|
80
83
|
export type PathsPanelProps = ComponentProps<typeof PathsPanelComponent>;
|
|
81
84
|
export type SelectAndMaskWorkspaceProps = ComponentProps<typeof SelectAndMaskWorkspaceComponent>;
|
|
82
85
|
export type ContentAwareFillWorkspaceProps = ComponentProps<typeof ContentAwareFillWorkspaceComponent>;
|
|
@@ -13,6 +13,7 @@ export { default as RefineEdgePanel } from './RefineEdgePanel.svelte';
|
|
|
13
13
|
export { default as MaskEdgeCheck } from './MaskEdgeCheck.svelte';
|
|
14
14
|
export { default as SelectionChangeCheck } from './SelectionChangeCheck.svelte';
|
|
15
15
|
export { default as SeparateSubjectPanel } from './SeparateSubjectPanel.svelte';
|
|
16
|
+
export { default as SelectionRecipePanel } from './SelectionRecipePanel.svelte';
|
|
16
17
|
export { default as PathsPanel } from './PathsPanel.svelte';
|
|
17
18
|
export { default as SelectAndMaskWorkspace } from './SelectAndMaskWorkspace.svelte';
|
|
18
19
|
export { default as ContentAwareFillWorkspace } from './ContentAwareFillWorkspace.svelte';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.164.1",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"**/*.css"
|
|
166
166
|
],
|
|
167
167
|
"dependencies": {
|
|
168
|
-
"@pixerate/editor": "0.
|
|
168
|
+
"@pixerate/editor": "0.221.1"
|
|
169
169
|
},
|
|
170
170
|
"peerDependencies": {
|
|
171
171
|
"@dagrejs/dagre": "^1.1.4",
|