@lvl99dotcom/paintcanvas 0.5.0 → 0.6.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/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 +26 -3
- package/dist/lib/classes/PaintCanvas/index.js +39 -12
- 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/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 +49 -0
- package/dist/lib/settings/schema.js +6 -0
- package/dist/lib/validation/validators.generated.js +13064 -3696
- package/dist/locales/en-GB.js +30 -0
- package/dist/locales/en-US.js +30 -0
- package/dist/locales/fr.js +30 -0
- package/dist/locales/ja.js +30 -0
- package/dist/locales/ko.js +30 -0
- package/dist/locales/zh-Hans.js +30 -0
- package/dist/locales/zh-Hant.js +30 -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.js +3 -1
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +6 -6
- package/dist/ui/elements/PcAppSettings/index.js +21 -65
- package/dist/ui/elements/PcBrushConfig/index.d.ts +25 -14
- package/dist/ui/elements/PcBrushConfig/index.js +216 -146
- package/dist/ui/elements/PcBrushConfig/style.js +1 -1
- package/dist/ui/elements/PcBrushTest/index.js +7 -2
- 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/PcCanvas/index.d.ts +6 -1
- package/dist/ui/elements/PcCanvas/index.js +10 -3
- package/dist/ui/elements/PcCanvas/style.js +1 -1
- package/dist/ui/elements/PcClipboardTools/index.d.ts +5 -4
- package/dist/ui/elements/PcClipboardTools/index.js +12 -5
- 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/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 +307 -0
- package/dist/ui/elements/PcStats/index.d.ts +19 -4
- package/dist/ui/elements/PcStats/index.js +29 -13
- package/dist/ui/elements/PcToolSettings/index.js +6 -6
- 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 +6 -1
- package/dist/ui/index.js +6 -1
- package/dist/ui/styles/index.d.ts +21 -1
- package/dist/ui/styles/index.js +73 -14
- package/locales/en-GB.json +30 -0
- package/locales/en-US.json +30 -0
- package/locales/fr.json +30 -0
- package/locales/ja.json +30 -0
- package/locales/ko.json +30 -0
- package/locales/zh-Hans.json +30 -0
- package/locales/zh-Hant.json +30 -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 { PcPanel } from "../PcPanel";
|
|
3
|
+
import { type ConfirmFn } from "../../ask";
|
|
3
4
|
import "../PcBrushSize";
|
|
4
5
|
import type PaintCanvas from "../../../lib/classes/PaintCanvas";
|
|
5
6
|
import type BrushSettings from "../../../lib/classes/BrushSettings";
|
|
@@ -8,8 +9,7 @@ import { DELETE_CURSOR } from "../../../lib/rendering/cursors";
|
|
|
8
9
|
import { type SaveMethod } from "../../../lib/rendering/screenshot";
|
|
9
10
|
/** Cursor over a preset that a click deletes (Alt/Option held): the `--pc-delete-cursor` token's default */
|
|
10
11
|
export { DELETE_CURSOR };
|
|
11
|
-
|
|
12
|
-
export type ConfirmFn = (message: string) => boolean;
|
|
12
|
+
export type { ConfirmFn } from "../../ask";
|
|
13
13
|
/** Largest shape preview in a preset tile, in CSS pixels */
|
|
14
14
|
export declare const MAX_PREVIEW_SIZE = 30;
|
|
15
15
|
/** Smallest shape preview in a preset tile, in CSS pixels */
|
|
@@ -73,6 +73,7 @@ export declare class PcPanelBrushes extends PcPanel {
|
|
|
73
73
|
app?: PaintCanvas;
|
|
74
74
|
/** Which settings this menu controls */
|
|
75
75
|
kind: "brush" | "eraser";
|
|
76
|
+
/** Asks the user to confirm, in a dialog (replaceable for tests) */
|
|
76
77
|
confirm: ConfirmFn;
|
|
77
78
|
/** Saves exported presets (replaceable for tests) */
|
|
78
79
|
saveFile: (blob: Blob, name: string) => Promise<SaveMethod | undefined>;
|
|
@@ -3,6 +3,7 @@ import { BRUSH_PRESET_FILE_FORMAT } from "../../../lib/presets/schema.js";
|
|
|
3
3
|
import { NO_TEXT_SELECTION_CSS, PILLS_CSS, WINDOWS_CSS } from "../../styles/index.js";
|
|
4
4
|
import { defineElement } from "../../base/index.js";
|
|
5
5
|
import { PcPanel } from "../PcPanel/index.js";
|
|
6
|
+
import { askConfirm, deletePresetQuestion } from "../../ask/index.js";
|
|
6
7
|
import "../PcBrushSize/index.js";
|
|
7
8
|
import ChangeController from "../../ChangeController/index.js";
|
|
8
9
|
import { groupPresetsByAuthor } from "../../../lib/brushes/authors/index.js";
|
|
@@ -77,7 +78,8 @@ var PcPanelBrushes = class extends PcPanel {
|
|
|
77
78
|
unsafeCSS(WINDOWS_CSS),
|
|
78
79
|
unsafeCSS(PILLS_CSS)
|
|
79
80
|
];
|
|
80
|
-
confirm
|
|
81
|
+
/** Asks the user to confirm, in a dialog (replaceable for tests) */
|
|
82
|
+
confirm = (options) => askConfirm(this.renderRoot, options);
|
|
81
83
|
/** Saves exported presets (replaceable for tests) */
|
|
82
84
|
saveFile = (blob, name) => saveFile(blob, name, void 0, JSON_FILE_TYPE);
|
|
83
85
|
changes = new ChangeController(this, () => [this.app, this.settings]);
|
|
@@ -127,7 +129,7 @@ var PcPanelBrushes = class extends PcPanel {
|
|
|
127
129
|
const app = this.app;
|
|
128
130
|
if (!app) return;
|
|
129
131
|
const name = preset.name ?? preset.id;
|
|
130
|
-
if (!this.confirm(this.
|
|
132
|
+
if (!await this.confirm(deletePresetQuestion(this.translations, name))) return;
|
|
131
133
|
try {
|
|
132
134
|
await app.customPresets.remove(preset.id);
|
|
133
135
|
this.emit("pc-toast", {
|
|
@@ -287,14 +289,14 @@ var PcPanelBrushes = class extends PcPanel {
|
|
|
287
289
|
<div class="actions">
|
|
288
290
|
<button
|
|
289
291
|
type="button"
|
|
290
|
-
class="pill new-preset"
|
|
292
|
+
class="pill-s new-preset"
|
|
291
293
|
@click=${() => this.emit("pc-new-preset", this.kind)}
|
|
292
294
|
>
|
|
293
295
|
${this.t("paintCanvas.brushMenu.create")}
|
|
294
296
|
</button>
|
|
295
297
|
<button
|
|
296
298
|
type="button"
|
|
297
|
-
class="pill import"
|
|
299
|
+
class="pill-s import"
|
|
298
300
|
@click=${() => this.renderRoot.querySelector(".import-file")?.click()}
|
|
299
301
|
>
|
|
300
302
|
${this.t("paintCanvas.brushMenu.import")}
|
|
@@ -317,7 +319,7 @@ var PcPanelBrushes = class extends PcPanel {
|
|
|
317
319
|
</label>
|
|
318
320
|
<button
|
|
319
321
|
type="button"
|
|
320
|
-
class="pill export"
|
|
322
|
+
class="pill-s export"
|
|
321
323
|
@click=${() => void this.exportFile()}
|
|
322
324
|
>
|
|
323
325
|
${this.t("paintCanvas.brushMenu.export")}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/ui/elements/PcPanelBrushes/style.css?inline
|
|
2
|
-
var style_default = "/* Narrower than other panels by default (set --pc-panel-width to change it) */\n:host {\n --pc-panel-default-width: 240px;\n}\n/* The current preset on one row: its shape, name and size */\n.current {\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: 44px;\n padding: 4px 8px;\n border: 1px solid var(--pc-border);\n background: var(--pc-surface);\n}\n.current-shape {\n flex: none;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: var(--pc-text);\n}\n.current-shape.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: contain;\n mask-position: center;\n}\n.name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.size {\n flex: none;\n align-self: stretch;\n margin: -4px -8px -4px 0;\n border-left: 1px solid var(--pc-border);\n}\n/* The presets scroll, at most 400px tall, or less so the whole menu fits under the settings bar in a shorter window */\n.presets {\n flex: 1 1 auto;\n min-height: 0;\n max-height: min(400px, calc(100vh - 180px));\n overflow-y: auto;\n}\nh3 {\n margin: 4px 0;\n font-size: 14px;\n}\nul {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n align-content: start;\n margin: 0;\n padding: 0;\n list-style: none;\n border: 1px solid var(--pc-border);\n background: var(--pc-bar);\n}\n:host([deleting]) li button.custom {\n cursor: var(--pc-delete-cursor);\n}\n/* Create, Import and Export side by side */\n.actions {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 6px;\n}\n.
|
|
2
|
+
var style_default = "/* Narrower than other panels by default (set --pc-panel-width to change it) */\n:host {\n --pc-panel-default-width: 240px;\n}\n/* The current preset on one row: its shape, name and size */\n.current {\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: 44px;\n padding: 4px 8px;\n border: 1px solid var(--pc-border);\n background: var(--pc-surface);\n}\n.current-shape {\n flex: none;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: var(--pc-text);\n}\n.current-shape.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: contain;\n mask-position: center;\n}\n.name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.size {\n flex: none;\n align-self: stretch;\n margin: -4px -8px -4px 0;\n border-left: 1px solid var(--pc-border);\n}\n/* The presets scroll, at most 400px tall, or less so the whole menu fits under the settings bar in a shorter window */\n.presets {\n flex: 1 1 auto;\n min-height: 0;\n max-height: min(400px, calc(100vh - 180px));\n overflow-y: auto;\n}\nh3 {\n margin: 4px 0;\n font-size: 14px;\n}\nul {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n align-content: start;\n margin: 0;\n padding: 0;\n list-style: none;\n border: 1px solid var(--pc-border);\n background: var(--pc-bar);\n}\n:host([deleting]) li button.custom {\n cursor: var(--pc-delete-cursor);\n}\n/* Create, Import and Export side by side */\n.actions {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 6px;\n}\n.visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\nli button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n aspect-ratio: 1;\n padding: 0;\n border: none;\n border-right: 1px solid var(--pc-border);\n border-bottom: 1px solid var(--pc-border);\n background: var(--pc-bar);\n cursor: pointer;\n}\nli button:hover {\n background: var(--pc-surface-hover);\n}\nli button[aria-pressed=\"true\"] {\n background: var(--pc-surface-active);\n}\n.shape {\n background: var(--pc-text);\n border-radius: 50%;\n}\n.shape.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: 100% 100%;\n}\n";
|
|
3
3
|
export { style_default as default };
|
|
@@ -613,14 +613,14 @@ var PcPanelColours = class extends PcPanel {
|
|
|
613
613
|
<div class="actions">
|
|
614
614
|
<button
|
|
615
615
|
type="button"
|
|
616
|
-
class="pill import"
|
|
616
|
+
class="pill-s import"
|
|
617
617
|
@click=${() => this.renderRoot.querySelector(".import-file")?.click()}
|
|
618
618
|
>
|
|
619
619
|
${this.t("paintCanvas.palette.import")}
|
|
620
620
|
</button>
|
|
621
621
|
<button
|
|
622
622
|
type="button"
|
|
623
|
-
class="pill export"
|
|
623
|
+
class="pill-s export"
|
|
624
624
|
@click=${() => void this.exportFile()}
|
|
625
625
|
>
|
|
626
626
|
${this.t("paintCanvas.palette.export")}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/ui/elements/PcPanelColours/style.css?inline
|
|
2
|
-
var style_default = ".palette {\n display: flex;\n flex-direction: column;\n gap: var(--pc-space-2);\n}\n\n/* The colours and their hex value beside the colour model's sliders */\n.top {\n display: flex;\n gap: var(--pc-space-2);\n}\n.current {\n display: flex;\n flex: none;\n flex-direction: column;\n width: 76px;\n border: 1px solid var(--pc-border);\n}\n.current pc-colour-pair {\n --pc-colour-pair-size: 76px;\n}\n.hex {\n box-sizing: border-box;\n width: 100%;\n min-height: var(--pc-control-size);\n padding: var(--pc-space-1);\n border: 0;\n border-top: 1px solid var(--pc-border);\n background: var(--pc-surface);\n color: var(--pc-text);\n font: inherit;\n font-size: var(--pc-font-size-small);\n text-align: center;\n font-variant-numeric: tabular-nums;\n}\n.hex[aria-invalid=\"true\"] {\n outline: 2px solid var(--pc-dismiss-text);\n outline-offset: -2px;\n}\n.hex:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n outline-offset: -3px;\n}\n\n.channels {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-width: 0;\n border: 1px solid var(--pc-border);\n}\n[role=\"tablist\"] {\n display: flex;\n}\n[role=\"tab\"] {\n flex: 1;\n min-height: 28px;\n border: 0;\n border-bottom: 1px solid var(--pc-border);\n background: var(--pc-surface-active);\n color: var(--pc-text);\n font: inherit;\n cursor: pointer;\n}\n[role=\"tab\"] + [role=\"tab\"] {\n border-left: 1px solid var(--pc-border);\n}\n[role=\"tab\"][aria-selected=\"true\"] {\n border-bottom-color: transparent;\n background: var(--pc-surface);\n font-weight: bold;\n}\n[role=\"tab\"]:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n outline-offset: -3px;\n}\n.sliders {\n display: grid;\n flex: 1;\n grid-template-columns: auto minmax(0, 1fr) 3.6em;\n gap: var(--pc-space-1);\n align-items: center;\n padding: var(--pc-space-1) var(--pc-space-2);\n background: var(--pc-surface);\n}\n.letter {\n font-weight: bold;\n}\n/* Each slider's track shows the colours its values give (--track, set per slider), under a round white handle with a\n dark outline that shows on any colour */\n.sliders input[type=\"range\"] {\n width: 100%;\n min-width: 0;\n height: 20px;\n margin: 0;\n background: transparent;\n appearance: none;\n -webkit-appearance: none;\n cursor: pointer;\n}\n.sliders input[type=\"range\"]::-webkit-slider-runnable-track {\n height: 10px;\n border: 1px solid var(--pc-border);\n border-radius: var(--pc-radius-pill);\n background: var(--track);\n}\n.sliders input[type=\"range\"]::-moz-range-track {\n height: 10px;\n border: 1px solid var(--pc-border);\n border-radius: var(--pc-radius-pill);\n background: var(--track);\n}\n.sliders input[type=\"range\"]::-webkit-slider-thumb {\n width: 16px;\n height: 16px;\n margin-top: -4px;\n border: 2px solid #000000;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 0 0 1px #ffffff;\n -webkit-appearance: none;\n}\n.sliders input[type=\"range\"]::-moz-range-thumb {\n box-sizing: border-box;\n width: 16px;\n height: 16px;\n border: 2px solid #000000;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 0 0 1px #ffffff;\n}\n.sliders input[type=\"number\"] {\n box-sizing: border-box;\n width: 100%;\n padding: 2px var(--pc-space-1);\n border: 1px solid var(--pc-border);\n background: var(--pc-surface);\n color: var(--pc-text);\n font: inherit;\n font-size: var(--pc-font-size-small);\n font-variant-numeric: tabular-nums;\n}\n.sliders input:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n outline-offset: 1px;\n}\n\n/* Saved colours: square swatches in a grid that scrolls */\n.swatches {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));\n align-content: start;\n min-height: 64px;\n max-height: 132px;\n overflow-y: auto;\n border: 1px solid var(--pc-border);\n background: var(--pc-bar);\n}\n.swatch,\n.add {\n aspect-ratio: 1;\n min-width: 32px;\n padding: 0;\n border: 0;\n border-right: 1px solid var(--pc-border);\n border-bottom: 1px solid var(--pc-border);\n cursor: pointer;\n}\n.swatch {\n background: var(--colour);\n /* Long presses open the colour's menu instead of the browser's */\n -webkit-touch-callout: none;\n touch-action: manipulation;\n}\n.swatch[aria-pressed=\"true\"] {\n box-shadow:\n inset 0 0 0 2px #ffffff,\n inset 0 0 0 3px #000000;\n}\n:host([deleting]) .swatch {\n cursor: var(--pc-delete-cursor);\n}\n.add {\n background: var(--pc-surface);\n color: var(--pc-text);\n font: inherit;\n font-size: 20px;\n font-weight: bold;\n}\n.add:hover:not(:disabled) {\n background: var(--pc-surface-hover);\n}\n.add:disabled {\n color: var(--pc-disabled);\n cursor: default;\n}\n.swatch:focus-visible,\n.add:focus-visible {\n position: relative;\n z-index: 1;\n outline: var(--pc-focus-width) solid var(--pc-focus);\n outline-offset: -3px;\n}\n\n.actions {\n display: flex;\n gap: var(--pc-space-2);\n}\n/*
|
|
2
|
+
var style_default = ".palette {\n display: flex;\n flex-direction: column;\n gap: var(--pc-space-2);\n}\n\n/* The colours and their hex value beside the colour model's sliders */\n.top {\n display: flex;\n gap: var(--pc-space-2);\n}\n.current {\n display: flex;\n flex: none;\n flex-direction: column;\n width: 76px;\n border: 1px solid var(--pc-border);\n}\n.current pc-colour-pair {\n --pc-colour-pair-size: 76px;\n}\n.hex {\n box-sizing: border-box;\n width: 100%;\n min-height: var(--pc-control-size);\n padding: var(--pc-space-1);\n border: 0;\n border-top: 1px solid var(--pc-border);\n background: var(--pc-surface);\n color: var(--pc-text);\n font: inherit;\n font-size: var(--pc-font-size-small);\n text-align: center;\n font-variant-numeric: tabular-nums;\n}\n.hex[aria-invalid=\"true\"] {\n outline: 2px solid var(--pc-dismiss-text);\n outline-offset: -2px;\n}\n.hex:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n outline-offset: -3px;\n}\n\n.channels {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-width: 0;\n border: 1px solid var(--pc-border);\n}\n[role=\"tablist\"] {\n display: flex;\n}\n[role=\"tab\"] {\n flex: 1;\n min-height: 28px;\n border: 0;\n border-bottom: 1px solid var(--pc-border);\n background: var(--pc-surface-active);\n color: var(--pc-text);\n font: inherit;\n cursor: pointer;\n}\n[role=\"tab\"] + [role=\"tab\"] {\n border-left: 1px solid var(--pc-border);\n}\n[role=\"tab\"][aria-selected=\"true\"] {\n border-bottom-color: transparent;\n background: var(--pc-surface);\n font-weight: bold;\n}\n[role=\"tab\"]:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n outline-offset: -3px;\n}\n.sliders {\n display: grid;\n flex: 1;\n grid-template-columns: auto minmax(0, 1fr) 3.6em;\n gap: var(--pc-space-1);\n align-items: center;\n padding: var(--pc-space-1) var(--pc-space-2);\n background: var(--pc-surface);\n}\n.letter {\n font-weight: bold;\n}\n/* Each slider's track shows the colours its values give (--track, set per slider), under a round white handle with a\n dark outline that shows on any colour */\n.sliders input[type=\"range\"] {\n width: 100%;\n min-width: 0;\n height: 20px;\n margin: 0;\n background: transparent;\n appearance: none;\n -webkit-appearance: none;\n cursor: pointer;\n}\n.sliders input[type=\"range\"]::-webkit-slider-runnable-track {\n height: 10px;\n border: 1px solid var(--pc-border);\n border-radius: var(--pc-radius-pill);\n background: var(--track);\n}\n.sliders input[type=\"range\"]::-moz-range-track {\n height: 10px;\n border: 1px solid var(--pc-border);\n border-radius: var(--pc-radius-pill);\n background: var(--track);\n}\n.sliders input[type=\"range\"]::-webkit-slider-thumb {\n width: 16px;\n height: 16px;\n margin-top: -4px;\n border: 2px solid #000000;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 0 0 1px #ffffff;\n -webkit-appearance: none;\n}\n.sliders input[type=\"range\"]::-moz-range-thumb {\n box-sizing: border-box;\n width: 16px;\n height: 16px;\n border: 2px solid #000000;\n border-radius: 50%;\n background: #ffffff;\n box-shadow: 0 0 0 1px #ffffff;\n}\n.sliders input[type=\"number\"] {\n box-sizing: border-box;\n width: 100%;\n padding: 2px var(--pc-space-1);\n border: 1px solid var(--pc-border);\n background: var(--pc-surface);\n color: var(--pc-text);\n font: inherit;\n font-size: var(--pc-font-size-small);\n font-variant-numeric: tabular-nums;\n}\n.sliders input:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n outline-offset: 1px;\n}\n\n/* Saved colours: square swatches in a grid that scrolls */\n.swatches {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));\n align-content: start;\n min-height: 64px;\n max-height: 132px;\n overflow-y: auto;\n border: 1px solid var(--pc-border);\n background: var(--pc-bar);\n}\n.swatch,\n.add {\n aspect-ratio: 1;\n min-width: 32px;\n padding: 0;\n border: 0;\n border-right: 1px solid var(--pc-border);\n border-bottom: 1px solid var(--pc-border);\n cursor: pointer;\n}\n.swatch {\n background: var(--colour);\n /* Long presses open the colour's menu instead of the browser's */\n -webkit-touch-callout: none;\n touch-action: manipulation;\n}\n.swatch[aria-pressed=\"true\"] {\n box-shadow:\n inset 0 0 0 2px #ffffff,\n inset 0 0 0 3px #000000;\n}\n:host([deleting]) .swatch {\n cursor: var(--pc-delete-cursor);\n}\n.add {\n background: var(--pc-surface);\n color: var(--pc-text);\n font: inherit;\n font-size: 20px;\n font-weight: bold;\n}\n.add:hover:not(:disabled) {\n background: var(--pc-surface-hover);\n}\n.add:disabled {\n color: var(--pc-disabled);\n cursor: default;\n}\n.swatch:focus-visible,\n.add:focus-visible {\n position: relative;\n z-index: 1;\n outline: var(--pc-focus-width) solid var(--pc-focus);\n outline-offset: -3px;\n}\n\n.actions {\n display: flex;\n gap: var(--pc-space-2);\n}\n/* Import and Export share the row */\n.actions button.pill-s {\n flex: 1;\n}\n";
|
|
3
3
|
export { style_default as default };
|
|
@@ -199,7 +199,7 @@ var PcPanelLayers = class extends PcPanel {
|
|
|
199
199
|
${this.renderRows(layers)}
|
|
200
200
|
<button
|
|
201
201
|
type="button"
|
|
202
|
-
class="pill new ${this.drag?.action === "duplicate" ? "drop-target" : ""}"
|
|
202
|
+
class="pill-s new ${this.drag?.action === "duplicate" ? "drop-target" : ""}"
|
|
203
203
|
?disabled=${!layers.canCreate}
|
|
204
204
|
@click=${() => layers.create()}
|
|
205
205
|
>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/ui/elements/PcPanelLayers/style.css?inline
|
|
2
|
-
var style_default = "/* \"New Layer\": a full-width purple pill */\nbutton.new {\n width: 100%;\n
|
|
2
|
+
var style_default = "/* \"New Layer\": a full-width purple pill */\nbutton.new {\n width: 100%;\n}\n/* Where a dragged layer would land: a full-width block with its name, as tall as the row (set by the menu) */\n.ghost {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n padding: 0 8px;\n border: 2px dashed;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n/* Dropping a dragged layer here duplicates it (highlighted for touch, where there is no \"+\" cursor) */\nbutton.new.drop-target {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n outline-offset: 2px;\n}\n/* For screen readers only */\n.announcement {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n";
|
|
3
3
|
export { style_default as default };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { PcElement } from "../../base";
|
|
2
|
+
import { type ConfirmFn, type PromptFn } from "../../ask";
|
|
3
|
+
import "../PcDialogWindow";
|
|
4
|
+
import "../PcProfileGraph";
|
|
5
|
+
import "../PcContextMenu";
|
|
6
|
+
import "../PcButton";
|
|
7
|
+
import ProfileGraphs from "../../../lib/classes/ProfileGraphs";
|
|
8
|
+
import { type ProfilePoint } from "../../../lib/profiles";
|
|
9
|
+
/** What the editor's `pc-save` gives: the points to use and the graph they came from (undefined for an unsaved one) */
|
|
10
|
+
export interface ProfileSaveDetail {
|
|
11
|
+
points: ProfilePoint[];
|
|
12
|
+
profileId?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A window for editing a brush setting's profile graph (see `<pc-profile-graph>`): a drop-down of the presets and the
|
|
16
|
+
* user's own graphs (it says "Custom" once the graph is edited, or for one that isn't saved), a ⋮ menu (New graph,
|
|
17
|
+
* Duplicate…, Rename… and Delete…, the last two for the user's own graphs), the graph, and Save, Reset and Dismiss.
|
|
18
|
+
*
|
|
19
|
+
* - Choosing a graph in the drop-down loads its points.
|
|
20
|
+
* - New graph starts from a straight line, and Duplicate… saves a copy of the current graph under a name ("Custom" to
|
|
21
|
+
* start with); Rename… and Delete… ask first.
|
|
22
|
+
* - Save uses the graph (firing `pc-save` with its points and ID); an edited or unsaved graph is first saved with a name
|
|
23
|
+
* it asks for, asking before replacing another of the user's graphs with that name.
|
|
24
|
+
* - Reset goes back to the graph it opened with; Dismiss, ⨉ and Escape close it (`pc-close`) without changing anything.
|
|
25
|
+
*
|
|
26
|
+
* Graphs are saved in `profiles` (the canvas's `ProfileGraphs`).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* html`<pc-profile-editor .profiles=${app.profiles} .points=${points} profile-id="overweight" label="Width"
|
|
30
|
+
* @pc-save=${(e) => use(e.detail)} @pc-close=${close}></pc-profile-editor>`
|
|
31
|
+
*/
|
|
32
|
+
export declare class PcProfileEditor extends PcElement {
|
|
33
|
+
static properties: {
|
|
34
|
+
profiles: {
|
|
35
|
+
attribute: boolean;
|
|
36
|
+
};
|
|
37
|
+
points: {
|
|
38
|
+
attribute: boolean;
|
|
39
|
+
};
|
|
40
|
+
profileId: {
|
|
41
|
+
type: StringConstructor;
|
|
42
|
+
attribute: string;
|
|
43
|
+
};
|
|
44
|
+
label: {
|
|
45
|
+
type: StringConstructor;
|
|
46
|
+
};
|
|
47
|
+
draft: {
|
|
48
|
+
state: boolean;
|
|
49
|
+
};
|
|
50
|
+
sourceId: {
|
|
51
|
+
state: boolean;
|
|
52
|
+
};
|
|
53
|
+
translations: {
|
|
54
|
+
attribute: boolean;
|
|
55
|
+
};
|
|
56
|
+
icons: {
|
|
57
|
+
attribute: boolean;
|
|
58
|
+
};
|
|
59
|
+
theme: {
|
|
60
|
+
type: StringConstructor;
|
|
61
|
+
reflect: boolean;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
static styles: import("lit").CSSResultGroup[];
|
|
65
|
+
/** Where graphs are saved (the canvas's `ProfileGraphs`) */
|
|
66
|
+
profiles: ProfileGraphs;
|
|
67
|
+
/** The points it opens with */
|
|
68
|
+
points?: ProfilePoint[];
|
|
69
|
+
/** The ID of the graph it opens with, if it's one of the presets or saved graphs */
|
|
70
|
+
profileId?: string;
|
|
71
|
+
/** The setting's name, e.g. "Width" */
|
|
72
|
+
label: string;
|
|
73
|
+
/** The points being edited */
|
|
74
|
+
draft: ProfilePoint[];
|
|
75
|
+
/** The graph the points came from, until they're edited */
|
|
76
|
+
sourceId?: string;
|
|
77
|
+
/** Asks the user to confirm, in a dialog (replaceable for tests) */
|
|
78
|
+
confirm: ConfirmFn;
|
|
79
|
+
/** Asks the user for text, in a dialog (replaceable for tests) */
|
|
80
|
+
prompt: PromptFn;
|
|
81
|
+
constructor();
|
|
82
|
+
protected willUpdate(changed: Map<PropertyKey, unknown>): void;
|
|
83
|
+
/** Go back to the graph it opened with */
|
|
84
|
+
reset(): void;
|
|
85
|
+
/** Load a graph's points */
|
|
86
|
+
load(id: string): void;
|
|
87
|
+
/** Whether the graph being edited is one of the user's own, unchanged */
|
|
88
|
+
private get ownSource();
|
|
89
|
+
/**
|
|
90
|
+
* Ask for a name and save the points as one of the user's graphs, asking before replacing another with that name.
|
|
91
|
+
* Returns the saved graph's ID, or undefined if it wasn't saved.
|
|
92
|
+
*/
|
|
93
|
+
private saveAs;
|
|
94
|
+
/**
|
|
95
|
+
* Use the graph: save it first when it's edited or unsaved (asking for a name), then fire `pc-save`.
|
|
96
|
+
*/
|
|
97
|
+
save(): Promise<void>;
|
|
98
|
+
/** Do one of the ⋮ menu's actions */
|
|
99
|
+
act(action: string): Promise<void>;
|
|
100
|
+
/** A graph's name: the presets' in the chosen language */
|
|
101
|
+
private graphName;
|
|
102
|
+
render(): import("lit").TemplateResult<1>;
|
|
103
|
+
}
|