@lvl99dotcom/paintcanvas 0.3.1 → 0.4.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 +1 -1
- package/dist/lib/brushes/StrokeEngine/index.d.ts +2 -2
- package/dist/lib/brushes/StrokeEngine/index.js +26 -28
- package/dist/lib/classes/BrushSettings/index.d.ts +24 -0
- package/dist/lib/classes/BrushSettings/index.js +35 -1
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +17 -6
- package/dist/lib/classes/KeyboardShortcuts/index.js +81 -18
- package/dist/lib/classes/PaintCanvas/index.d.ts +77 -27
- package/dist/lib/classes/PaintCanvas/index.js +141 -41
- package/dist/lib/classes/PaintCanvas/tool.d.ts +15 -0
- package/dist/lib/classes/PaintCanvas/tool.js +18 -0
- package/dist/lib/classes/Palette/index.d.ts +58 -0
- package/dist/lib/classes/Palette/index.js +92 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +52 -0
- package/dist/lib/classes/SettingsAutosave/index.js +96 -0
- package/dist/lib/classes/ShortcutSettings/index.d.ts +63 -0
- package/dist/lib/classes/ShortcutSettings/index.js +110 -0
- package/dist/lib/palette/index.d.ts +49 -0
- package/dist/lib/palette/index.js +82 -0
- package/dist/lib/palette/schema.d.ts +45 -0
- package/dist/lib/palette/schema.js +41 -0
- package/dist/lib/rendering/cursors/index.d.ts +2 -0
- package/dist/lib/rendering/cursors/index.js +3 -1
- package/dist/lib/rendering/sample/index.js +3 -2
- package/dist/lib/settings/index.d.ts +63 -0
- package/dist/lib/settings/index.js +91 -0
- package/dist/lib/settings/schema.d.ts +150 -0
- package/dist/lib/settings/schema.js +102 -0
- package/dist/lib/storage/index.d.ts +36 -5
- package/dist/lib/storage/index.js +87 -29
- package/dist/lib/utils/colour/index.d.ts +72 -0
- package/dist/lib/utils/colour/index.js +154 -0
- package/dist/lib/utils/shortcuts/index.d.ts +174 -11
- package/dist/lib/utils/shortcuts/index.js +391 -145
- package/dist/lib/validation/validators.generated.d.ts +5 -0
- package/dist/lib/validation/validators.generated.js +872 -1
- package/dist/locales/en-GB.js +87 -1
- package/dist/locales/en-US.js +87 -1
- package/dist/locales/fr.js +87 -1
- package/dist/locales/ja.js +87 -1
- package/dist/locales/ko.js +87 -1
- package/dist/locales/zh-Hans.js +87 -1
- package/dist/locales/zh-Hant.js +87 -1
- package/dist/package.js +1 -1
- package/dist/ui/elements/PaintCanvas/index.d.ts +49 -47
- package/dist/ui/elements/PaintCanvas/index.js +121 -171
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +59 -0
- package/dist/ui/elements/PcAppSettings/index.js +222 -0
- package/dist/ui/elements/PcCanvas/index.d.ts +1 -1
- package/dist/ui/elements/PcCanvas/index.js +7 -5
- package/dist/ui/elements/PcColourPair/index.d.ts +43 -0
- package/dist/ui/elements/PcColourPair/index.js +125 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +70 -33
- package/dist/ui/elements/PcContextMenu/index.js +128 -97
- package/dist/ui/elements/PcLayerItem/index.d.ts +13 -34
- package/dist/ui/elements/PcLayerItem/index.js +39 -91
- package/dist/ui/elements/PcLayerItem/style.js +1 -1
- package/dist/ui/elements/PcMenu/index.d.ts +93 -0
- package/dist/ui/elements/PcMenu/index.js +204 -0
- package/dist/ui/elements/PcPane/index.d.ts +118 -0
- package/dist/ui/elements/PcPane/index.js +357 -0
- package/dist/ui/elements/PcPanel/index.d.ts +26 -8
- package/dist/ui/elements/PcPanel/index.js +76 -8
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.d.ts +20 -6
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.js +18 -14
- package/dist/ui/elements/PcPanelBrushes/style.js +3 -0
- package/dist/ui/elements/PcPanelColours/index.d.ts +171 -0
- package/dist/ui/elements/PcPanelColours/index.js +650 -0
- package/dist/ui/elements/PcPanelColours/style.js +3 -0
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.d.ts +17 -4
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.js +17 -10
- package/dist/ui/elements/PcPanelLayers/style.js +3 -0
- package/dist/ui/elements/PcShortcutSettings/index.d.ts +69 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +273 -0
- package/dist/ui/elements/PcToolSettings/index.js +2 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +47 -12
- package/dist/ui/elements/PcToolbar/index.js +77 -56
- package/dist/ui/elements/PcToolbar/style.js +1 -1
- package/dist/ui/index.d.ts +7 -1
- package/dist/ui/index.js +8 -2
- package/dist/ui/tokens/index.js +3 -1
- package/locales/en-GB.json +87 -1
- package/locales/en-US.json +87 -1
- package/locales/fr.json +87 -1
- package/locales/ja.json +87 -1
- package/locales/ko.json +87 -1
- package/locales/zh-Hans.json +87 -1
- package/locales/zh-Hant.json +87 -1
- package/package.json +1 -1
- package/dist/ui/elements/PcBrushMenu/style.js +0 -3
- package/dist/ui/elements/PcLayersMenu/style.js +0 -3
|
@@ -4,23 +4,28 @@ import ChangeController from "../../ChangeController/index.js";
|
|
|
4
4
|
import PaintCanvas, { Panel } from "../../../lib/classes/PaintCanvas/index.js";
|
|
5
5
|
import KeyboardShortcuts, { SHORTCUT_EVENT } from "../../../lib/classes/KeyboardShortcuts/index.js";
|
|
6
6
|
import { isTouchDevice } from "../../../lib/classes/InputSettings/index.js";
|
|
7
|
-
import IndexedDbLayerStore from "../../../lib/storage/index.js";
|
|
7
|
+
import IndexedDbLayerStore, { IndexedDbSettingsStore } from "../../../lib/storage/index.js";
|
|
8
|
+
import SettingsAutosave, { SETTINGS_AUTOSAVE_ERROR_EVENT } from "../../../lib/classes/SettingsAutosave/index.js";
|
|
8
9
|
import { AUTOSAVE_ERROR_EVENT, AutosaveState } from "../../../lib/classes/LayerAutosave/index.js";
|
|
9
10
|
import { isOwnAuthor } from "../../../lib/brushes/authors/index.js";
|
|
10
11
|
import { renderScreenshot, saveFile, screenshotName } from "../../../lib/rendering/screenshot/index.js";
|
|
11
12
|
import { DEFAULT_SIDE_MODES, STATE_EVENT, parseSideModes, sideModeFor } from "../../layout/index.js";
|
|
12
13
|
import { hasToolSettings } from "../PcToolSettings/index.js";
|
|
13
14
|
import "../PcPanel/index.js";
|
|
15
|
+
import { PANE_COLLAPSE_EVENT, PcPane } from "../PcPane/index.js";
|
|
14
16
|
import "../PcToolbar/index.js";
|
|
15
17
|
import "../PcCanvas/index.js";
|
|
16
18
|
import "../PcCanvasMap/index.js";
|
|
17
|
-
import "../
|
|
18
|
-
import "../
|
|
19
|
+
import "../PcPanelBrushes/index.js";
|
|
20
|
+
import "../PcPanelLayers/index.js";
|
|
19
21
|
import "../PcBrushConfig/index.js";
|
|
20
22
|
import "../PcStats/index.js";
|
|
23
|
+
import "../PcPanelColours/index.js";
|
|
21
24
|
import "../PcDialog/index.js";
|
|
22
25
|
import { html, nothing, unsafeCSS } from "lit";
|
|
23
26
|
// src/ui/elements/PaintCanvas/index.ts
|
|
27
|
+
/** The sides */
|
|
28
|
+
var SIDES = ["left", "right"];
|
|
24
29
|
/** No space kept clear */
|
|
25
30
|
var NO_INSETS = {
|
|
26
31
|
top: 0,
|
|
@@ -30,29 +35,28 @@ var NO_INSETS = {
|
|
|
30
35
|
};
|
|
31
36
|
/** The choice that takes the drawing over from another window */
|
|
32
37
|
var TAKE_OVER_CHOICE = "take-over";
|
|
33
|
-
/** Elements that can take keyboard focus */
|
|
34
|
-
var FOCUSABLE = "button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])";
|
|
35
38
|
/**
|
|
36
39
|
* A ready-made painting app: the drawing elements arranged around the canvas, sized to its container (not the
|
|
37
40
|
* window), with slots to add or replace content:
|
|
38
41
|
*
|
|
39
42
|
* - `header`: across the top (empty by default)
|
|
40
|
-
* - `left`: `<pc-toolbar>` by default
|
|
41
|
-
* - `top`: `<pc-tool-settings>` (for tools that have settings) by default
|
|
42
|
-
* - `right`:
|
|
43
|
+
* - `left`: `<pc-toolbar>` (a pane) by default
|
|
44
|
+
* - `top`: a horizontal `<pc-pane>` with `<pc-tool-settings>` (for tools that have settings) by default
|
|
45
|
+
* - `right`: a `<pc-pane>` with `<pc-panel-layers>` by default
|
|
43
46
|
* - `center`: `<pc-canvas>` by default
|
|
44
47
|
* - `overlay`: drawn over the canvas; `<pc-canvas-map>` by default
|
|
45
48
|
* - `bottom`: under the canvas (empty by default)
|
|
46
49
|
* - `footer`: across the bottom; `<pc-stats>` by default
|
|
47
|
-
* - `menu`: the app menu the toolbar's menu button opens (the button shows only with a menu)
|
|
50
|
+
* - `menu`: the app menu the toolbar's menu button opens, shown in a `<pc-panel>` (the button shows only with a menu)
|
|
48
51
|
*
|
|
49
52
|
* Content given for a slot replaces its default; an empty element clears it. Empty slots take no space. Hiding the
|
|
50
|
-
* interface (Tab key) leaves only the canvas and overlay.
|
|
51
|
-
*
|
|
53
|
+
* interface (Tab key) leaves only the canvas and overlay. While a side has a `<pc-panel-layers>` (as the right side
|
|
54
|
+
* does by default), the toolbar has no layers menu button.
|
|
52
55
|
*
|
|
53
56
|
* Each side has a mode, chosen by the layout's width from `left-modes` and `right-modes` (default `"docked,
|
|
54
|
-
* rail@600"`): `docked` beside the canvas; `rail`, a
|
|
55
|
-
* the canvas; or `floating`, the
|
|
57
|
+
* rail@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
|
|
58
|
+
* column of buttons (one per panel) that open each panel over the canvas; or `floating`, the pane over the canvas as a
|
|
59
|
+
* box that collapses to a button. Give side content in a `<pc-pane>` to have it follow the side's mode.
|
|
56
60
|
*
|
|
57
61
|
* Reports its state for styling as attributes (`narrow`, `input="pen|touch|mouse"`, `ui-hidden`, `left-mode`,
|
|
58
62
|
* `right-mode`) and as `pc-state-change` events (see `LayoutState`). Creates its own canvas unless given one, and
|
|
@@ -62,10 +66,16 @@ var FOCUSABLE = "button, [href], input, select, textarea, [tabindex]:not([tabind
|
|
|
62
66
|
* reloading the page (see `LayerAutosave`). While another window is drawing on it, the layout is blocked by a message
|
|
63
67
|
* that offers to draw here instead. Fires `pc-toast` when the drawing can't be loaded or saved.
|
|
64
68
|
*
|
|
69
|
+
* With a `settings-store` name, the user's app settings (input settings, keyboard shortcuts, colour palette and
|
|
70
|
+
* colours) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when they can't be.
|
|
71
|
+
*
|
|
65
72
|
* @example
|
|
66
73
|
* html`<paint-canvas style="height: 600px"></paint-canvas>`
|
|
67
74
|
* html`<paint-canvas .app=${canvas} right-modes="docked, rail@800">
|
|
68
|
-
* <pc-
|
|
75
|
+
* <pc-pane slot="right">
|
|
76
|
+
* <pc-panel-layers .app=${canvas}></pc-panel-layers>
|
|
77
|
+
* <pc-panel icon="chat" label="Chat"><my-chat></my-chat></pc-panel>
|
|
78
|
+
* </pc-pane>
|
|
69
79
|
* </paint-canvas>`
|
|
70
80
|
*/
|
|
71
81
|
var PaintCanvasElement = class extends PcElement {
|
|
@@ -94,11 +104,13 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
94
104
|
},
|
|
95
105
|
shortcuts: { type: Boolean },
|
|
96
106
|
store: { type: String },
|
|
107
|
+
settingsStore: {
|
|
108
|
+
type: String,
|
|
109
|
+
attribute: "settings-store"
|
|
110
|
+
},
|
|
97
111
|
width: { state: true },
|
|
98
112
|
input: { state: true },
|
|
99
|
-
configuring: { state: true }
|
|
100
|
-
collapsed: { state: true },
|
|
101
|
-
openPanel: { state: true }
|
|
113
|
+
configuring: { state: true }
|
|
102
114
|
};
|
|
103
115
|
static styles = [PcElement.styles, unsafeCSS(style_default)];
|
|
104
116
|
/** Saves files (replaceable for tests) */
|
|
@@ -108,13 +120,10 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
108
120
|
ownAutosave;
|
|
109
121
|
/** The autosave whose errors are shown */
|
|
110
122
|
watchedAutosave;
|
|
123
|
+
/** Keeps the app settings under the `settings-store` name, if any */
|
|
124
|
+
ownSettingsAutosave;
|
|
111
125
|
/** Whether drawing was blocked when shortcuts were last watched */
|
|
112
126
|
blockedShortcuts = false;
|
|
113
|
-
/** The panels in each side's slot */
|
|
114
|
-
panels = {
|
|
115
|
-
left: [],
|
|
116
|
-
right: []
|
|
117
|
-
};
|
|
118
127
|
/** Whether each side's slot has a layers menu (the toolbar's isn't needed then) */
|
|
119
128
|
sideLayers = {
|
|
120
129
|
left: false,
|
|
@@ -130,12 +139,9 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
130
139
|
reported;
|
|
131
140
|
/** Space the floating sides cover over the canvas */
|
|
132
141
|
floatingInsets = NO_INSETS;
|
|
133
|
-
/** The rail button that opened the open panel, to return focus to */
|
|
134
|
-
opener;
|
|
135
142
|
resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(([entry]) => {
|
|
136
143
|
this.width = Math.round(entry?.contentRect.width ?? 0);
|
|
137
144
|
}) : void 0;
|
|
138
|
-
panelObserver = new MutationObserver(() => this.requestUpdate());
|
|
139
145
|
onPointerDown = (event) => {
|
|
140
146
|
const kind = event.pointerType;
|
|
141
147
|
this.input = kind === "pen" || kind === "touch" ? kind : "mouse";
|
|
@@ -144,20 +150,15 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
144
150
|
message: this.t("paintCanvas.autosave.failed"),
|
|
145
151
|
kind: "error"
|
|
146
152
|
});
|
|
153
|
+
onSettingsAutosaveError = () => emit(this, "pc-toast", {
|
|
154
|
+
message: this.t("paintCanvas.settings.saveFailed"),
|
|
155
|
+
kind: "error"
|
|
156
|
+
});
|
|
147
157
|
onShortcut = (event) => {
|
|
148
158
|
if (event.detail.type === "save") this.saveScreenshot();
|
|
149
159
|
};
|
|
150
|
-
onDocumentPointerDown = (event) =>
|
|
151
|
-
|
|
152
|
-
if (!panel) return;
|
|
153
|
-
const path = event.composedPath();
|
|
154
|
-
if (!path.includes(panel) && !(this.opener && path.includes(this.opener))) this.closePanel(false);
|
|
155
|
-
};
|
|
156
|
-
onKeyDown = (event) => {
|
|
157
|
-
if (event.key !== "Escape" || !this.openPanel) return;
|
|
158
|
-
event.preventDefault();
|
|
159
|
-
this.closePanel(true);
|
|
160
|
-
};
|
|
160
|
+
onDocumentPointerDown = (event) => this.closePaletteOutside(event.composedPath());
|
|
161
|
+
onPaneCollapse = () => this.requestUpdate();
|
|
161
162
|
constructor() {
|
|
162
163
|
super();
|
|
163
164
|
this.leftModes = DEFAULT_SIDE_MODES;
|
|
@@ -167,10 +168,6 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
167
168
|
this.shortcuts = true;
|
|
168
169
|
this.width = 0;
|
|
169
170
|
this.input = isTouchDevice() ? "touch" : "mouse";
|
|
170
|
-
this.collapsed = {
|
|
171
|
-
left: true,
|
|
172
|
-
right: true
|
|
173
|
-
};
|
|
174
171
|
}
|
|
175
172
|
/** Whether another window is drawing on the stored drawing, so this one is blocked */
|
|
176
173
|
get blocked() {
|
|
@@ -195,36 +192,25 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
195
192
|
this.app ??= new PaintCanvas();
|
|
196
193
|
super.connectedCallback();
|
|
197
194
|
this.resizeObserver?.observe(this);
|
|
198
|
-
this.panelObserver.observe(this, {
|
|
199
|
-
subtree: true,
|
|
200
|
-
attributes: true,
|
|
201
|
-
attributeFilter: [
|
|
202
|
-
"icon",
|
|
203
|
-
"label",
|
|
204
|
-
"badge"
|
|
205
|
-
]
|
|
206
|
-
});
|
|
207
195
|
this.addEventListener("pointerdown", this.onPointerDown, true);
|
|
208
|
-
this.addEventListener(
|
|
196
|
+
this.addEventListener(PANE_COLLAPSE_EVENT, this.onPaneCollapse);
|
|
209
197
|
document.addEventListener("pointerdown", this.onDocumentPointerDown, true);
|
|
210
198
|
this.watchShortcuts();
|
|
211
199
|
}
|
|
212
200
|
disconnectedCallback() {
|
|
213
201
|
super.disconnectedCallback();
|
|
214
202
|
this.resizeObserver?.disconnect();
|
|
215
|
-
this.panelObserver.disconnect();
|
|
216
203
|
this.removeEventListener("pointerdown", this.onPointerDown, true);
|
|
217
|
-
this.removeEventListener(
|
|
204
|
+
this.removeEventListener(PANE_COLLAPSE_EVENT, this.onPaneCollapse);
|
|
218
205
|
document.removeEventListener("pointerdown", this.onDocumentPointerDown, true);
|
|
219
206
|
this.unwatchShortcuts();
|
|
220
207
|
}
|
|
221
208
|
willUpdate(changed) {
|
|
222
209
|
super.willUpdate(changed);
|
|
223
210
|
if (changed.has("app") || changed.has("store")) this.useStore(changed.get("app"));
|
|
211
|
+
if (changed.has("app") || changed.has("settingsStore")) this.useSettingsStore();
|
|
224
212
|
this.watchAutosaveErrors();
|
|
225
213
|
if (this.isConnected && (changed.has("app") || changed.has("shortcuts") || this.blocked !== this.blockedShortcuts)) this.watchShortcuts();
|
|
226
|
-
const panel = this.openPanel;
|
|
227
|
-
if (panel && this.modeOf(this.sideOf(panel)) !== "rail") this.closePanel(false);
|
|
228
214
|
}
|
|
229
215
|
updated() {
|
|
230
216
|
const state = this.state;
|
|
@@ -238,11 +224,25 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
238
224
|
this.reported = report;
|
|
239
225
|
this.dispatchEvent(new CustomEvent(STATE_EVENT, { detail: state }));
|
|
240
226
|
}
|
|
227
|
+
this.setPaneModes();
|
|
241
228
|
this.measureFloatingSides();
|
|
242
229
|
}
|
|
230
|
+
/** The panes in a side: the default one, or those given for it */
|
|
231
|
+
panesIn(side) {
|
|
232
|
+
const slot = this.renderRoot.querySelector(`slot[name="${side}"]`);
|
|
233
|
+
const given = slot.assignedElements();
|
|
234
|
+
return (given.length ? given : [...slot.children]).filter((element) => element instanceof PcPane);
|
|
235
|
+
}
|
|
236
|
+
/** Show each side's panes in the side's mode, facing the canvas */
|
|
237
|
+
setPaneModes() {
|
|
238
|
+
for (const side of SIDES) for (const pane of this.panesIn(side)) {
|
|
239
|
+
pane.mode = this.modeOf(side);
|
|
240
|
+
pane.edge = side;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
243
|
/** Keep the canvas clear of floating sides when fitting it */
|
|
244
244
|
measureFloatingSides() {
|
|
245
|
-
const width = (side) => this.
|
|
245
|
+
const width = (side) => this.panesIn(side).filter((pane) => pane.mode === "floating" && !pane.collapsed).reduce((widest, pane) => Math.max(widest, pane.getBoundingClientRect().width), 0);
|
|
246
246
|
const insets = {
|
|
247
247
|
...NO_INSETS,
|
|
248
248
|
left: width("left"),
|
|
@@ -267,6 +267,20 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
267
267
|
}
|
|
268
268
|
if (app.autosave?.store.name !== name) this.ownAutosave = app.useLayerStore(new IndexedDbLayerStore(name));
|
|
269
269
|
}
|
|
270
|
+
/** Keeps the app settings under the `settings-store` name, if any (e.g. to wait until they are restored with `ready`) */
|
|
271
|
+
get settingsAutosave() {
|
|
272
|
+
return this.ownSettingsAutosave;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Keep the canvas's app settings under the `settings-store` name, stopping the previous settings autosave (which
|
|
276
|
+
* saves any change waiting to be saved).
|
|
277
|
+
*/
|
|
278
|
+
useSettingsStore() {
|
|
279
|
+
this.ownSettingsAutosave?.stop();
|
|
280
|
+
const name = this.settingsStore || void 0;
|
|
281
|
+
this.ownSettingsAutosave = name === void 0 ? void 0 : new SettingsAutosave(this.app, new IndexedDbSettingsStore(name));
|
|
282
|
+
this.ownSettingsAutosave?.addEventListener(SETTINGS_AUTOSAVE_ERROR_EVENT, this.onSettingsAutosaveError);
|
|
283
|
+
}
|
|
270
284
|
/** Show errors from the canvas's current autosave */
|
|
271
285
|
watchAutosaveErrors() {
|
|
272
286
|
const autosave = this.app?.autosave;
|
|
@@ -339,40 +353,6 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
339
353
|
}
|
|
340
354
|
};
|
|
341
355
|
}
|
|
342
|
-
/** Which side a panel is in */
|
|
343
|
-
sideOf(panel) {
|
|
344
|
-
return panel.slot === "left" ? "left" : "right";
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Open a panel from its rail button (closing any other), or close it if it's open. Focus moves into the panel.
|
|
348
|
-
*/
|
|
349
|
-
togglePanel(panel, opener) {
|
|
350
|
-
if (this.openPanel === panel) {
|
|
351
|
-
this.closePanel(true);
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
if (this.openPanel) this.closePanel(false);
|
|
355
|
-
this.openPanel = panel;
|
|
356
|
-
this.opener = opener;
|
|
357
|
-
panel.toggleAttribute("data-pc-open", true);
|
|
358
|
-
const focusable = panel.querySelector(FOCUSABLE);
|
|
359
|
-
if (focusable) focusable.focus();
|
|
360
|
-
else {
|
|
361
|
-
panel.tabIndex = -1;
|
|
362
|
-
panel.focus();
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Close the panel open from a rail, moving focus back to its rail button if asked.
|
|
367
|
-
*/
|
|
368
|
-
closePanel(returnFocus) {
|
|
369
|
-
const panel = this.openPanel;
|
|
370
|
-
if (!panel) return;
|
|
371
|
-
panel.removeAttribute("data-pc-open");
|
|
372
|
-
this.openPanel = void 0;
|
|
373
|
-
if (returnFocus) this.opener?.focus();
|
|
374
|
-
this.opener = void 0;
|
|
375
|
-
}
|
|
376
356
|
/** Follow which slots have content given (their defaults aren't needed) */
|
|
377
357
|
onSlotChange(event) {
|
|
378
358
|
const slot = event.target;
|
|
@@ -388,16 +368,12 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
388
368
|
>${this.filled[name] ? nothing : fallback}</slot
|
|
389
369
|
>`;
|
|
390
370
|
}
|
|
391
|
-
/** Follow
|
|
371
|
+
/** Follow whether a side given content has a layers menu */
|
|
392
372
|
onSideSlotChange(side, event) {
|
|
393
373
|
const assigned = event.target.assignedElements();
|
|
394
|
-
this.panels = {
|
|
395
|
-
...this.panels,
|
|
396
|
-
[side]: assigned.filter((element) => element.localName === "pc-panel")
|
|
397
|
-
};
|
|
398
374
|
this.sideLayers = {
|
|
399
375
|
...this.sideLayers,
|
|
400
|
-
[side]: assigned.some((element) => element.matches("pc-layers
|
|
376
|
+
[side]: assigned.some((element) => element.matches("pc-panel-layers, :has(pc-panel-layers)"))
|
|
401
377
|
};
|
|
402
378
|
this.onSlotChange(event);
|
|
403
379
|
}
|
|
@@ -408,99 +384,70 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
408
384
|
}
|
|
409
385
|
/** Whether a side has a layers menu, replacing the toolbar's */
|
|
410
386
|
get layersInSide() {
|
|
411
|
-
return this.sideLayers.left || this.sideLayers.right;
|
|
387
|
+
return this.sideLayers.left || this.sideLayers.right || !this.filled.right;
|
|
412
388
|
}
|
|
413
|
-
/**
|
|
414
|
-
|
|
415
|
-
return html`<pc-toolbar
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
class="rail"
|
|
429
|
-
part="${side}-rail"
|
|
430
|
-
role="toolbar"
|
|
431
|
-
aria-orientation="vertical"
|
|
432
|
-
aria-label=${label}
|
|
433
|
-
>
|
|
434
|
-
${panels.map((panel) => html`<button
|
|
435
|
-
type="button"
|
|
436
|
-
title=${panel.label}
|
|
437
|
-
aria-label=${panel.badge ? this.t("paintCanvas.layout.new", { name: panel.label }) : panel.label}
|
|
438
|
-
aria-expanded=${String(this.openPanel === panel)}
|
|
439
|
-
@click=${(event) => this.togglePanel(panel, event.currentTarget)}
|
|
440
|
-
>
|
|
441
|
-
<img src=${this.iconUrl(panel.icon)} alt="" />
|
|
442
|
-
${panel.badge ? html`<span class="badge"></span>` : nothing}
|
|
443
|
-
</button>`)}
|
|
444
|
-
</div>`;
|
|
445
|
-
}
|
|
446
|
-
/** A side, in its mode */
|
|
389
|
+
/** A side's default content: the toolbar on the left, and the layers on the right */
|
|
390
|
+
renderSideDefault(side, app, label) {
|
|
391
|
+
if (side === "left") return html`<pc-toolbar
|
|
392
|
+
.app=${app}
|
|
393
|
+
.menu=${this.hasMenu}
|
|
394
|
+
.layers=${!this.layersInSide}
|
|
395
|
+
.menuText=${this.menuText}
|
|
396
|
+
.menuLabel=${this.menuLabel}
|
|
397
|
+
label=${label}
|
|
398
|
+
></pc-toolbar>`;
|
|
399
|
+
return html`<pc-pane label=${label}
|
|
400
|
+
><pc-panel-layers slot="start" .app=${app}></pc-panel-layers
|
|
401
|
+
></pc-pane>`;
|
|
402
|
+
}
|
|
403
|
+
/** A side: its panes, in its mode (set on them after rendering) */
|
|
447
404
|
renderSide(side, app, blocked) {
|
|
448
|
-
const mode = this.modeOf(side);
|
|
449
405
|
const label = this.t(side === "left" ? "paintCanvas.layout.tools" : "paintCanvas.layout.panels");
|
|
450
|
-
const slot = html`<slot
|
|
451
|
-
name=${side}
|
|
452
|
-
@slotchange=${(event) => this.onSideSlotChange(side, event)}
|
|
453
|
-
>${side === "left" && !this.filled.left ? this.renderToolbar(app) : nothing}</slot
|
|
454
|
-
>`;
|
|
455
|
-
let content = slot;
|
|
456
|
-
if (mode === "rail") content = side === "left" ? html`${slot}${this.renderRail(side, label)}` : html`${this.renderRail(side, label)}${slot}`;
|
|
457
|
-
else if (mode === "floating") {
|
|
458
|
-
const collapsed = this.collapsed[side];
|
|
459
|
-
const arrows = side === "left" ? ["»", "«"] : ["«", "»"];
|
|
460
|
-
content = html`<div
|
|
461
|
-
class=${collapsed ? "float collapsed" : "float"}
|
|
462
|
-
part="${side}-float"
|
|
463
|
-
>
|
|
464
|
-
<button
|
|
465
|
-
type="button"
|
|
466
|
-
class="collapse"
|
|
467
|
-
aria-expanded=${String(!collapsed)}
|
|
468
|
-
aria-label=${this.t(collapsed ? "paintCanvas.layout.show" : "paintCanvas.layout.hide", { name: label })}
|
|
469
|
-
@click=${() => this.collapsed = {
|
|
470
|
-
...this.collapsed,
|
|
471
|
-
[side]: !collapsed
|
|
472
|
-
}}
|
|
473
|
-
>
|
|
474
|
-
${collapsed ? arrows[0] : arrows[1]}
|
|
475
|
-
</button>
|
|
476
|
-
<div class="content" ?hidden=${collapsed}>${slot}</div>
|
|
477
|
-
</div>`;
|
|
478
|
-
}
|
|
479
406
|
return html`<div
|
|
480
407
|
class="side ${side}"
|
|
481
408
|
part=${side}
|
|
482
|
-
data-mode=${
|
|
409
|
+
data-mode=${this.modeOf(side)}
|
|
483
410
|
role="region"
|
|
484
411
|
aria-label=${label}
|
|
485
412
|
?inert=${blocked}
|
|
486
413
|
>
|
|
487
|
-
|
|
414
|
+
<slot
|
|
415
|
+
name=${side}
|
|
416
|
+
@slotchange=${(event) => this.onSideSlotChange(side, event)}
|
|
417
|
+
>${this.filled[side] ? nothing : this.renderSideDefault(side, app, label)}</slot
|
|
418
|
+
>
|
|
488
419
|
</div>`;
|
|
489
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Close the colour palette on a press outside it and the colours that open it.
|
|
423
|
+
*/
|
|
424
|
+
closePaletteOutside(path) {
|
|
425
|
+
const app = this.app;
|
|
426
|
+
if (app?.panel !== Panel.Palette) return;
|
|
427
|
+
if (!path.some((target) => target instanceof Element && (target.localName === "pc-panel-colours" || target.localName === "pc-colour-pair"))) app.closePanel();
|
|
428
|
+
}
|
|
490
429
|
/** The menus the toolbar opens, floating over the canvas beside it */
|
|
491
430
|
renderPanels(app) {
|
|
492
|
-
const brushMenu = (kind) => html`<pc-
|
|
431
|
+
const brushMenu = (kind) => html`<pc-panel-brushes
|
|
493
432
|
class="panel top-panel"
|
|
494
433
|
kind=${kind}
|
|
495
434
|
.app=${app}
|
|
496
435
|
@pc-new-preset=${() => this.newPreset(kind)}
|
|
497
|
-
></pc-
|
|
436
|
+
></pc-panel-brushes>`;
|
|
498
437
|
return html`${app.panel === Panel.Brush ? brushMenu("brush") : nothing}
|
|
499
438
|
${app.panel === Panel.Eraser ? brushMenu("eraser") : nothing}
|
|
500
|
-
${app.panel === Panel.Layers && !this.layersInSide ? html`<pc-layers
|
|
501
|
-
|
|
439
|
+
${app.panel === Panel.Layers && !this.layersInSide ? html`<pc-panel-layers class="panel" .app=${app}></pc-panel-layers>` : nothing}
|
|
440
|
+
${app.panel === Panel.Palette ? html`<pc-panel-colours
|
|
441
|
+
class="panel top-panel"
|
|
442
|
+
.app=${app}
|
|
443
|
+
></pc-panel-colours>` : nothing}
|
|
444
|
+
<pc-panel
|
|
445
|
+
class="panel menu-panel"
|
|
446
|
+
label=${this.menuLabel || this.t("paintCanvas.toolbar.menu")}
|
|
447
|
+
?open=${app.panel === Panel.Menu}
|
|
448
|
+
>
|
|
502
449
|
<slot name="menu" @slotchange=${this.onMenuSlotChange}></slot>
|
|
503
|
-
</
|
|
450
|
+
</pc-panel>`;
|
|
504
451
|
}
|
|
505
452
|
/** The brush configuration window, while open */
|
|
506
453
|
renderConfig(app) {
|
|
@@ -549,10 +496,13 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
549
496
|
aria-label=${this.t("paintCanvas.layout.toolSettings")}
|
|
550
497
|
?inert=${blocked}
|
|
551
498
|
>
|
|
552
|
-
${this.slotWithDefault("top", showsToolSettings ? html`<pc-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
499
|
+
${this.slotWithDefault("top", showsToolSettings ? html`<pc-pane orientation="horizontal" edge="top"
|
|
500
|
+
><pc-tool-settings
|
|
501
|
+
slot="middle"
|
|
502
|
+
.app=${app}
|
|
503
|
+
@pc-configure-brush=${(event) => this.configure(event.detail)}
|
|
504
|
+
></pc-tool-settings
|
|
505
|
+
></pc-pane>` : nothing)}
|
|
556
506
|
</div>
|
|
557
507
|
<div
|
|
558
508
|
class="center"
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/ui/elements/PaintCanvas/style.css?inline
|
|
2
|
-
var style_default = "/* The layout: header across the top, footer across the bottom, sides left and right, and between them the tool\n settings, the canvas and the bottom bar. Empty areas take no space. */\n:host {\n position: relative;\n display: grid;\n grid-template:\n \"header header header\" auto\n \"left top right\" auto\n \"left center right\" 1fr\n \"left bottom right\" auto\n \"footer footer footer\" auto / auto 1fr auto;\n overflow: hidden;\n background: var(--pc-bar);\n font-family: var(--pc-font);\n font-size: var(--pc-font-size);\n}\nheader {\n grid-area: header;\n}\n.top {\n grid-area: top;\n min-width: 0;\n}\n.center {\n position: relative;\n grid-area: center;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n.bottom {\n grid-area: bottom;\n min-width: 0;\n}\nfooter {\n grid-area: footer;\n}\n.side {\n position: relative;\n z-index: 3;\n display: flex;\n min-height: 0;\n}\n.side.left {\n grid-area: left;\n}\n.side.right {\n grid-area: right;\n}\n\n/* Hiding the interface (Tab key) leaves only the canvas */\n:host([ui-hidden]) header,\n:host([ui-hidden]) .top,\n:host([ui-hidden]) .bottom,\n:host([ui-hidden]) footer,\n:host([ui-hidden]) .side {\n display: none;\n}\n\n/* The canvas fills the middle; things drawn over it go in the overlay */\n.center ::slotted([slot=\"center\"]),\n.center pc-canvas {\n position: absolute;\n inset: 0;\n}\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.overlay > * {\n pointer-events: auto;\n}\npc-canvas-map {\n position: absolute;\n right: 20px;\n bottom: 20px;\n}\n\n/* Menus opened from the toolbar float over the canvas, beside the toolbar */\n.panel {\n position: absolute;\n z-index: 4;\n left: 0;\n bottom: 0;\n max-height: 100%;\n overflow: auto;\n}\n.panel.top-panel {\n top: 0;\n bottom: auto;\n}\n
|
|
2
|
+
var style_default = "/* The layout: header across the top, footer across the bottom, sides left and right, and between them the tool\n settings, the canvas and the bottom bar. Empty areas take no space. */\n:host {\n position: relative;\n display: grid;\n grid-template:\n \"header header header\" auto\n \"left top right\" auto\n \"left center right\" 1fr\n \"left bottom right\" auto\n \"footer footer footer\" auto / auto 1fr auto;\n overflow: hidden;\n background: var(--pc-bar);\n font-family: var(--pc-font);\n font-size: var(--pc-font-size);\n}\nheader {\n grid-area: header;\n}\n.top {\n grid-area: top;\n min-width: 0;\n}\n/* The tool settings fill the top pane */\n.top pc-tool-settings {\n flex: 1;\n}\n.center {\n position: relative;\n grid-area: center;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n.bottom {\n grid-area: bottom;\n min-width: 0;\n}\nfooter {\n grid-area: footer;\n}\n.side {\n position: relative;\n z-index: 3;\n display: flex;\n min-height: 0;\n}\n/* A side's panes fill it */\n.side pc-pane,\n.side pc-toolbar,\n.side ::slotted(*) {\n flex: 1;\n}\n.side.left {\n grid-area: left;\n}\n.side.right {\n grid-area: right;\n}\n\n/* Hiding the interface (Tab key) leaves only the canvas */\n:host([ui-hidden]) header,\n:host([ui-hidden]) .top,\n:host([ui-hidden]) .bottom,\n:host([ui-hidden]) footer,\n:host([ui-hidden]) .side {\n display: none;\n}\n\n/* The canvas fills the middle; things drawn over it go in the overlay */\n.center ::slotted([slot=\"center\"]),\n.center pc-canvas {\n position: absolute;\n inset: 0;\n}\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.overlay > * {\n pointer-events: auto;\n}\npc-canvas-map {\n position: absolute;\n right: 20px;\n bottom: 20px;\n}\n\n/* Menus opened from the toolbar float over the canvas, beside the toolbar */\n.panel {\n position: absolute;\n z-index: 4;\n left: 0;\n bottom: 0;\n max-height: 100%;\n overflow: auto;\n}\n.panel.top-panel {\n top: 0;\n bottom: auto;\n}\n/* The app menu: the implementer's content in a panel */\n.menu-panel:not([open]) {\n display: none;\n}\n\n/* Floating: the side's panes float over the canvas (see pc-pane), so the side takes no space */\n.side[data-mode=\"floating\"] {\n /* Beside the canvas only, so it floats over the canvas and not the tool settings */\n grid-row: 3;\n width: 0;\n}\n/* While another window is drawing, its message covers only the layout (not the whole page) */\npc-dialog.elsewhere {\n position: absolute;\n}\n";
|
|
3
3
|
export { style_default as default };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { nothing, type PropertyValues } from "lit";
|
|
2
|
+
import { PcElement } from "../../base";
|
|
3
|
+
import "../PcShortcutSettings";
|
|
4
|
+
import type PaintCanvas from "../../../lib/classes/PaintCanvas";
|
|
5
|
+
import { type SaveMethod } from "../../../lib/rendering/screenshot";
|
|
6
|
+
/**
|
|
7
|
+
* Buttons for the user's app settings: "Keyboard shortcuts", which opens a window to change them (see
|
|
8
|
+
* `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
|
|
9
|
+
* settings, keyboard shortcuts, colour palette and colours). The window is a modal `<dialog>` above the whole page; it
|
|
10
|
+
* closes with its Close button, Escape, or a press on the backdrop, and the browser gives focus back to the button. Fires `pc-toast` after importing, or when importing or
|
|
11
|
+
* exporting fails.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* html`<pc-app-settings .app=${canvas}></pc-app-settings>`
|
|
15
|
+
*/
|
|
16
|
+
export declare class PcAppSettings extends PcElement {
|
|
17
|
+
static properties: {
|
|
18
|
+
app: {
|
|
19
|
+
attribute: boolean;
|
|
20
|
+
};
|
|
21
|
+
shortcutsOpen: {
|
|
22
|
+
type: BooleanConstructor;
|
|
23
|
+
attribute: string;
|
|
24
|
+
};
|
|
25
|
+
translations: {
|
|
26
|
+
attribute: boolean;
|
|
27
|
+
};
|
|
28
|
+
icons: {
|
|
29
|
+
attribute: boolean;
|
|
30
|
+
};
|
|
31
|
+
theme: {
|
|
32
|
+
type: StringConstructor;
|
|
33
|
+
reflect: boolean;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
static styles: import("lit").CSSResultGroup[];
|
|
37
|
+
app?: PaintCanvas;
|
|
38
|
+
/** Whether the keyboard shortcuts window is open */
|
|
39
|
+
shortcutsOpen: boolean;
|
|
40
|
+
/** Saves exported files (replaceable for tests) */
|
|
41
|
+
saveFile: (blob: Blob, name: string) => Promise<SaveMethod | undefined>;
|
|
42
|
+
constructor();
|
|
43
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
44
|
+
/** Close the keyboard shortcuts window */
|
|
45
|
+
closeShortcuts(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Import the settings in a settings file, using the parts it has.
|
|
48
|
+
*/
|
|
49
|
+
importFile(file: File): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Export the settings as a settings file.
|
|
52
|
+
*/
|
|
53
|
+
exportFile(): Promise<void>;
|
|
54
|
+
/** Show a settings file error (one of `SETTINGS_FILE_ERRORS`) in the chosen language */
|
|
55
|
+
private toastError;
|
|
56
|
+
/** The keyboard shortcuts window: a modal dialog, with its contents only while open */
|
|
57
|
+
private renderShortcuts;
|
|
58
|
+
render(): import("lit").TemplateResult<1> | typeof nothing;
|
|
59
|
+
}
|