@lvl99dotcom/paintcanvas 0.5.0 → 0.7.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/README.md +2 -2
- package/dist/lib/brushes/configRanges/index.d.ts +17 -0
- package/dist/lib/brushes/configRanges/index.js +31 -1
- package/dist/lib/brushes/params/index.d.ts +2 -1
- package/dist/lib/brushes/params/index.js +7 -4
- package/dist/lib/brushes/types/index.d.ts +8 -0
- package/dist/lib/classes/Layer/index.d.ts +10 -0
- package/dist/lib/classes/Layer/index.js +15 -1
- package/dist/lib/classes/LayerAutosave/index.d.ts +9 -15
- package/dist/lib/classes/LayerAutosave/index.js +30 -43
- package/dist/lib/classes/LayerStack/index.d.ts +15 -0
- package/dist/lib/classes/LayerStack/index.js +26 -2
- package/dist/lib/classes/PaintCanvas/index.d.ts +36 -5
- package/dist/lib/classes/PaintCanvas/index.js +45 -14
- package/dist/lib/classes/PngEncoder/index.d.ts +41 -0
- package/dist/lib/classes/PngEncoder/index.js +104 -0
- package/dist/lib/classes/ProfileGraphs/index.d.ts +87 -0
- package/dist/lib/classes/ProfileGraphs/index.js +148 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +1 -1
- package/dist/lib/classes/SettingsAutosave/index.js +2 -1
- package/dist/lib/classes/UiSettings/index.d.ts +24 -2
- package/dist/lib/classes/UiSettings/index.js +32 -7
- package/dist/lib/messages/schema.d.ts +720 -0
- package/dist/lib/presets/schema.d.ts +360 -0
- package/dist/lib/presets/schema.js +8 -1
- package/dist/lib/profiles/index.d.ts +61 -0
- package/dist/lib/profiles/index.js +131 -0
- package/dist/lib/profiles/schema.d.ts +73 -0
- package/dist/lib/profiles/schema.js +53 -0
- package/dist/lib/rendering/sample/index.d.ts +11 -0
- package/dist/lib/rendering/sample/index.js +36 -5
- package/dist/lib/settings/index.d.ts +2 -1
- package/dist/lib/settings/index.js +4 -1
- package/dist/lib/settings/schema.d.ts +53 -1
- package/dist/lib/settings/schema.js +9 -2
- package/dist/lib/storage/index.d.ts +26 -0
- package/dist/lib/storage/index.js +38 -1
- package/dist/lib/validation/validators.generated.js +13061 -3675
- package/dist/lib/version/index.d.ts +25 -0
- package/dist/lib/version/index.js +35 -0
- package/dist/locales/en-GB.js +35 -0
- package/dist/locales/en-US.js +35 -0
- package/dist/locales/fr.js +35 -0
- package/dist/locales/ja.js +35 -0
- package/dist/locales/ko.js +35 -0
- package/dist/locales/zh-Hans.js +35 -0
- package/dist/locales/zh-Hant.js +35 -0
- package/dist/package.js +1 -1
- package/dist/ui/ask/index.d.ts +48 -0
- package/dist/ui/ask/index.js +67 -0
- package/dist/ui/base/index.d.ts +8 -1
- package/dist/ui/base/index.js +19 -4
- package/dist/ui/elements/PaintCanvas/index.d.ts +13 -3
- package/dist/ui/elements/PaintCanvas/index.js +25 -6
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +15 -7
- package/dist/ui/elements/PcAppSettings/index.js +53 -62
- package/dist/ui/elements/PcBrushConfig/index.d.ts +26 -14
- package/dist/ui/elements/PcBrushConfig/index.js +214 -145
- package/dist/ui/elements/PcBrushConfig/style.js +1 -1
- package/dist/ui/elements/PcBrushTest/index.d.ts +1 -0
- package/dist/ui/elements/PcBrushTest/index.js +15 -9
- package/dist/ui/elements/PcBrushTest/style.js +1 -1
- package/dist/ui/elements/PcButton/index.d.ts +12 -3
- package/dist/ui/elements/PcButton/index.js +18 -10
- package/dist/ui/elements/PcButtonMenu/index.d.ts +88 -0
- package/dist/ui/elements/PcButtonMenu/index.js +125 -0
- package/dist/ui/elements/PcCanvas/index.d.ts +15 -1
- package/dist/ui/elements/PcCanvas/index.js +42 -19
- package/dist/ui/elements/PcCanvas/style.js +1 -1
- package/dist/ui/elements/PcClipboardTools/index.d.ts +11 -7
- package/dist/ui/elements/PcClipboardTools/index.js +30 -29
- package/dist/ui/elements/PcDialog/index.d.ts +46 -10
- package/dist/ui/elements/PcDialog/index.js +102 -51
- package/dist/ui/elements/PcDialogWindow/index.d.ts +141 -0
- package/dist/ui/elements/PcDialogWindow/index.js +385 -0
- package/dist/ui/elements/PcInputSettings/index.d.ts +2 -1
- package/dist/ui/elements/PcInputSettings/index.js +9 -15
- package/dist/ui/elements/PcLayerItem/index.d.ts +21 -6
- package/dist/ui/elements/PcLayerItem/index.js +53 -26
- package/dist/ui/elements/PcLayerItem/style.js +1 -1
- package/dist/ui/elements/PcPanelBrushes/index.d.ts +3 -2
- package/dist/ui/elements/PcPanelBrushes/index.js +7 -5
- package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
- package/dist/ui/elements/PcPanelColours/index.js +2 -2
- package/dist/ui/elements/PcPanelColours/style.js +1 -1
- package/dist/ui/elements/PcPanelLayers/index.js +1 -1
- package/dist/ui/elements/PcPanelLayers/style.js +1 -1
- package/dist/ui/elements/PcProfileEditor/index.d.ts +103 -0
- package/dist/ui/elements/PcProfileEditor/index.js +259 -0
- package/dist/ui/elements/PcProfileGraph/index.d.ts +84 -0
- package/dist/ui/elements/PcProfileGraph/index.js +317 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +9 -13
- package/dist/ui/elements/PcSliderRange/index.d.ts +147 -0
- package/dist/ui/elements/PcSliderRange/index.js +308 -0
- package/dist/ui/elements/PcStats/index.d.ts +19 -4
- package/dist/ui/elements/PcStats/index.js +29 -13
- package/dist/ui/elements/PcToggleCheckbox/index.d.ts +13 -2
- package/dist/ui/elements/PcToggleCheckbox/index.js +27 -8
- package/dist/ui/elements/PcToolSettings/index.d.ts +1 -0
- package/dist/ui/elements/PcToolSettings/index.js +14 -14
- package/dist/ui/elements/PcVersion/index.d.ts +12 -0
- package/dist/ui/elements/PcVersion/index.js +43 -0
- package/dist/ui/icons/images/icon-profile-graph@2x.js +3 -0
- package/dist/ui/icons/index.d.ts +1 -1
- package/dist/ui/icons/index.js +2 -0
- package/dist/ui/index.d.ts +9 -2
- package/dist/ui/index.js +9 -2
- package/dist/ui/layout/index.d.ts +2 -2
- package/dist/ui/layout/index.js +2 -2
- package/dist/ui/styles/index.d.ts +21 -1
- package/dist/ui/styles/index.js +73 -14
- package/locales/en-GB.json +35 -0
- package/locales/en-US.json +35 -0
- package/locales/fr.json +35 -0
- package/locales/ja.json +35 -0
- package/locales/ko.json +35 -0
- package/locales/zh-Hans.json +35 -0
- package/locales/zh-Hant.json +35 -0
- package/package.json +25 -23
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { PcElement, defineElement } from "../../base/index.js";
|
|
2
|
+
import { css, html, nothing } from "lit";
|
|
3
|
+
// src/ui/elements/PcDialogWindow/index.ts
|
|
4
|
+
/** Event fired when the user asks to close a dialog window (detail: `{ reason }`); cancel it to keep the window open */
|
|
5
|
+
var DIALOG_DISMISS_EVENT = "pc-dismiss";
|
|
6
|
+
/** Header and footer slots, from start to end */
|
|
7
|
+
var REGIONS = [
|
|
8
|
+
"start",
|
|
9
|
+
"middle",
|
|
10
|
+
"end"
|
|
11
|
+
];
|
|
12
|
+
/** Reads a boolean attribute that's on unless it's `"false"` (for options that are on by default) */
|
|
13
|
+
var onUnlessFalse = {
|
|
14
|
+
fromAttribute: (value) => value !== null && value !== "false",
|
|
15
|
+
toAttribute: (value) => value ? "" : "false"
|
|
16
|
+
};
|
|
17
|
+
/** How many open modal windows are keeping the page from scrolling */
|
|
18
|
+
var scrollLocks = 0;
|
|
19
|
+
/** The page's own `overflow` while windows keep it from scrolling, to put back */
|
|
20
|
+
var pageOverflow = "";
|
|
21
|
+
/**
|
|
22
|
+
* Keep the page from scrolling (or let it scroll again once every window that kept it from scrolling has closed).
|
|
23
|
+
*/
|
|
24
|
+
var lockPageScroll = (lock) => {
|
|
25
|
+
const root = document.documentElement;
|
|
26
|
+
if (lock && scrollLocks++ === 0) {
|
|
27
|
+
pageOverflow = root.style.overflow;
|
|
28
|
+
root.style.overflow = "hidden";
|
|
29
|
+
} else if (!lock && --scrollLocks === 0) root.style.overflow = pageOverflow;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The element with keyboard focus, inside shadow roots too.
|
|
33
|
+
*/
|
|
34
|
+
var focusedElement = () => {
|
|
35
|
+
let focused = document.activeElement;
|
|
36
|
+
while (focused?.shadowRoot?.activeElement) focused = focused.shadowRoot.activeElement;
|
|
37
|
+
return focused;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Whether a named slot has content (only elements can go in named slots).
|
|
41
|
+
*/
|
|
42
|
+
var hasContent = (slot) => slot.assignedElements({ flatten: true }).length > 0;
|
|
43
|
+
/**
|
|
44
|
+
* A window over the page, built on the browser's `<dialog>`: centred in the viewport, never bigger than it, with a
|
|
45
|
+
* header, a body and a footer. While it's `open`, the rest of the page can't be used or scrolled and keyboard focus stays
|
|
46
|
+
* in it (moving to its first control, and back to where it was once it closes).
|
|
47
|
+
*
|
|
48
|
+
* Content goes in its slots: `header-start`, `header-middle` (which names the window for screen readers, unless it has a
|
|
49
|
+
* `label`; `description` describes it), `header-end`, `body` (or no slot), `footer-start`, `footer-middle` and `footer-end`. The body scrolls when
|
|
50
|
+
* there's too much to fit, keeping the header and footer in view. The header and footer only show with content (or the
|
|
51
|
+
* ⨉ button).
|
|
52
|
+
*
|
|
53
|
+
* Options:
|
|
54
|
+
* - `closable` (on by default; `closable="false"` turns it off): a ⨉ button at the top right.
|
|
55
|
+
* - `shaded`: shade the page under it.
|
|
56
|
+
* - `dismissible` (on by default; `dismissible="false"` turns it off): a press outside it closes it.
|
|
57
|
+
* - `inline`: cover only its nearest positioned ancestor instead of the whole page, which stays usable (and scrolls).
|
|
58
|
+
*
|
|
59
|
+
* The ⨉ button, a press outside it (while `dismissible`) and Escape (while `closable` or `dismissible`) fire a cancellable
|
|
60
|
+
* `pc-dismiss` event (detail: `{ reason }`, one of `close`, `outside` or `escape`); unless it's cancelled, the window
|
|
61
|
+
* closes (`open` becomes false). `--pc-dialog-width` sets its width (fitted to its content by default).
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* html`<pc-dialog-window open shaded label="Keyboard shortcuts" @pc-dismiss=${close}>
|
|
65
|
+
* <h2 slot="header-middle">Keyboard shortcuts</h2>
|
|
66
|
+
* <pc-shortcut-settings slot="body" .settings=${settings}></pc-shortcut-settings>
|
|
67
|
+
* <pc-button slot="footer-end" @click=${close}>Done</pc-button>
|
|
68
|
+
* </pc-dialog-window>`
|
|
69
|
+
*/
|
|
70
|
+
var PcDialogWindow = class extends PcElement {
|
|
71
|
+
static properties = {
|
|
72
|
+
...PcElement.properties,
|
|
73
|
+
open: {
|
|
74
|
+
type: Boolean,
|
|
75
|
+
reflect: true
|
|
76
|
+
},
|
|
77
|
+
closable: {
|
|
78
|
+
converter: onUnlessFalse,
|
|
79
|
+
reflect: true
|
|
80
|
+
},
|
|
81
|
+
shaded: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
reflect: true
|
|
84
|
+
},
|
|
85
|
+
dismissible: {
|
|
86
|
+
converter: onUnlessFalse,
|
|
87
|
+
reflect: true
|
|
88
|
+
},
|
|
89
|
+
inline: {
|
|
90
|
+
type: Boolean,
|
|
91
|
+
reflect: true
|
|
92
|
+
},
|
|
93
|
+
label: { type: String },
|
|
94
|
+
description: { type: String },
|
|
95
|
+
filled: { state: true }
|
|
96
|
+
};
|
|
97
|
+
static styles = [PcElement.styles, css`
|
|
98
|
+
:host {
|
|
99
|
+
display: contents;
|
|
100
|
+
--pc-dialog-gutter: var(--pc-space-3);
|
|
101
|
+
}
|
|
102
|
+
/* Inline: over the nearest positioned ancestor, while open */
|
|
103
|
+
:host([inline]) {
|
|
104
|
+
position: absolute;
|
|
105
|
+
z-index: 20;
|
|
106
|
+
inset: 0;
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
box-sizing: border-box;
|
|
111
|
+
padding: var(--pc-dialog-gutter);
|
|
112
|
+
}
|
|
113
|
+
:host([inline]:not([open])) {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
116
|
+
:host([inline][shaded]) {
|
|
117
|
+
background: var(--pc-backdrop);
|
|
118
|
+
}
|
|
119
|
+
dialog {
|
|
120
|
+
max-width: none;
|
|
121
|
+
max-height: none;
|
|
122
|
+
margin: auto;
|
|
123
|
+
padding: 0;
|
|
124
|
+
border: 0;
|
|
125
|
+
background: transparent;
|
|
126
|
+
color: var(--pc-text);
|
|
127
|
+
overflow: visible;
|
|
128
|
+
}
|
|
129
|
+
dialog[open] {
|
|
130
|
+
display: flex;
|
|
131
|
+
}
|
|
132
|
+
:host([inline]) dialog {
|
|
133
|
+
position: static;
|
|
134
|
+
max-height: 100%;
|
|
135
|
+
margin: 0;
|
|
136
|
+
}
|
|
137
|
+
dialog::backdrop {
|
|
138
|
+
background: transparent;
|
|
139
|
+
}
|
|
140
|
+
:host([shaded]) dialog::backdrop {
|
|
141
|
+
background: var(--pc-backdrop);
|
|
142
|
+
}
|
|
143
|
+
.window {
|
|
144
|
+
display: flex;
|
|
145
|
+
box-sizing: border-box;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
gap: var(--pc-space-2);
|
|
148
|
+
width: var(--pc-dialog-width, auto);
|
|
149
|
+
max-width: calc(100vw - 2 * var(--pc-dialog-gutter));
|
|
150
|
+
/* The visible viewport's height where browsers have it, so phones' toolbars don't cover the footer */
|
|
151
|
+
max-height: calc(100vh - 2 * var(--pc-dialog-gutter));
|
|
152
|
+
max-height: calc(100dvh - 2 * var(--pc-dialog-gutter));
|
|
153
|
+
padding: var(--pc-space-3);
|
|
154
|
+
border-radius: var(--pc-radius);
|
|
155
|
+
background: var(--pc-surface-raised);
|
|
156
|
+
color: var(--pc-text);
|
|
157
|
+
box-shadow: var(--pc-shadow);
|
|
158
|
+
font-family: var(--pc-font);
|
|
159
|
+
font-size: var(--pc-font-size);
|
|
160
|
+
}
|
|
161
|
+
:host([inline]) .window {
|
|
162
|
+
max-height: 100%;
|
|
163
|
+
}
|
|
164
|
+
header,
|
|
165
|
+
footer {
|
|
166
|
+
display: flex;
|
|
167
|
+
flex: none;
|
|
168
|
+
align-items: center;
|
|
169
|
+
gap: var(--pc-space-2);
|
|
170
|
+
}
|
|
171
|
+
[hidden] {
|
|
172
|
+
display: none !important;
|
|
173
|
+
}
|
|
174
|
+
.start,
|
|
175
|
+
.end {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex: none;
|
|
178
|
+
align-items: center;
|
|
179
|
+
gap: var(--pc-space-2);
|
|
180
|
+
}
|
|
181
|
+
.middle {
|
|
182
|
+
display: flex;
|
|
183
|
+
flex: 1;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
gap: var(--pc-space-2);
|
|
186
|
+
min-width: 0;
|
|
187
|
+
}
|
|
188
|
+
.end {
|
|
189
|
+
margin-left: auto;
|
|
190
|
+
}
|
|
191
|
+
/* Scrolls when there's too much to fit; the padding keeps focus outlines in view */
|
|
192
|
+
.body {
|
|
193
|
+
flex: 1 1 auto;
|
|
194
|
+
min-height: 0;
|
|
195
|
+
margin: calc(-1 * var(--pc-space-1));
|
|
196
|
+
padding: var(--pc-space-1);
|
|
197
|
+
overflow: auto;
|
|
198
|
+
overscroll-behavior: contain;
|
|
199
|
+
}
|
|
200
|
+
button.close {
|
|
201
|
+
display: inline-flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
justify-content: center;
|
|
204
|
+
width: var(--pc-control-size);
|
|
205
|
+
height: var(--pc-control-size);
|
|
206
|
+
padding: 0;
|
|
207
|
+
border: 0;
|
|
208
|
+
border-radius: var(--pc-radius-pill);
|
|
209
|
+
background: transparent;
|
|
210
|
+
color: var(--pc-text);
|
|
211
|
+
font: inherit;
|
|
212
|
+
font-size: 20px;
|
|
213
|
+
line-height: 1;
|
|
214
|
+
cursor: pointer;
|
|
215
|
+
}
|
|
216
|
+
button.close:hover {
|
|
217
|
+
background: var(--pc-surface-hover);
|
|
218
|
+
}
|
|
219
|
+
button.close:focus-visible {
|
|
220
|
+
outline: var(--pc-focus-width) solid var(--pc-focus);
|
|
221
|
+
}
|
|
222
|
+
`];
|
|
223
|
+
/** Whether it's keeping the page from scrolling */
|
|
224
|
+
scrollLocked = false;
|
|
225
|
+
/** What had focus before it opened, to give it back to if it's taken out of the page while open */
|
|
226
|
+
returnFocus = null;
|
|
227
|
+
/** Whether the `<dialog>` was last shown inline (not modal) */
|
|
228
|
+
shownInline = false;
|
|
229
|
+
/** Whether the press being made started outside the window */
|
|
230
|
+
pressedOutside = false;
|
|
231
|
+
constructor() {
|
|
232
|
+
super();
|
|
233
|
+
this.open = false;
|
|
234
|
+
this.closable = true;
|
|
235
|
+
this.shaded = false;
|
|
236
|
+
this.dismissible = true;
|
|
237
|
+
this.inline = false;
|
|
238
|
+
this.filled = /* @__PURE__ */ new Set();
|
|
239
|
+
}
|
|
240
|
+
/** The `<dialog>` it's built on */
|
|
241
|
+
get dialog() {
|
|
242
|
+
return this.renderRoot.querySelector("dialog");
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Ask to close the window, as the user would: fires `pc-dismiss` (detail: `{ reason }`), and closes it unless that's
|
|
246
|
+
* cancelled.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* dialogWindow.requestClose("close");
|
|
250
|
+
*/
|
|
251
|
+
requestClose(reason) {
|
|
252
|
+
const event = new CustomEvent(DIALOG_DISMISS_EVENT, {
|
|
253
|
+
detail: { reason },
|
|
254
|
+
cancelable: true
|
|
255
|
+
});
|
|
256
|
+
if (this.dispatchEvent(event)) this.open = false;
|
|
257
|
+
}
|
|
258
|
+
connectedCallback() {
|
|
259
|
+
super.connectedCallback();
|
|
260
|
+
if (this.hasUpdated) this.syncOpen();
|
|
261
|
+
}
|
|
262
|
+
disconnectedCallback() {
|
|
263
|
+
super.disconnectedCallback();
|
|
264
|
+
const dialog = this.dialog;
|
|
265
|
+
if (dialog?.open) {
|
|
266
|
+
dialog.close();
|
|
267
|
+
if (this.returnFocus?.isConnected) this.returnFocus.focus();
|
|
268
|
+
}
|
|
269
|
+
this.syncScrollLock();
|
|
270
|
+
}
|
|
271
|
+
firstUpdated(changed) {
|
|
272
|
+
super.firstUpdated(changed);
|
|
273
|
+
this.updateRegions();
|
|
274
|
+
}
|
|
275
|
+
updated(changed) {
|
|
276
|
+
super.updated(changed);
|
|
277
|
+
if (changed.has("open") || changed.has("inline")) this.syncOpen();
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Show or close the `<dialog>` to match `open` (modal unless `inline`), keeping the page from scrolling while a modal
|
|
281
|
+
* window is open.
|
|
282
|
+
*/
|
|
283
|
+
syncOpen() {
|
|
284
|
+
const dialog = this.dialog;
|
|
285
|
+
if (dialog.open && (!this.open || this.shownInline !== this.inline)) dialog.close();
|
|
286
|
+
if (this.open && !dialog.open && this.isConnected) {
|
|
287
|
+
this.shownInline = this.inline;
|
|
288
|
+
this.returnFocus = focusedElement();
|
|
289
|
+
if (this.inline) dialog.show();
|
|
290
|
+
else dialog.showModal();
|
|
291
|
+
}
|
|
292
|
+
this.syncScrollLock();
|
|
293
|
+
}
|
|
294
|
+
/** Keep the page from scrolling while a modal window is open in it, and let it scroll again otherwise */
|
|
295
|
+
syncScrollLock() {
|
|
296
|
+
const lock = this.open && !this.inline && this.isConnected;
|
|
297
|
+
if (lock === this.scrollLocked) return;
|
|
298
|
+
this.scrollLocked = lock;
|
|
299
|
+
lockPageScroll(lock);
|
|
300
|
+
}
|
|
301
|
+
/** Note which header and footer slots have content: only those (and the header and footer with them) are shown */
|
|
302
|
+
updateRegions() {
|
|
303
|
+
this.filled = new Set([...this.renderRoot.querySelectorAll("slot[name]")].filter(hasContent).map((slot) => slot.name));
|
|
304
|
+
}
|
|
305
|
+
/** Whether any of a header's or footer's slots have content */
|
|
306
|
+
hasRegion(region) {
|
|
307
|
+
return REGIONS.some((place) => this.filled.has(`${region}-${place}`));
|
|
308
|
+
}
|
|
309
|
+
onKeyDown = (event) => {
|
|
310
|
+
if (event.key !== "Escape" || event.defaultPrevented) return;
|
|
311
|
+
event.preventDefault();
|
|
312
|
+
if (this.closable || this.dismissible) this.requestClose("escape");
|
|
313
|
+
};
|
|
314
|
+
onCancel = (event) => {
|
|
315
|
+
event.preventDefault();
|
|
316
|
+
if (this.closable || this.dismissible) this.requestClose("escape");
|
|
317
|
+
};
|
|
318
|
+
onClose = () => {
|
|
319
|
+
if (this.isConnected && this.open && !this.dialog?.open) this.open = false;
|
|
320
|
+
};
|
|
321
|
+
onPointerDown = (event) => {
|
|
322
|
+
this.pressedOutside = event.target === event.currentTarget;
|
|
323
|
+
};
|
|
324
|
+
onClick = (event) => {
|
|
325
|
+
const outside = this.pressedOutside && event.target === event.currentTarget;
|
|
326
|
+
this.pressedOutside = false;
|
|
327
|
+
if (outside && this.dismissible) this.requestClose("outside");
|
|
328
|
+
};
|
|
329
|
+
/** A header or footer's start, middle and end slots */
|
|
330
|
+
renderRegion(region, end = nothing) {
|
|
331
|
+
return REGIONS.map((place) => html`<div
|
|
332
|
+
class=${place}
|
|
333
|
+
part="${region}-${place}"
|
|
334
|
+
?hidden=${!this.filled.has(`${region}-${place}`) && (place !== "end" || end === nothing)}
|
|
335
|
+
id=${region === "header" && place === "middle" ? "title" : nothing}
|
|
336
|
+
>
|
|
337
|
+
<slot
|
|
338
|
+
name="${region}-${place}"
|
|
339
|
+
@slotchange=${this.updateRegions}
|
|
340
|
+
></slot>
|
|
341
|
+
${place === "end" ? end : nothing}
|
|
342
|
+
</div>`);
|
|
343
|
+
}
|
|
344
|
+
render() {
|
|
345
|
+
const closeButton = this.closable ? html`<button
|
|
346
|
+
type="button"
|
|
347
|
+
class="close"
|
|
348
|
+
part="close"
|
|
349
|
+
aria-label=${this.t("paintCanvas.common.close")}
|
|
350
|
+
title=${this.t("paintCanvas.common.close")}
|
|
351
|
+
@click=${() => this.requestClose("close")}
|
|
352
|
+
>
|
|
353
|
+
⨉
|
|
354
|
+
</button>` : nothing;
|
|
355
|
+
return html`<dialog
|
|
356
|
+
part="dialog"
|
|
357
|
+
aria-label=${this.label || nothing}
|
|
358
|
+
aria-labelledby=${this.label ? nothing : "title"}
|
|
359
|
+
aria-description=${this.description || nothing}
|
|
360
|
+
@keydown=${this.onKeyDown}
|
|
361
|
+
@cancel=${this.onCancel}
|
|
362
|
+
@close=${this.onClose}
|
|
363
|
+
@pointerdown=${this.onPointerDown}
|
|
364
|
+
@click=${this.onClick}
|
|
365
|
+
>
|
|
366
|
+
<div class="window" part="window">
|
|
367
|
+
<header
|
|
368
|
+
part="header"
|
|
369
|
+
?hidden=${!this.hasRegion("header") && !this.closable}
|
|
370
|
+
>
|
|
371
|
+
${this.renderRegion("header", closeButton)}
|
|
372
|
+
</header>
|
|
373
|
+
<div class="body" part="body">
|
|
374
|
+
<slot name="body"></slot>
|
|
375
|
+
<slot></slot>
|
|
376
|
+
</div>
|
|
377
|
+
<footer part="footer" ?hidden=${!this.hasRegion("footer")}>
|
|
378
|
+
${this.renderRegion("footer")}
|
|
379
|
+
</footer>
|
|
380
|
+
</div>
|
|
381
|
+
</dialog>`;
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
defineElement("pc-dialog-window", PcDialogWindow);
|
|
385
|
+
export { DIALOG_DISMISS_EVENT, PcDialogWindow };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { nothing } from "lit";
|
|
2
2
|
import { PcElement } from "../../base";
|
|
3
|
+
import "../PcToggleCheckbox";
|
|
3
4
|
import { type InputSettingsData } from "../../../lib/classes/InputSettings";
|
|
4
5
|
import type { UiSettingsLike } from "../../../lib/classes/UiSettings";
|
|
5
6
|
/** Settings `pc-input-settings` can change: PaintCanvas's `InputSettings`, or anything with the same data and `update` */
|
|
@@ -69,7 +70,7 @@ export declare class PcInputSettings extends PcElement {
|
|
|
69
70
|
constructor();
|
|
70
71
|
/** Use the kept language when it changes */
|
|
71
72
|
protected willUpdate(): void;
|
|
72
|
-
/** A
|
|
73
|
+
/** A switch for an on/off setting */
|
|
73
74
|
private toggle;
|
|
74
75
|
/**
|
|
75
76
|
* The touch gestures: which gesture undoes, redoes, copies and pastes. Choosing a gesture another action uses swaps
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PcElement, defineElement, emit } from "../../base/index.js";
|
|
2
2
|
import ChangeController from "../../ChangeController/index.js";
|
|
3
|
+
import "../PcToggleCheckbox/index.js";
|
|
3
4
|
import { languageName } from "../../../lib/i18n/index.js";
|
|
4
5
|
import { isTouchDevice, zoomModifierName } from "../../../lib/classes/InputSettings/index.js";
|
|
5
6
|
import { GESTURE_ACTIONS, TOUCH_GESTURES, assignGesture } from "../../../lib/utils/gestures/index.js";
|
|
@@ -57,13 +58,7 @@ var PcInputSettings = class extends PcElement {
|
|
|
57
58
|
font-weight: bold;
|
|
58
59
|
}
|
|
59
60
|
.toggle {
|
|
60
|
-
display: flex;
|
|
61
61
|
grid-column: 1 / -1;
|
|
62
|
-
gap: var(--pc-space-2);
|
|
63
|
-
align-items: center;
|
|
64
|
-
}
|
|
65
|
-
input[type="checkbox"] {
|
|
66
|
-
accent-color: var(--pc-accent);
|
|
67
62
|
}
|
|
68
63
|
input:focus-visible,
|
|
69
64
|
select:focus-visible {
|
|
@@ -88,17 +83,16 @@ var PcInputSettings = class extends PcElement {
|
|
|
88
83
|
this.usedLocale = locale;
|
|
89
84
|
if (locale !== this.translations.locale) this.translations.setLocale(locale);
|
|
90
85
|
}
|
|
91
|
-
/** A
|
|
86
|
+
/** A switch for an on/off setting */
|
|
92
87
|
toggle(data, key, label) {
|
|
93
|
-
return html`<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
/>
|
|
88
|
+
return html`<pc-toggle-checkbox
|
|
89
|
+
class="toggle"
|
|
90
|
+
data-setting=${key}
|
|
91
|
+
.checked=${data[key]}
|
|
92
|
+
@change=${(e) => this.settings?.update({ [key]: e.detail.checked })}
|
|
93
|
+
>
|
|
100
94
|
${label}
|
|
101
|
-
</
|
|
95
|
+
</pc-toggle-checkbox>`;
|
|
102
96
|
}
|
|
103
97
|
/**
|
|
104
98
|
* The touch gestures: which gesture undoes, redoes, copies and pastes. Choosing a gesture another action uses swaps
|
|
@@ -2,6 +2,8 @@ import { PcElement } from "../../base";
|
|
|
2
2
|
import { BlendMode, type default as Layer } from "../../../lib/classes/Layer";
|
|
3
3
|
import type LayerStack from "../../../lib/classes/LayerStack";
|
|
4
4
|
import "../PcDialog";
|
|
5
|
+
import "../PcSliderRange";
|
|
6
|
+
import { type PromptFn } from "../../ask";
|
|
5
7
|
import "../PcContextMenu";
|
|
6
8
|
import { MENU_MARGIN, menuTop } from "../PcContextMenu";
|
|
7
9
|
import { type DialogChoice } from "../PcDialog";
|
|
@@ -12,7 +14,7 @@ export declare const BLEND_MODE_OPTIONS: readonly {
|
|
|
12
14
|
label: string;
|
|
13
15
|
}[];
|
|
14
16
|
/** Actions in the reorder handle's context menu */
|
|
15
|
-
export type LayerAction = "mask" | "duplicate" | "merge" | "clear" | "delete" | "up" | "top" | "down" | "bottom";
|
|
17
|
+
export type LayerAction = "rename" | "mask" | "duplicate" | "merge" | "clear" | "delete" | "up" | "top" | "down" | "bottom";
|
|
16
18
|
/** Context menu options in display order, with the translation keys of their labels */
|
|
17
19
|
export declare const LAYER_ACTIONS: readonly {
|
|
18
20
|
action: LayerAction;
|
|
@@ -61,7 +63,9 @@ export interface LayerDragDetail {
|
|
|
61
63
|
/** Where the layer actions menu goes and how close to the window's edges (see `pc-context-menu`) */
|
|
62
64
|
export { MENU_MARGIN, menuTop };
|
|
63
65
|
/**
|
|
64
|
-
* One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock
|
|
66
|
+
* One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock
|
|
67
|
+
* (padlock) and visibility (eye) icons. Pressing anywhere on the row (its controls too) selects the layer, which is then
|
|
68
|
+
* shown with a purple background (`active`). "Rename…" in the actions menu asks for a new name.
|
|
65
69
|
* Dragging the handle (with mouse, pen or touch) reorders the layer: the row fires {@link LAYER_DRAG_EVENT} and the
|
|
66
70
|
* layers menu shows where it will land. Clicking or tapping the handle, pressing Enter or Space on it, or right-clicking
|
|
67
71
|
* it opens the layer's actions menu (which can also reorder it). Cmd/Ctrl + click on the thumbnail or name (or "Create
|
|
@@ -96,6 +100,10 @@ export declare class PcLayerItem extends PcElement {
|
|
|
96
100
|
attribute: string;
|
|
97
101
|
reflect: boolean;
|
|
98
102
|
};
|
|
103
|
+
active: {
|
|
104
|
+
type: BooleanConstructor;
|
|
105
|
+
reflect: boolean;
|
|
106
|
+
};
|
|
99
107
|
translations: {
|
|
100
108
|
attribute: boolean;
|
|
101
109
|
};
|
|
@@ -116,6 +124,10 @@ export declare class PcLayerItem extends PcElement {
|
|
|
116
124
|
menuOpen: boolean;
|
|
117
125
|
/** The action the user is being asked to confirm, if any */
|
|
118
126
|
confirming?: ConfirmedAction;
|
|
127
|
+
/** Whether this row's layer is the selected one (shown with a purple background) */
|
|
128
|
+
active: boolean;
|
|
129
|
+
/** Asks the user for the layer's new name (replaceable for tests) */
|
|
130
|
+
prompt: PromptFn;
|
|
119
131
|
/** Whether this row's layer is being dragged (set by the layers menu; the row floats with the pointer) */
|
|
120
132
|
dragging: boolean;
|
|
121
133
|
/** While this row's layer is dragged: what dropping it now would do (set by the layers menu; shown by the cursor) */
|
|
@@ -128,6 +140,8 @@ export declare class PcLayerItem extends PcElement {
|
|
|
128
140
|
/** Device pixels per CSS pixel (replaceable for tests) */
|
|
129
141
|
pixelRatio: () => number;
|
|
130
142
|
constructor();
|
|
143
|
+
/** Pressing anywhere on the row selects its layer, except in the dialogs it shows (e.g. after deleting it) */
|
|
144
|
+
private readonly onClick;
|
|
131
145
|
/**
|
|
132
146
|
* Run a context menu action. Merging down, clearing and deleting ask for confirmation first (see `confirmed`).
|
|
133
147
|
*/
|
|
@@ -180,13 +194,14 @@ export declare class PcLayerItem extends PcElement {
|
|
|
180
194
|
*/
|
|
181
195
|
private requestMask;
|
|
182
196
|
/**
|
|
183
|
-
*
|
|
197
|
+
* Cmd/Ctrl + click on the thumbnail or name makes a mask from the layer's alpha (the click also selects the layer).
|
|
184
198
|
*/
|
|
185
|
-
private
|
|
199
|
+
private onDetailsClick;
|
|
186
200
|
/**
|
|
187
|
-
*
|
|
201
|
+
* Ask for the layer's new name and use it. Cancelling or an empty name leaves the name as it was.
|
|
188
202
|
*/
|
|
189
|
-
private
|
|
203
|
+
private rename;
|
|
204
|
+
protected willUpdate(changed: Map<PropertyKey, unknown>): void;
|
|
190
205
|
updated(): void;
|
|
191
206
|
/**
|
|
192
207
|
* Draw the layer's image, scaled to fit, on a checkerboard that shows transparency.
|
|
@@ -5,8 +5,11 @@ import ChangeController from "../../ChangeController/index.js";
|
|
|
5
5
|
import { BlendMode } from "../../../lib/classes/Layer/index.js";
|
|
6
6
|
import { drawCheckerboard, fitSize } from "../../../lib/rendering/checkerboard/index.js";
|
|
7
7
|
import { CANCEL_CHOICE } from "../PcDialog/index.js";
|
|
8
|
+
import "../PcSliderRange/index.js";
|
|
9
|
+
import { askText } from "../../ask/index.js";
|
|
8
10
|
import { MENU_MARGIN, menuTop } from "../PcContextMenu/index.js";
|
|
9
11
|
import { html, nothing, unsafeCSS } from "lit";
|
|
12
|
+
import { live } from "lit/directives/live.js";
|
|
10
13
|
// src/ui/elements/PcLayerItem/index.ts
|
|
11
14
|
/** Blend mode options shown in the layer's select input */
|
|
12
15
|
var BLEND_MODE_OPTIONS = [
|
|
@@ -37,6 +40,10 @@ var BLEND_MODE_OPTIONS = [
|
|
|
37
40
|
];
|
|
38
41
|
/** Context menu options in display order, with the translation keys of their labels */
|
|
39
42
|
var LAYER_ACTIONS = [
|
|
43
|
+
{
|
|
44
|
+
action: "rename",
|
|
45
|
+
label: "paintCanvas.layers.rename"
|
|
46
|
+
},
|
|
40
47
|
{
|
|
41
48
|
action: "mask",
|
|
42
49
|
label: "paintCanvas.layers.createMask"
|
|
@@ -129,7 +136,9 @@ var DRAG_THRESHOLD = 4;
|
|
|
129
136
|
/** Event a row dispatches while its layer is dragged by its reorder handle (detail: {@link LayerDragDetail}) */
|
|
130
137
|
var LAYER_DRAG_EVENT = "pc-layer-drag";
|
|
131
138
|
/**
|
|
132
|
-
* One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock
|
|
139
|
+
* One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock
|
|
140
|
+
* (padlock) and visibility (eye) icons. Pressing anywhere on the row (its controls too) selects the layer, which is then
|
|
141
|
+
* shown with a purple background (`active`). "Rename…" in the actions menu asks for a new name.
|
|
133
142
|
* Dragging the handle (with mouse, pen or touch) reorders the layer: the row fires {@link LAYER_DRAG_EVENT} and the
|
|
134
143
|
* layers menu shows where it will land. Clicking or tapping the handle, pressing Enter or Space on it, or right-clicking
|
|
135
144
|
* it opens the layer's actions menu (which can also reorder it). Cmd/Ctrl + click on the thumbnail or name (or "Create
|
|
@@ -154,6 +163,10 @@ var PcLayerItem = class extends PcElement {
|
|
|
154
163
|
dropAction: {
|
|
155
164
|
attribute: "drop-action",
|
|
156
165
|
reflect: true
|
|
166
|
+
},
|
|
167
|
+
active: {
|
|
168
|
+
type: Boolean,
|
|
169
|
+
reflect: true
|
|
157
170
|
}
|
|
158
171
|
};
|
|
159
172
|
static styles = [
|
|
@@ -162,6 +175,8 @@ var PcLayerItem = class extends PcElement {
|
|
|
162
175
|
unsafeCSS(style_default),
|
|
163
176
|
unsafeCSS(WINDOWS_CSS)
|
|
164
177
|
];
|
|
178
|
+
/** Asks the user for the layer's new name (replaceable for tests) */
|
|
179
|
+
prompt = (options) => askText(this.renderRoot, options);
|
|
165
180
|
/** The pointer pressed on the reorder handle, where it was pressed, and whether it has started dragging */
|
|
166
181
|
press;
|
|
167
182
|
/** Whether to ignore the next click on the handle (it ended a drag) */
|
|
@@ -173,7 +188,15 @@ var PcLayerItem = class extends PcElement {
|
|
|
173
188
|
super();
|
|
174
189
|
this.menuOpen = false;
|
|
175
190
|
this.dragging = false;
|
|
191
|
+
this.active = false;
|
|
192
|
+
this.addEventListener("click", this.onClick);
|
|
176
193
|
}
|
|
194
|
+
/** Pressing anywhere on the row selects its layer, except in the dialogs it shows (e.g. after deleting it) */
|
|
195
|
+
onClick = (event) => {
|
|
196
|
+
const { stack, layer } = this;
|
|
197
|
+
if (!stack || !layer || !stack.layers.includes(layer)) return;
|
|
198
|
+
if (!event.composedPath().some((target) => target instanceof Element && target.matches("pc-dialog")) && stack.active !== layer) stack.select(layer.id);
|
|
199
|
+
};
|
|
177
200
|
/**
|
|
178
201
|
* Run a context menu action. Merging down, clearing and deleting ask for confirmation first (see `confirmed`).
|
|
179
202
|
*/
|
|
@@ -182,6 +205,9 @@ var PcLayerItem = class extends PcElement {
|
|
|
182
205
|
this.menuOpen = false;
|
|
183
206
|
if (!stack || !layer || !this.canRun(action)) return;
|
|
184
207
|
switch (action) {
|
|
208
|
+
case "rename":
|
|
209
|
+
this.rename(stack, layer);
|
|
210
|
+
break;
|
|
185
211
|
case "mask":
|
|
186
212
|
this.requestMask(layer, {
|
|
187
213
|
shiftKey: false,
|
|
@@ -360,10 +386,9 @@ var PcLayerItem = class extends PcElement {
|
|
|
360
386
|
}));
|
|
361
387
|
}
|
|
362
388
|
/**
|
|
363
|
-
*
|
|
389
|
+
* Cmd/Ctrl + click on the thumbnail or name makes a mask from the layer's alpha (the click also selects the layer).
|
|
364
390
|
*/
|
|
365
|
-
|
|
366
|
-
stack.select(layer.id);
|
|
391
|
+
onDetailsClick(event, layer) {
|
|
367
392
|
if (!event.metaKey && !event.ctrlKey) return;
|
|
368
393
|
const target = event.composedPath()[0];
|
|
369
394
|
if (!(target instanceof Element) || !target.matches(".thumbnail, .name")) return;
|
|
@@ -371,15 +396,21 @@ var PcLayerItem = class extends PcElement {
|
|
|
371
396
|
this.requestMask(layer, event);
|
|
372
397
|
}
|
|
373
398
|
/**
|
|
374
|
-
*
|
|
399
|
+
* Ask for the layer's new name and use it. Cancelling or an empty name leaves the name as it was.
|
|
375
400
|
*/
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
401
|
+
async rename(stack, layer) {
|
|
402
|
+
const name = (await this.prompt({
|
|
403
|
+
heading: this.t("paintCanvas.layers.renameTitle"),
|
|
404
|
+
label: this.t("paintCanvas.layers.name"),
|
|
405
|
+
value: layer.name,
|
|
406
|
+
confirm: this.t("paintCanvas.common.save"),
|
|
407
|
+
cancel: this.t("paintCanvas.common.cancel")
|
|
408
|
+
}))?.trim();
|
|
409
|
+
if (name && stack.layers.includes(layer)) stack.rename(layer.id, name);
|
|
410
|
+
}
|
|
411
|
+
willUpdate(changed) {
|
|
412
|
+
super.willUpdate(changed);
|
|
413
|
+
this.active = this.layer !== void 0 && this.stack?.active.id === this.layer.id;
|
|
383
414
|
}
|
|
384
415
|
updated() {
|
|
385
416
|
const id = this.layer?.id;
|
|
@@ -434,7 +465,6 @@ var PcLayerItem = class extends PcElement {
|
|
|
434
465
|
render() {
|
|
435
466
|
const { stack, layer } = this;
|
|
436
467
|
if (!stack || !layer) return html``;
|
|
437
|
-
const active = stack.active.id === layer.id;
|
|
438
468
|
return html`
|
|
439
469
|
<pc-context-menu
|
|
440
470
|
class="handle"
|
|
@@ -451,23 +481,20 @@ var PcLayerItem = class extends PcElement {
|
|
|
451
481
|
>
|
|
452
482
|
<div
|
|
453
483
|
class="details"
|
|
454
|
-
@click=${(e) => this.
|
|
484
|
+
@click=${(e) => this.onDetailsClick(e, layer)}
|
|
455
485
|
>
|
|
456
486
|
<canvas class="thumbnail" aria-hidden="true"></canvas>
|
|
457
|
-
<
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
/>
|
|
463
|
-
<input
|
|
464
|
-
type="range"
|
|
487
|
+
<span class="name" title=${layer.name}>${layer.name}</span>
|
|
488
|
+
<pc-slider-range
|
|
489
|
+
single
|
|
490
|
+
class="opacity"
|
|
491
|
+
label=${this.t("paintCanvas.common.opacity")}
|
|
465
492
|
min="0"
|
|
466
493
|
max="100"
|
|
467
|
-
|
|
468
|
-
.value=${
|
|
469
|
-
@change=${(e) => stack.update(layer.id, { opacity:
|
|
470
|
-
|
|
494
|
+
unit="%"
|
|
495
|
+
.value=${live(Math.round(layer.opacity * 100))}
|
|
496
|
+
@pc-change=${(e) => stack.update(layer.id, { opacity: e.detail.value / 100 })}
|
|
497
|
+
></pc-slider-range>
|
|
471
498
|
<select
|
|
472
499
|
aria-label=${this.t("paintCanvas.layers.blendMode")}
|
|
473
500
|
@change=${(e) => stack.update(layer.id, { blendMode: e.target.value })}
|