@sealpixel/ui 1.0.0-rc.0
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/LICENSE.md +59 -0
- package/README.md +10 -0
- package/dist/components/lumbra-button.d.ts +28 -0
- package/dist/components/lumbra-button.d.ts.map +1 -0
- package/dist/components/lumbra-button.js +87 -0
- package/dist/components/lumbra-button.js.map +1 -0
- package/dist/components/lumbra-editor.d.ts +226 -0
- package/dist/components/lumbra-editor.d.ts.map +1 -0
- package/dist/components/lumbra-editor.js +873 -0
- package/dist/components/lumbra-editor.js.map +1 -0
- package/dist/components/lumbra-nav.d.ts +48 -0
- package/dist/components/lumbra-nav.d.ts.map +1 -0
- package/dist/components/lumbra-nav.js +159 -0
- package/dist/components/lumbra-nav.js.map +1 -0
- package/dist/components/lumbra-slider.d.ts +48 -0
- package/dist/components/lumbra-slider.d.ts.map +1 -0
- package/dist/components/lumbra-slider.js +168 -0
- package/dist/components/lumbra-slider.js.map +1 -0
- package/dist/components/lumbra-stage.d.ts +92 -0
- package/dist/components/lumbra-stage.d.ts.map +1 -0
- package/dist/components/lumbra-stage.js +344 -0
- package/dist/components/lumbra-stage.js.map +1 -0
- package/dist/components/lumbra-toolbar.d.ts +30 -0
- package/dist/components/lumbra-toolbar.d.ts.map +1 -0
- package/dist/components/lumbra-toolbar.js +127 -0
- package/dist/components/lumbra-toolbar.js.map +1 -0
- package/dist/components/pincel-button.d.ts +28 -0
- package/dist/components/pincel-button.d.ts.map +1 -0
- package/dist/components/pincel-button.js +87 -0
- package/dist/components/pincel-button.js.map +1 -0
- package/dist/components/pincel-editor.d.ts +195 -0
- package/dist/components/pincel-editor.d.ts.map +1 -0
- package/dist/components/pincel-editor.js +794 -0
- package/dist/components/pincel-editor.js.map +1 -0
- package/dist/components/pincel-nav.d.ts +33 -0
- package/dist/components/pincel-nav.d.ts.map +1 -0
- package/dist/components/pincel-nav.js +147 -0
- package/dist/components/pincel-nav.js.map +1 -0
- package/dist/components/pincel-slider.d.ts +48 -0
- package/dist/components/pincel-slider.d.ts.map +1 -0
- package/dist/components/pincel-slider.js +168 -0
- package/dist/components/pincel-slider.js.map +1 -0
- package/dist/components/pincel-stage.d.ts +66 -0
- package/dist/components/pincel-stage.d.ts.map +1 -0
- package/dist/components/pincel-stage.js +233 -0
- package/dist/components/pincel-stage.js.map +1 -0
- package/dist/components/pincel-toolbar.d.ts +30 -0
- package/dist/components/pincel-toolbar.d.ts.map +1 -0
- package/dist/components/pincel-toolbar.js +127 -0
- package/dist/components/pincel-toolbar.js.map +1 -0
- package/dist/components/sealpixel-button.d.ts +28 -0
- package/dist/components/sealpixel-button.d.ts.map +1 -0
- package/dist/components/sealpixel-button.js +117 -0
- package/dist/components/sealpixel-button.js.map +1 -0
- package/dist/components/sealpixel-editor.d.ts +226 -0
- package/dist/components/sealpixel-editor.d.ts.map +1 -0
- package/dist/components/sealpixel-editor.js +1245 -0
- package/dist/components/sealpixel-editor.js.map +1 -0
- package/dist/components/sealpixel-nav.d.ts +48 -0
- package/dist/components/sealpixel-nav.d.ts.map +1 -0
- package/dist/components/sealpixel-nav.js +159 -0
- package/dist/components/sealpixel-nav.js.map +1 -0
- package/dist/components/sealpixel-slider.d.ts +75 -0
- package/dist/components/sealpixel-slider.d.ts.map +1 -0
- package/dist/components/sealpixel-slider.js +269 -0
- package/dist/components/sealpixel-slider.js.map +1 -0
- package/dist/components/sealpixel-stage.d.ts +118 -0
- package/dist/components/sealpixel-stage.d.ts.map +1 -0
- package/dist/components/sealpixel-stage.js +409 -0
- package/dist/components/sealpixel-stage.js.map +1 -0
- package/dist/components/sealpixel-stepper.d.ts +69 -0
- package/dist/components/sealpixel-stepper.d.ts.map +1 -0
- package/dist/components/sealpixel-stepper.js +248 -0
- package/dist/components/sealpixel-stepper.js.map +1 -0
- package/dist/components/sealpixel-toolbar.d.ts +30 -0
- package/dist/components/sealpixel-toolbar.d.ts.map +1 -0
- package/dist/components/sealpixel-toolbar.js +149 -0
- package/dist/components/sealpixel-toolbar.js.map +1 -0
- package/dist/controllers/gesture-controller.d.ts +65 -0
- package/dist/controllers/gesture-controller.d.ts.map +1 -0
- package/dist/controllers/gesture-controller.js +125 -0
- package/dist/controllers/gesture-controller.js.map +1 -0
- package/dist/controllers/session-controller.d.ts +15 -0
- package/dist/controllers/session-controller.d.ts.map +1 -0
- package/dist/controllers/session-controller.js +37 -0
- package/dist/controllers/session-controller.js.map +1 -0
- package/dist/define.d.ts +21 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +27 -0
- package/dist/define.js.map +1 -0
- package/dist/i18n.d.ts +11 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +27 -0
- package/dist/i18n.js.map +1 -0
- package/dist/icons.d.ts +32 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +45 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/locales/de.d.ts +3 -0
- package/dist/locales/de.d.ts.map +1 -0
- package/dist/locales/de.js +132 -0
- package/dist/locales/de.js.map +1 -0
- package/dist/locales/en.d.ts +3 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/en.js +132 -0
- package/dist/locales/en.js.map +1 -0
- package/dist/locales/es.d.ts +3 -0
- package/dist/locales/es.d.ts.map +1 -0
- package/dist/locales/es.js +132 -0
- package/dist/locales/es.js.map +1 -0
- package/dist/locales/fr.d.ts +3 -0
- package/dist/locales/fr.d.ts.map +1 -0
- package/dist/locales/fr.js +132 -0
- package/dist/locales/fr.js.map +1 -0
- package/dist/locales/index.d.ts +6 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/index.js +13 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/locales/ja.d.ts +3 -0
- package/dist/locales/ja.d.ts.map +1 -0
- package/dist/locales/ja.js +132 -0
- package/dist/locales/ja.js.map +1 -0
- package/dist/locales/pt.d.ts +3 -0
- package/dist/locales/pt.d.ts.map +1 -0
- package/dist/locales/pt.js +132 -0
- package/dist/locales/pt.js.map +1 -0
- package/dist/theme.d.ts +19 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +150 -0
- package/dist/theme.js.map +1 -0
- package/dist/tool.d.ts +70 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +2 -0
- package/dist/tool.js.map +1 -0
- package/dist/tools/annotate.d.ts +3 -0
- package/dist/tools/annotate.d.ts.map +1 -0
- package/dist/tools/annotate.js +436 -0
- package/dist/tools/annotate.js.map +1 -0
- package/dist/tools/crop-mask.d.ts +29 -0
- package/dist/tools/crop-mask.d.ts.map +1 -0
- package/dist/tools/crop-mask.js +75 -0
- package/dist/tools/crop-mask.js.map +1 -0
- package/dist/tools/crop.d.ts +11 -0
- package/dist/tools/crop.d.ts.map +1 -0
- package/dist/tools/crop.js +361 -0
- package/dist/tools/crop.js.map +1 -0
- package/dist/tools/delete-handle.d.ts +18 -0
- package/dist/tools/delete-handle.d.ts.map +1 -0
- package/dist/tools/delete-handle.js +61 -0
- package/dist/tools/delete-handle.js.map +1 -0
- package/dist/tools/filter.d.ts +3 -0
- package/dist/tools/filter.d.ts.map +1 -0
- package/dist/tools/filter.js +174 -0
- package/dist/tools/filter.js.map +1 -0
- package/dist/tools/finetune.d.ts +11 -0
- package/dist/tools/finetune.d.ts.map +1 -0
- package/dist/tools/finetune.js +98 -0
- package/dist/tools/finetune.js.map +1 -0
- package/dist/tools/frame.d.ts +3 -0
- package/dist/tools/frame.d.ts.map +1 -0
- package/dist/tools/frame.js +57 -0
- package/dist/tools/frame.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +41 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/redact.d.ts +3 -0
- package/dist/tools/redact.d.ts.map +1 -0
- package/dist/tools/redact.js +252 -0
- package/dist/tools/redact.js.map +1 -0
- package/dist/tools/resize.d.ts +3 -0
- package/dist/tools/resize.d.ts.map +1 -0
- package/dist/tools/resize.js +91 -0
- package/dist/tools/resize.js.map +1 -0
- package/dist/tools/sticker.d.ts +8 -0
- package/dist/tools/sticker.d.ts.map +1 -0
- package/dist/tools/sticker.js +156 -0
- package/dist/tools/sticker.js.map +1 -0
- package/dist/tools/watermark.d.ts +3 -0
- package/dist/tools/watermark.d.ts.map +1 -0
- package/dist/tools/watermark.js +102 -0
- package/dist/tools/watermark.js.map +1 -0
- package/dist/view-mapping.d.ts +23 -0
- package/dist/view-mapping.d.ts.map +1 -0
- package/dist/view-mapping.js +72 -0
- package/dist/view-mapping.js.map +1 -0
- package/package.json +55 -0
- package/themes/default.css +23 -0
- package/themes/high-contrast.css +30 -0
- package/themes/minimal.css +32 -0
- package/themes/nocturne.css +21 -0
- package/themes/studio.css +23 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { CROP_DEFAULT_OPTIONS, ORIGINAL_ASPECT } from '@sealpixel/plugins';
|
|
2
|
+
import { html, nothing, svg } from 'lit';
|
|
3
|
+
import { icons } from '../icons.js';
|
|
4
|
+
import { opId } from './finetune.js';
|
|
5
|
+
/**
|
|
6
|
+
* While the crop tool is active the preview shows the whole rotated image
|
|
7
|
+
* (rect forced to full), and the gizmo draws the crop rectangle on top.
|
|
8
|
+
*/
|
|
9
|
+
function showFullRect(log) {
|
|
10
|
+
return {
|
|
11
|
+
...log,
|
|
12
|
+
ops: [
|
|
13
|
+
...log.ops,
|
|
14
|
+
{
|
|
15
|
+
type: 'crop.set',
|
|
16
|
+
id: '__sealpixel_preview_rect__',
|
|
17
|
+
rect: { x: 0, y: 0, width: 1, height: 1 },
|
|
18
|
+
// The gizmo draws the round mask itself; the preview shows the unmasked pixels.
|
|
19
|
+
mask: 'none',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const drag = {
|
|
25
|
+
handle: 'move',
|
|
26
|
+
start: { x: 0, y: 0, width: 1, height: 1 },
|
|
27
|
+
sx: 0,
|
|
28
|
+
sy: 0,
|
|
29
|
+
live: null,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* How long the pointer has to be still after a change before the stage shows
|
|
33
|
+
* the result. Long enough never to fire between two drags, short enough that
|
|
34
|
+
* nobody waits for it.
|
|
35
|
+
*/
|
|
36
|
+
const SETTLE_MS = 500;
|
|
37
|
+
/**
|
|
38
|
+
* The crop is written to the log the moment a handle is released, but the
|
|
39
|
+
* stage keeps showing the whole image, because a corner cannot be pulled
|
|
40
|
+
* outwards through an edge that is off screen. Letting go therefore looks
|
|
41
|
+
* exactly like doing nothing. After the pointer rests, the scrim closes over
|
|
42
|
+
* what the crop removes and the gizmo fades, so what is left on the stage is
|
|
43
|
+
* the picture that will come out. Touching the gizmo brings it all back.
|
|
44
|
+
*/
|
|
45
|
+
const settle = {
|
|
46
|
+
at: null,
|
|
47
|
+
timer: null,
|
|
48
|
+
touched: false,
|
|
49
|
+
};
|
|
50
|
+
/** The preview clipped to the crop, in percentages of the canvas box. */
|
|
51
|
+
function clipFor(rect, circle) {
|
|
52
|
+
const l = rect.x * 100;
|
|
53
|
+
const t = rect.y * 100;
|
|
54
|
+
const r = (rect.x + rect.width) * 100;
|
|
55
|
+
const b = (rect.y + rect.height) * 100;
|
|
56
|
+
if (circle) {
|
|
57
|
+
return `ellipse(${(rect.width / 2) * 100}% ${(rect.height / 2) * 100}% at ${(rect.x + rect.width / 2) * 100}% ${(rect.y + rect.height / 2) * 100}%)`;
|
|
58
|
+
}
|
|
59
|
+
// A polygon at both ends, so the clip animates; `inset(0)` would not
|
|
60
|
+
// interpolate with it and the reveal would snap.
|
|
61
|
+
return `polygon(${l}% ${t}%, ${r}% ${t}%, ${r}% ${b}%, ${l}% ${b}%)`;
|
|
62
|
+
}
|
|
63
|
+
const FULL_CLIP = 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)';
|
|
64
|
+
function unsettle(ctx) {
|
|
65
|
+
if (settle.timer !== null)
|
|
66
|
+
clearTimeout(settle.timer);
|
|
67
|
+
settle.timer = null;
|
|
68
|
+
if (settle.at !== null) {
|
|
69
|
+
settle.at = null;
|
|
70
|
+
ctx.clipPreview(FULL_CLIP);
|
|
71
|
+
ctx.refresh();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function scheduleSettle(ctx) {
|
|
75
|
+
if (settle.timer !== null)
|
|
76
|
+
clearTimeout(settle.timer);
|
|
77
|
+
settle.at = null;
|
|
78
|
+
settle.touched = true;
|
|
79
|
+
settle.timer = setTimeout(() => {
|
|
80
|
+
settle.timer = null;
|
|
81
|
+
settle.at = Date.now();
|
|
82
|
+
// Fresh from the session: `ctx.document` is the snapshot from the render
|
|
83
|
+
// that scheduled this, which is the state from before the commit.
|
|
84
|
+
const doc = ctx.session.getDocument().crop;
|
|
85
|
+
const circle = (ctx.options('crop') ?? CROP_DEFAULT_OPTIONS).shape === 'circle' ||
|
|
86
|
+
doc.mask === 'circle';
|
|
87
|
+
ctx.clipPreview(clipFor(doc.rect, circle));
|
|
88
|
+
ctx.refresh();
|
|
89
|
+
}, SETTLE_MS);
|
|
90
|
+
}
|
|
91
|
+
export const cropTool = {
|
|
92
|
+
id: 'crop',
|
|
93
|
+
icon: icons.crop,
|
|
94
|
+
label: 'tool.crop',
|
|
95
|
+
activate: (ctx) => {
|
|
96
|
+
unsettle(ctx);
|
|
97
|
+
settle.touched = false;
|
|
98
|
+
ctx.clipPreview(FULL_CLIP);
|
|
99
|
+
ctx.session.setPreviewTransform(showFullRect);
|
|
100
|
+
},
|
|
101
|
+
deactivate: (ctx) => {
|
|
102
|
+
drag.live = null;
|
|
103
|
+
unsettle(ctx);
|
|
104
|
+
// The next preview is the real crop; nothing left to clip.
|
|
105
|
+
ctx.clipPreview(null);
|
|
106
|
+
// The image becomes the crop now, so ask for it to be seen happening — but
|
|
107
|
+
// only if this visit changed it. Crop is often the tool an editor opens on,
|
|
108
|
+
// and leaving one the user never touched should move nothing.
|
|
109
|
+
if (settle.touched)
|
|
110
|
+
ctx.animateNextView();
|
|
111
|
+
settle.touched = false;
|
|
112
|
+
ctx.session.setPreviewTransform(null);
|
|
113
|
+
},
|
|
114
|
+
panel: (ctx) => {
|
|
115
|
+
const doc = ctx.document.crop;
|
|
116
|
+
const deg = (doc.rotation * 180) / Math.PI;
|
|
117
|
+
// A rotation, a flip or an aspect ratio is a change like a drag is, so it
|
|
118
|
+
// settles into the result the same way.
|
|
119
|
+
const set = (patch) => {
|
|
120
|
+
ctx.dispatch({ type: 'crop.set', id: opId(), ...patch });
|
|
121
|
+
scheduleSettle(ctx);
|
|
122
|
+
};
|
|
123
|
+
const src = ctx.state.source;
|
|
124
|
+
const original = src ? src.width / src.height : 1;
|
|
125
|
+
const options = ctx.options('crop') ?? CROP_DEFAULT_OPTIONS;
|
|
126
|
+
const circle = options.shape === 'circle';
|
|
127
|
+
// In circle mode a reset restores a full round crop instead of dropping the mask.
|
|
128
|
+
const reset = () => {
|
|
129
|
+
unsettle(ctx);
|
|
130
|
+
return ctx.dispatch(circle
|
|
131
|
+
? {
|
|
132
|
+
type: 'crop.set',
|
|
133
|
+
id: opId(),
|
|
134
|
+
rotation: 0,
|
|
135
|
+
flipX: false,
|
|
136
|
+
flipY: false,
|
|
137
|
+
rect: { x: 0, y: 0, width: 1, height: 1 },
|
|
138
|
+
aspect: 1,
|
|
139
|
+
mask: 'circle',
|
|
140
|
+
}
|
|
141
|
+
: { type: 'crop.reset', id: opId() });
|
|
142
|
+
};
|
|
143
|
+
const aspects = options.aspects.map((a) => ({
|
|
144
|
+
key: a.label,
|
|
145
|
+
value: a.value === ORIGINAL_ASPECT ? original : a.value,
|
|
146
|
+
}));
|
|
147
|
+
const aspectSelect = html `<select
|
|
148
|
+
data-field="aspect"
|
|
149
|
+
aria-label=${ctx.t('crop.aspect')}
|
|
150
|
+
@change=${(e) => {
|
|
151
|
+
const v = e.target.value;
|
|
152
|
+
const aspect = v === '' ? null : Number(v);
|
|
153
|
+
set({ aspect, ...(aspect === null ? {} : { rect: fitAspect(doc.rect, aspect, ctx) }) });
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
${aspects.map((a) => html `<option value=${a.value === null ? '' : String(a.value)} ?selected=${a.value === doc.aspect || (a.value !== null && doc.aspect !== null && Math.abs(a.value - doc.aspect) < 1e-6)}>
|
|
157
|
+
${a.key.startsWith('crop.') ? ctx.t(a.key) : a.key}
|
|
158
|
+
</option>`)}
|
|
159
|
+
</select>`;
|
|
160
|
+
return html `
|
|
161
|
+
${options.rotation
|
|
162
|
+
? html `<sealpixel-slider
|
|
163
|
+
data-param="rotation"
|
|
164
|
+
.label=${ctx.t('crop.rotation')}
|
|
165
|
+
.value=${Number(deg.toFixed(2))}
|
|
166
|
+
.min=${-180}
|
|
167
|
+
.max=${180}
|
|
168
|
+
.step=${options.rotationStep}
|
|
169
|
+
.format=${(v) => `${v.toFixed(1)}°`}
|
|
170
|
+
@slide=${(e) => ctx.hint({ transform: `rotate(${e.detail - deg}deg)` })}
|
|
171
|
+
@commit=${(e) => {
|
|
172
|
+
ctx.hint(null);
|
|
173
|
+
set({ rotation: (e.detail * Math.PI) / 180 });
|
|
174
|
+
}}
|
|
175
|
+
></sealpixel-slider>`
|
|
176
|
+
: nothing}
|
|
177
|
+
<div class="row compact choice">
|
|
178
|
+
<div class="group">
|
|
179
|
+
${options.rotation
|
|
180
|
+
? html `<sealpixel-button data-action="rotate-left" .label=${'-90°'} @click=${() => set({ rotation: doc.rotation - Math.PI / 2 })}>⟲ 90°</sealpixel-button>
|
|
181
|
+
<sealpixel-button data-action="rotate-right" .label=${'+90°'} @click=${() => set({ rotation: doc.rotation + Math.PI / 2 })}>⟳ 90°</sealpixel-button>`
|
|
182
|
+
: nothing}
|
|
183
|
+
${options.flip
|
|
184
|
+
? html `<sealpixel-button data-action="flip-x" ?pressed=${doc.flipX} .label=${ctx.t('crop.flipX')} @click=${() => set({ flipX: !doc.flipX })}>⇋</sealpixel-button>
|
|
185
|
+
<sealpixel-button data-action="flip-y" ?pressed=${doc.flipY} .label=${ctx.t('crop.flipY')} @click=${() => set({ flipY: !doc.flipY })}>⇅</sealpixel-button>`
|
|
186
|
+
: nothing}
|
|
187
|
+
</div>
|
|
188
|
+
${circle ? nothing : aspectSelect}
|
|
189
|
+
${options.reset
|
|
190
|
+
? html `<sealpixel-button variant="ghost" data-action="reset" @click=${reset}>
|
|
191
|
+
${ctx.t('crop.reset')}
|
|
192
|
+
</sealpixel-button>`
|
|
193
|
+
: nothing}
|
|
194
|
+
</div>
|
|
195
|
+
`;
|
|
196
|
+
},
|
|
197
|
+
overlay: (ctx) => {
|
|
198
|
+
const doc = ctx.document.crop;
|
|
199
|
+
const { width: W, height: H, zoom } = ctx.stage;
|
|
200
|
+
if (!W || !H)
|
|
201
|
+
return svg ``;
|
|
202
|
+
const r = drag.live ?? doc.rect;
|
|
203
|
+
const x = r.x * W;
|
|
204
|
+
const y = r.y * H;
|
|
205
|
+
const w = r.width * W;
|
|
206
|
+
const h = r.height * H;
|
|
207
|
+
const hr = 7 / zoom;
|
|
208
|
+
const sw = 1.5 / zoom;
|
|
209
|
+
const handles = [
|
|
210
|
+
{ id: 'nw', cx: x, cy: y },
|
|
211
|
+
{ id: 'n', cx: x + w / 2, cy: y },
|
|
212
|
+
{ id: 'ne', cx: x + w, cy: y },
|
|
213
|
+
{ id: 'e', cx: x + w, cy: y + h / 2 },
|
|
214
|
+
{ id: 'se', cx: x + w, cy: y + h },
|
|
215
|
+
{ id: 's', cx: x + w / 2, cy: y + h },
|
|
216
|
+
{ id: 'sw', cx: x, cy: y + h },
|
|
217
|
+
{ id: 'w', cx: x, cy: y + h / 2 },
|
|
218
|
+
];
|
|
219
|
+
const start = (handle) => (e) => {
|
|
220
|
+
e.stopPropagation();
|
|
221
|
+
e.preventDefault();
|
|
222
|
+
unsettle(ctx);
|
|
223
|
+
const p = ctx.stage.toImage(e.clientX, e.clientY);
|
|
224
|
+
drag.handle = handle;
|
|
225
|
+
drag.start = doc.rect;
|
|
226
|
+
drag.sx = p.x / W;
|
|
227
|
+
drag.sy = p.y / H;
|
|
228
|
+
drag.live = doc.rect;
|
|
229
|
+
const move = (ev) => {
|
|
230
|
+
const q = ctx.stage.toImage(ev.clientX, ev.clientY);
|
|
231
|
+
drag.live = applyDrag(drag.start, handle, q.x / W - drag.sx, q.y / H - drag.sy, doc.aspect, W / H);
|
|
232
|
+
ctx.refresh();
|
|
233
|
+
};
|
|
234
|
+
const up = () => {
|
|
235
|
+
window.removeEventListener('pointermove', move);
|
|
236
|
+
window.removeEventListener('pointerup', up);
|
|
237
|
+
window.removeEventListener('pointercancel', up);
|
|
238
|
+
const final = drag.live;
|
|
239
|
+
drag.live = null;
|
|
240
|
+
if (final && !sameRect(final, doc.rect))
|
|
241
|
+
ctx.dispatch({ type: 'crop.set', id: opId(), rect: final });
|
|
242
|
+
scheduleSettle(ctx);
|
|
243
|
+
ctx.refresh();
|
|
244
|
+
};
|
|
245
|
+
window.addEventListener('pointermove', move);
|
|
246
|
+
window.addEventListener('pointerup', up);
|
|
247
|
+
window.addEventListener('pointercancel', up);
|
|
248
|
+
};
|
|
249
|
+
const circleShape = (ctx.options('crop') ?? CROP_DEFAULT_OPTIONS).shape === 'circle' ||
|
|
250
|
+
doc.mask === 'circle';
|
|
251
|
+
const cx = x + w / 2;
|
|
252
|
+
const cy = y + h / 2;
|
|
253
|
+
// Ellipse hole as two arcs so the evenodd path can carve it out.
|
|
254
|
+
const hole = circleShape
|
|
255
|
+
? `M${cx - w / 2} ${cy}A${w / 2} ${h / 2} 0 1 0 ${cx + w / 2} ${cy}A${w / 2} ${h / 2} 0 1 0 ${cx - w / 2} ${cy}Z`
|
|
256
|
+
: `M${x} ${y}H${x + w}V${y + h}H${x}Z`;
|
|
257
|
+
// Settled: the preview is clipped to the crop and the gizmo goes, so what is
|
|
258
|
+
// left on the stage is the picture that comes out, with the stage's own
|
|
259
|
+
// backdrop around it rather than a painted stand-in. The scrim is only the
|
|
260
|
+
// dim for adjusting and fades out with the rest.
|
|
261
|
+
//
|
|
262
|
+
// The transitions are inline, and the reduced-motion rule in the shared
|
|
263
|
+
// base still overrides them, which leaves the same state change unanimated.
|
|
264
|
+
const shown = settle.at !== null;
|
|
265
|
+
const ease = 'var(--sealpixel-duration-slow, 320ms) var(--sealpixel-ease-out, ease-out)';
|
|
266
|
+
const gizmo = `transition:opacity ${ease};opacity:${shown ? 0 : 1}`;
|
|
267
|
+
return svg `
|
|
268
|
+
<path
|
|
269
|
+
data-crop-scrim
|
|
270
|
+
d=${`M0 0H${W}V${H}H0Z ${hole}`}
|
|
271
|
+
fill="var(--sealpixel-color-bg, #000)"
|
|
272
|
+
fill-opacity=${shown ? 0 : 0.5}
|
|
273
|
+
fill-rule="evenodd"
|
|
274
|
+
style=${`transition:fill-opacity ${ease}`}
|
|
275
|
+
pointer-events="none"
|
|
276
|
+
/>
|
|
277
|
+
${circleShape ? svg `<ellipse data-crop-shape="circle" cx=${cx} cy=${cy} rx=${w / 2} ry=${h / 2} fill="none" stroke="white" stroke-width=${sw} style=${gizmo} pointer-events="none" />` : nothing}
|
|
278
|
+
<rect data-interactive data-handle="move" x=${x} y=${y} width=${w} height=${h} fill="transparent" stroke="white" stroke-width=${sw} style=${`cursor:move;transition:stroke-opacity ${ease};stroke-opacity:${shown ? 0 : 1}`} @pointerdown=${start('move')} />
|
|
279
|
+
<path d=${`M${x + w / 3} ${y}V${y + h}M${x + (2 * w) / 3} ${y}V${y + h}M${x} ${y + h / 3}H${x + w}M${x} ${y + (2 * h) / 3}H${x + w}`} stroke="rgba(255,255,255,0.35)" stroke-width=${sw / 1.5} fill="none" style=${gizmo} pointer-events="none" />
|
|
280
|
+
${handles.map((hd) => svg `<circle data-interactive data-handle=${hd.id} cx=${hd.cx} cy=${hd.cy} r=${hr} fill="white" stroke="var(--sealpixel-color-accent)" stroke-width=${sw} style=${`cursor:${cursorFor(hd.id)};transition:opacity ${ease};opacity:${shown ? 0 : 1}`} @pointerdown=${start(hd.id)} />`)}
|
|
281
|
+
`;
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
function cursorFor(h) {
|
|
285
|
+
return {
|
|
286
|
+
nw: 'nwse-resize',
|
|
287
|
+
se: 'nwse-resize',
|
|
288
|
+
ne: 'nesw-resize',
|
|
289
|
+
sw: 'nesw-resize',
|
|
290
|
+
n: 'ns-resize',
|
|
291
|
+
s: 'ns-resize',
|
|
292
|
+
e: 'ew-resize',
|
|
293
|
+
w: 'ew-resize',
|
|
294
|
+
move: 'move',
|
|
295
|
+
}[h];
|
|
296
|
+
}
|
|
297
|
+
function sameRect(a, b) {
|
|
298
|
+
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
299
|
+
}
|
|
300
|
+
const MIN = 0.02;
|
|
301
|
+
/**
|
|
302
|
+
* Applies a normalised drag delta to `start`. `aspect` is width/height in
|
|
303
|
+
* output pixels; `stageAspect` converts it into normalised units.
|
|
304
|
+
*/
|
|
305
|
+
export function applyDrag(start, handle, dx, dy, aspect, stageAspect) {
|
|
306
|
+
if (handle === 'move') {
|
|
307
|
+
return {
|
|
308
|
+
...start,
|
|
309
|
+
x: clamp(start.x + dx, 0, 1 - start.width),
|
|
310
|
+
y: clamp(start.y + dy, 0, 1 - start.height),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
let x0 = start.x;
|
|
314
|
+
let y0 = start.y;
|
|
315
|
+
let x1 = start.x + start.width;
|
|
316
|
+
let y1 = start.y + start.height;
|
|
317
|
+
if (handle.includes('w'))
|
|
318
|
+
x0 = clamp(x0 + dx, 0, x1 - MIN);
|
|
319
|
+
if (handle.includes('e'))
|
|
320
|
+
x1 = clamp(x1 + dx, x0 + MIN, 1);
|
|
321
|
+
if (handle.includes('n'))
|
|
322
|
+
y0 = clamp(y0 + dy, 0, y1 - MIN);
|
|
323
|
+
if (handle.includes('s'))
|
|
324
|
+
y1 = clamp(y1 + dy, y0 + MIN, 1);
|
|
325
|
+
let rect = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
|
|
326
|
+
if (aspect !== null) {
|
|
327
|
+
// normalised height for a given normalised width under this aspect
|
|
328
|
+
const ratio = aspect / stageAspect; // width_n / height_n
|
|
329
|
+
const anchorRight = handle.includes('w');
|
|
330
|
+
const anchorBottom = handle.includes('n');
|
|
331
|
+
const horizontal = handle === 'e' || handle === 'w';
|
|
332
|
+
let w = rect.width;
|
|
333
|
+
let h = rect.height;
|
|
334
|
+
if (horizontal || (!['n', 's'].includes(handle) && Math.abs(dx) >= Math.abs(dy)))
|
|
335
|
+
h = w / ratio;
|
|
336
|
+
else
|
|
337
|
+
w = h * ratio;
|
|
338
|
+
// keep inside bounds
|
|
339
|
+
if (w > 1) {
|
|
340
|
+
w = 1;
|
|
341
|
+
h = w / ratio;
|
|
342
|
+
}
|
|
343
|
+
if (h > 1) {
|
|
344
|
+
h = 1;
|
|
345
|
+
w = h * ratio;
|
|
346
|
+
}
|
|
347
|
+
const nx = anchorRight ? x1 - w : x0;
|
|
348
|
+
const ny = anchorBottom ? y1 - h : y0;
|
|
349
|
+
rect = { x: clamp(nx, 0, 1 - w), y: clamp(ny, 0, 1 - h), width: w, height: h };
|
|
350
|
+
}
|
|
351
|
+
return rect;
|
|
352
|
+
}
|
|
353
|
+
function fitAspect(rect, aspect, ctx) {
|
|
354
|
+
const { width: W, height: H } = ctx.stage;
|
|
355
|
+
const stageAspect = W && H ? W / H : 1;
|
|
356
|
+
return applyDrag({ ...rect }, 'se', 0, 0, aspect, stageAspect);
|
|
357
|
+
}
|
|
358
|
+
function clamp(v, lo, hi) {
|
|
359
|
+
return v < lo ? lo : v > hi ? hi : v;
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=crop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crop.js","sourceRoot":"","sources":["../../src/tools/crop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Rect } from '@sealpixel/core';
|
|
2
|
+
import { type SVGTemplateResult } from 'lit';
|
|
3
|
+
/**
|
|
4
|
+
* A trash button floating above a selected element, rendered inside a tool
|
|
5
|
+
* overlay in image pixel coordinates. It is sized through `zoom`, so it stays
|
|
6
|
+
* 32 px on screen at every zoom level, and moves below the element when the
|
|
7
|
+
* image edge leaves no room above. Enter and Space trigger it from the keyboard.
|
|
8
|
+
*/
|
|
9
|
+
export declare function deleteHandle(box: Rect, stage: {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
zoom: number;
|
|
13
|
+
}, label: string, onDelete: () => void): SVGTemplateResult;
|
|
14
|
+
/** Axis-aligned box around normalized points. */
|
|
15
|
+
export declare function pointsBox(points: readonly (readonly [number, number])[]): Rect;
|
|
16
|
+
/** Normalized rect to image pixels. */
|
|
17
|
+
export declare function toPx(r: Rect, W: number, H: number): Rect;
|
|
18
|
+
//# sourceMappingURL=delete-handle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-handle.d.ts","sourceRoot":"","sources":["../../src/tools/delete-handle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,KAAK,iBAAiB,EAAO,MAAM,KAAK,CAAC;AAGlD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,IAAI,EACT,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACtD,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,IAAI,GACnB,iBAAiB,CAwBnB;AAED,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,GAAG,IAAI,CAY9E;AAED,uCAAuC;AACvC,wBAAgB,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAExD"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { svg } from 'lit';
|
|
2
|
+
import { TRASH_PATH } from '../icons.js';
|
|
3
|
+
/**
|
|
4
|
+
* A trash button floating above a selected element, rendered inside a tool
|
|
5
|
+
* overlay in image pixel coordinates. It is sized through `zoom`, so it stays
|
|
6
|
+
* 32 px on screen at every zoom level, and moves below the element when the
|
|
7
|
+
* image edge leaves no room above. Enter and Space trigger it from the keyboard.
|
|
8
|
+
*/
|
|
9
|
+
export function deleteHandle(box, stage, label, onDelete) {
|
|
10
|
+
const { zoom, width: W, height: H } = stage;
|
|
11
|
+
const r = 16 / zoom;
|
|
12
|
+
const gap = 10 / zoom;
|
|
13
|
+
const cx = clamp(box.x + box.width / 2, r, W - r);
|
|
14
|
+
let cy = box.y - gap - r;
|
|
15
|
+
if (cy - r < 0)
|
|
16
|
+
cy = Math.min(box.y + box.height + gap + r, H - r);
|
|
17
|
+
const stop = (e) => {
|
|
18
|
+
e.stopPropagation();
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
};
|
|
21
|
+
const act = (e) => {
|
|
22
|
+
e.stopPropagation();
|
|
23
|
+
onDelete();
|
|
24
|
+
};
|
|
25
|
+
return svg `<g data-interactive data-action="delete" role="button" tabindex="0" aria-label=${label}
|
|
26
|
+
transform=${`translate(${cx} ${cy})`} style="cursor:pointer"
|
|
27
|
+
@pointerdown=${stop} @click=${act}
|
|
28
|
+
@keydown=${(e) => {
|
|
29
|
+
if (e.key === 'Enter' || e.key === ' ')
|
|
30
|
+
act(e);
|
|
31
|
+
}}>
|
|
32
|
+
<circle r=${r} fill="var(--sealpixel-color-surface)" stroke="var(--sealpixel-color-border)" stroke-width=${1 / zoom} />
|
|
33
|
+
<g transform=${`scale(${1 / zoom}) translate(-9 -9) scale(0.75)`} fill="none" stroke="var(--sealpixel-color-danger)" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d=${TRASH_PATH} /></g>
|
|
34
|
+
</g>`;
|
|
35
|
+
}
|
|
36
|
+
/** Axis-aligned box around normalized points. */
|
|
37
|
+
export function pointsBox(points) {
|
|
38
|
+
let x0 = 1;
|
|
39
|
+
let y0 = 1;
|
|
40
|
+
let x1 = 0;
|
|
41
|
+
let y1 = 0;
|
|
42
|
+
for (const [x, y] of points) {
|
|
43
|
+
if (x < x0)
|
|
44
|
+
x0 = x;
|
|
45
|
+
if (y < y0)
|
|
46
|
+
y0 = y;
|
|
47
|
+
if (x > x1)
|
|
48
|
+
x1 = x;
|
|
49
|
+
if (y > y1)
|
|
50
|
+
y1 = y;
|
|
51
|
+
}
|
|
52
|
+
return { x: x0, y: y0, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };
|
|
53
|
+
}
|
|
54
|
+
/** Normalized rect to image pixels. */
|
|
55
|
+
export function toPx(r, W, H) {
|
|
56
|
+
return { x: r.x * W, y: r.y * H, width: r.width * W, height: r.height * H };
|
|
57
|
+
}
|
|
58
|
+
function clamp(v, lo, hi) {
|
|
59
|
+
return v < lo ? lo : v > hi ? hi : v;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=delete-handle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-handle.js","sourceRoot":"","sources":["../../src/tools/delete-handle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/tools/filter.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,YAAY,CAAC;AA6GxD,eAAO,MAAM,UAAU,EAAE,MAmFxB,CAAC"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { canonicalJson } from '@sealpixel/core';
|
|
2
|
+
import { FILTER_DEFAULT_OPTIONS, presetLabelKey, presetMatrix, presetName, } from '@sealpixel/plugins';
|
|
3
|
+
import { html, nothing } from 'lit';
|
|
4
|
+
import { icons } from '../icons.js';
|
|
5
|
+
import { opId } from './finetune.js';
|
|
6
|
+
/** Leaves the image as it is, so the "none" tile shows the real source. */
|
|
7
|
+
const IDENTITY = [
|
|
8
|
+
[1, 0, 0, 0, 0],
|
|
9
|
+
[0, 1, 0, 0, 0],
|
|
10
|
+
[0, 0, 1, 0, 0],
|
|
11
|
+
[0, 0, 0, 1, 0],
|
|
12
|
+
];
|
|
13
|
+
const sameMatrix = (a, b) => a !== null &&
|
|
14
|
+
a.every((row, i) => row.every((v, j) => Math.abs(v - b[i][j]) < 1e-9));
|
|
15
|
+
/**
|
|
16
|
+
* The image every thumbnail is drawn from: the current log with the filter
|
|
17
|
+
* taken out. Rendering the tiles from the stage preview instead would push
|
|
18
|
+
* each preset through whichever preset is already chosen, so picking Sepia
|
|
19
|
+
* turned every other tile sepia too and "None" stopped meaning none.
|
|
20
|
+
*
|
|
21
|
+
* Keyed by the rest of the log, so a crop or a finetune invalidates it while
|
|
22
|
+
* changing the filter does not.
|
|
23
|
+
*/
|
|
24
|
+
const withoutFilter = (log) => ({
|
|
25
|
+
...log,
|
|
26
|
+
ops: log.ops.filter((op) => !op.type.startsWith('filter.')),
|
|
27
|
+
});
|
|
28
|
+
const baseKey = (log) => canonicalJson(withoutFilter(log).ops);
|
|
29
|
+
let base = null;
|
|
30
|
+
let basePending = null;
|
|
31
|
+
/**
|
|
32
|
+
* Returns the unfiltered image for this log, rendering it once per key. While
|
|
33
|
+
* a render is in flight the caller gets nothing and the tiles stay flat; the
|
|
34
|
+
* refresh lands a moment later.
|
|
35
|
+
*/
|
|
36
|
+
function baseImage(ctx) {
|
|
37
|
+
const { log } = ctx.state.history;
|
|
38
|
+
const key = baseKey(log);
|
|
39
|
+
if (base?.key === key)
|
|
40
|
+
return base.image;
|
|
41
|
+
// No filter in the log means the stage already shows the unfiltered image.
|
|
42
|
+
if (log.ops.every((op) => !op.type.startsWith('filter.'))) {
|
|
43
|
+
const image = ctx.state.preview?.image ?? null;
|
|
44
|
+
if (image)
|
|
45
|
+
base = { key, image };
|
|
46
|
+
return image;
|
|
47
|
+
}
|
|
48
|
+
if (basePending === key)
|
|
49
|
+
return null;
|
|
50
|
+
basePending = key;
|
|
51
|
+
ctx.session
|
|
52
|
+
.renderDetached(withoutFilter)
|
|
53
|
+
.then((image) => {
|
|
54
|
+
base = { key, image };
|
|
55
|
+
ctx.refresh();
|
|
56
|
+
})
|
|
57
|
+
.catch(() => { })
|
|
58
|
+
.finally(() => {
|
|
59
|
+
if (basePending === key)
|
|
60
|
+
basePending = null;
|
|
61
|
+
});
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/** Thumbnails: the base image downsampled and pushed through a matrix. */
|
|
65
|
+
const thumbCache = new Map();
|
|
66
|
+
function thumbnail(image, version, name, matrix) {
|
|
67
|
+
const key = `${version}:${name}`;
|
|
68
|
+
const hit = thumbCache.get(key);
|
|
69
|
+
if (hit)
|
|
70
|
+
return hit;
|
|
71
|
+
if (thumbCache.size > 64)
|
|
72
|
+
thumbCache.clear();
|
|
73
|
+
// Rendered wider than the tile so an 84 px thumbnail is not an upscale.
|
|
74
|
+
const w = 96;
|
|
75
|
+
const h = Math.max(1, Math.round((image.height / image.width) * w));
|
|
76
|
+
const canvas = document.createElement('canvas');
|
|
77
|
+
canvas.width = w;
|
|
78
|
+
canvas.height = h;
|
|
79
|
+
const ctx = canvas.getContext('2d');
|
|
80
|
+
if (!ctx)
|
|
81
|
+
return '';
|
|
82
|
+
const out = ctx.createImageData(w, h);
|
|
83
|
+
for (let y = 0; y < h; y++) {
|
|
84
|
+
const sy = Math.floor((y / h) * image.height);
|
|
85
|
+
for (let x = 0; x < w; x++) {
|
|
86
|
+
const sx = Math.floor((x / w) * image.width);
|
|
87
|
+
const si = (sy * image.width + sx) * 4;
|
|
88
|
+
const r = image.data[si] / 255;
|
|
89
|
+
const g = image.data[si + 1] / 255;
|
|
90
|
+
const b = image.data[si + 2] / 255;
|
|
91
|
+
const a = image.data[si + 3] / 255;
|
|
92
|
+
const di = (y * w + x) * 4;
|
|
93
|
+
for (let c = 0; c < 4; c++) {
|
|
94
|
+
const m = matrix[c];
|
|
95
|
+
const v = m[0] * r + m[1] * g + m[2] * b + m[3] * a + m[4];
|
|
96
|
+
out.data[di + c] = Math.max(0, Math.min(255, Math.round(v * 255)));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
ctx.putImageData(out, 0, 0);
|
|
101
|
+
const url = canvas.toDataURL('image/png');
|
|
102
|
+
thumbCache.set(key, url);
|
|
103
|
+
return url;
|
|
104
|
+
}
|
|
105
|
+
export const filterTool = {
|
|
106
|
+
id: 'filter',
|
|
107
|
+
icon: icons.filter,
|
|
108
|
+
label: 'tool.filter',
|
|
109
|
+
panel: (ctx) => {
|
|
110
|
+
const doc = ctx.document.filter;
|
|
111
|
+
const options = ctx.options('filter') ?? FILTER_DEFAULT_OPTIONS;
|
|
112
|
+
const strength = doc.preset === null ? options.defaultStrength : doc.strength;
|
|
113
|
+
const select = (preset) => {
|
|
114
|
+
if (preset === null) {
|
|
115
|
+
ctx.dispatch({ type: 'filter.set', id: opId(), preset: null });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const option = options.presets.find((p) => presetName(p) === preset);
|
|
119
|
+
if (!option)
|
|
120
|
+
return;
|
|
121
|
+
ctx.dispatch(typeof option === 'string'
|
|
122
|
+
? { type: 'filter.set', id: opId(), preset, strength }
|
|
123
|
+
: { type: 'filter.set', id: opId(), preset: 'custom', matrix: option.matrix, strength });
|
|
124
|
+
};
|
|
125
|
+
const pressed = (p) => typeof p === 'string'
|
|
126
|
+
? doc.preset === p
|
|
127
|
+
: doc.preset === 'custom' && sameMatrix(doc.matrix, p.matrix);
|
|
128
|
+
const label = (p) => {
|
|
129
|
+
const { key, literal } = presetLabelKey(p);
|
|
130
|
+
return literal ?? ctx.t(key);
|
|
131
|
+
};
|
|
132
|
+
const source = options.thumbnails ? baseImage(ctx) : null;
|
|
133
|
+
const sourceKey = source ? baseKey(ctx.state.history.log) : '';
|
|
134
|
+
// The untouched image sits in its own group. It is not one preset among
|
|
135
|
+
// the others; it is what every preset is being compared against, and a gap
|
|
136
|
+
// says that faster than a label can.
|
|
137
|
+
const tile = (name, text, isPressed, matrix, onClick) => html `<sealpixel-button
|
|
138
|
+
variant="ghost"
|
|
139
|
+
data-preset=${name}
|
|
140
|
+
?pressed=${isPressed}
|
|
141
|
+
.label=${text}
|
|
142
|
+
@click=${onClick}
|
|
143
|
+
>${source && matrix
|
|
144
|
+
? html `<img class="thumb" alt="" src=${thumbnail(source, sourceKey, name, matrix)} /><span>${text}</span>`
|
|
145
|
+
: text}</sealpixel-button
|
|
146
|
+
>`;
|
|
147
|
+
return html `
|
|
148
|
+
<div class=${source ? 'strip tiles choice' : 'strip choice'} role="group" aria-label=${ctx.t('tool.filter')}>
|
|
149
|
+
${tile('none', ctx.t('filter.none'), doc.preset === null, IDENTITY, () => select(null))}
|
|
150
|
+
<span class="divider" aria-hidden="true"></span>
|
|
151
|
+
${options.presets.map((p) => tile(presetName(p), label(p), pressed(p), presetMatrix(p), () => select(presetName(p))))}
|
|
152
|
+
</div>
|
|
153
|
+
${options.strength
|
|
154
|
+
? html `<sealpixel-slider
|
|
155
|
+
data-param="strength"
|
|
156
|
+
.label=${ctx.t('filter.strength')}
|
|
157
|
+
.value=${doc.strength}
|
|
158
|
+
.min=${options.strengthRange.min}
|
|
159
|
+
.max=${options.strengthRange.max}
|
|
160
|
+
.step=${options.strengthRange.step}
|
|
161
|
+
?disabled=${doc.preset === null}
|
|
162
|
+
@commit=${(e) => ctx.dispatch({
|
|
163
|
+
type: 'filter.set',
|
|
164
|
+
id: opId(),
|
|
165
|
+
preset: doc.preset,
|
|
166
|
+
...(doc.preset === 'custom' && doc.matrix ? { matrix: doc.matrix } : {}),
|
|
167
|
+
strength: e.detail,
|
|
168
|
+
})}
|
|
169
|
+
></sealpixel-slider>`
|
|
170
|
+
: nothing}
|
|
171
|
+
`;
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=filter.js.map
|