@reffy/infinite-canvas 0.0.11 → 0.0.12
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/assets/index-KcoKIUJZ.js +281 -0
- package/dist/index.html +54 -0
- package/esm/Canvas.js +100 -112
- package/esm/Canvas.js.map +1 -1
- package/esm/Component.d.ts.map +1 -1
- package/esm/Component.js +273 -308
- package/esm/Component.js.map +1 -1
- package/esm/manager/ContextMenu.js +4 -17
- package/esm/manager/ContextMenu.js.map +1 -1
- package/esm/manager/Selection.js +87 -100
- package/esm/manager/Selection.js.map +1 -1
- package/esm/serializer/serializer.d.ts.map +1 -1
- package/esm/serializer/serializer.js +11 -5
- package/esm/serializer/serializer.js.map +1 -1
- package/esm/shapes/Shape.js +1 -1
- package/esm/shapes/Shape.js.map +1 -1
- package/lib/Canvas.js +100 -112
- package/lib/Canvas.js.map +1 -1
- package/lib/Component.d.ts.map +1 -1
- package/lib/Component.js +273 -308
- package/lib/Component.js.map +1 -1
- package/lib/manager/ContextMenu.js +4 -17
- package/lib/manager/ContextMenu.js.map +1 -1
- package/lib/manager/Selection.js +87 -100
- package/lib/manager/Selection.js.map +1 -1
- package/lib/serializer/serializer.d.ts.map +1 -1
- package/lib/serializer/serializer.js +11 -5
- package/lib/serializer/serializer.js.map +1 -1
- package/lib/shapes/Shape.js +1 -1
- package/lib/shapes/Shape.js.map +1 -1
- package/package.json +3 -2
- package/dist/index.js +0 -4114
- package/dist/index.js.map +0 -1
- package/dist/index.umd.js +0 -276
- package/dist/index.umd.js.map +0 -1
package/esm/Component.js
CHANGED
|
@@ -4,18 +4,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
-
};
|
|
12
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
13
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
14
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
15
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
16
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
17
|
-
};
|
|
18
|
-
var _InfiniteCanvasElement_canvas, _InfiniteCanvasElement_eventHub, _InfiniteCanvasElement_resizeObserver, _InfiniteCanvasElement_history, _InfiniteCanvasElement_fileStorage, _InfiniteCanvasElement_canvasStorage, _InfiniteCanvasElement_saveFrequency, _InfiniteCanvasElement_timeoutId, _InfiniteCanvasElement_intervalId, _InfiniteCanvasElement_onViewportResize, _InfiniteCanvasElement_rootDiv, _InfiniteCanvasElement_onChange, _InfiniteCanvasElement_singleImageMenuOptions, _InfiniteCanvasElement_multiImageMenuOptions, _InfiniteCanvasElement_canvasImageMenuOptions;
|
|
19
7
|
import { CanvasHistory } from "./history";
|
|
20
8
|
import { Canvas } from "./Canvas";
|
|
21
9
|
import { LitElement, css } from "lit";
|
|
@@ -28,7 +16,6 @@ import { addContextMenu, clearContextMenu, createBasicImageMenuOptions, createCa
|
|
|
28
16
|
import { DefaultIndexedDbStorage, DefaultLocalStorage, } from "./storage";
|
|
29
17
|
import EventEmitter from "eventemitter3";
|
|
30
18
|
import { hideLoader, showLoader } from "./loader";
|
|
31
|
-
import Stats from "stats.js";
|
|
32
19
|
let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
33
20
|
constructor() {
|
|
34
21
|
super(...arguments);
|
|
@@ -36,21 +23,7 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
36
23
|
this.name = "Reffy";
|
|
37
24
|
/** There are two acceptable options: fullscreen (default) and windowed. Fullscreen mode will ignore the width and height you inputed. */
|
|
38
25
|
this.displayMode = "fullscreen";
|
|
39
|
-
|
|
40
|
-
_InfiniteCanvasElement_eventHub.set(this, void 0);
|
|
41
|
-
_InfiniteCanvasElement_resizeObserver.set(this, void 0);
|
|
42
|
-
_InfiniteCanvasElement_history.set(this, void 0);
|
|
43
|
-
_InfiniteCanvasElement_fileStorage.set(this, void 0);
|
|
44
|
-
_InfiniteCanvasElement_canvasStorage.set(this, void 0);
|
|
45
|
-
_InfiniteCanvasElement_saveFrequency.set(this, 300000);
|
|
46
|
-
_InfiniteCanvasElement_timeoutId.set(this, void 0);
|
|
47
|
-
_InfiniteCanvasElement_intervalId.set(this, void 0);
|
|
48
|
-
_InfiniteCanvasElement_onViewportResize.set(this, void 0);
|
|
49
|
-
_InfiniteCanvasElement_rootDiv.set(this, void 0);
|
|
50
|
-
_InfiniteCanvasElement_onChange.set(this, void 0);
|
|
51
|
-
_InfiniteCanvasElement_singleImageMenuOptions.set(this, void 0);
|
|
52
|
-
_InfiniteCanvasElement_multiImageMenuOptions.set(this, void 0);
|
|
53
|
-
_InfiniteCanvasElement_canvasImageMenuOptions.set(this, void 0);
|
|
26
|
+
this.#saveFrequency = 300000;
|
|
54
27
|
// Global helper
|
|
55
28
|
this.handleGlobalPointerDown = (e) => {
|
|
56
29
|
if (!this.contains(e.target) &&
|
|
@@ -59,29 +32,179 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
59
32
|
}
|
|
60
33
|
};
|
|
61
34
|
}
|
|
35
|
+
static { this.properties = {
|
|
36
|
+
name: { type: String, reflect: true },
|
|
37
|
+
width: { type: String, reflect: true },
|
|
38
|
+
height: { type: String, reflect: true },
|
|
39
|
+
displayMode: { type: String, reflect: true },
|
|
40
|
+
}; }
|
|
41
|
+
static { this.styles = css `
|
|
42
|
+
:host {
|
|
43
|
+
position: relative;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.context-menu {
|
|
48
|
+
position: absolute;
|
|
49
|
+
background: white;
|
|
50
|
+
min-width: 180px;
|
|
51
|
+
background: var(--menu-bg, #fff);
|
|
52
|
+
border-radius: 6px;
|
|
53
|
+
border: 1px solid var(--menu-border, #9f9f9fff);
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
padding: 6px 0;
|
|
56
|
+
display: flex;
|
|
57
|
+
gap: 2px;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
font-family: system-ui, sans-serif;
|
|
60
|
+
animation: fadeInMenu 0.13s cubic-bezier(0.4, 0, 0.2, 1);
|
|
61
|
+
overflow: scroll;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@keyframes fadeInMenu {
|
|
65
|
+
from {
|
|
66
|
+
opacity: 0;
|
|
67
|
+
transform: translateY(8px);
|
|
68
|
+
}
|
|
69
|
+
to {
|
|
70
|
+
opacity: 1;
|
|
71
|
+
transform: none;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.context-menu button {
|
|
76
|
+
all: unset;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
width: 100%;
|
|
81
|
+
padding: 8px 18px;
|
|
82
|
+
font-size: 15px;
|
|
83
|
+
color: var(--menu-fg, #222);
|
|
84
|
+
background: none;
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
transition:
|
|
87
|
+
background 0.1s,
|
|
88
|
+
color 0.1s;
|
|
89
|
+
user-select: none;
|
|
90
|
+
outline: none;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.context-menu button:hover,
|
|
94
|
+
.context-menu button:focus-visible {
|
|
95
|
+
background: var(--menu-hover, #c7d5eaff);
|
|
96
|
+
color: var(--menu-accent, #155290ff);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.context-menu button:active {
|
|
100
|
+
background: var(--menu-active, #e3eaf3);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.context-menu button[disabled] {
|
|
104
|
+
color: #aaa;
|
|
105
|
+
cursor: not-allowed;
|
|
106
|
+
background: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.context-menu-divider {
|
|
110
|
+
height: 1px;
|
|
111
|
+
background: var(--menu-divider, #c7d5eaff);
|
|
112
|
+
margin: 6px 12px;
|
|
113
|
+
border: none;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
canvas {
|
|
117
|
+
width: 100%;
|
|
118
|
+
height: 100%;
|
|
119
|
+
outline: none;
|
|
120
|
+
padding: 0;
|
|
121
|
+
margin: 0;
|
|
122
|
+
touch-action: none;
|
|
123
|
+
display: block;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.canvas-loader {
|
|
127
|
+
position: absolute;
|
|
128
|
+
top: 0;
|
|
129
|
+
left: 0;
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
background: rgba(255, 255, 255, 0.7);
|
|
135
|
+
z-index: 1000;
|
|
136
|
+
pointer-events: all;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.canvas-loader-spinner {
|
|
140
|
+
width: 48px;
|
|
141
|
+
height: 48px;
|
|
142
|
+
border: 6px solid #e0e0e0;
|
|
143
|
+
border-top: 6px solid #1976d2;
|
|
144
|
+
border-radius: 50%;
|
|
145
|
+
animation: canvas-loader-spin 1s linear infinite;
|
|
146
|
+
margin-bottom: 16px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@keyframes canvas-loader-spin {
|
|
150
|
+
0% {
|
|
151
|
+
transform: rotate(0deg);
|
|
152
|
+
}
|
|
153
|
+
100% {
|
|
154
|
+
transform: rotate(360deg);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.canvas-loader-message {
|
|
159
|
+
font-size: 1.1rem;
|
|
160
|
+
color: #333;
|
|
161
|
+
background: rgba(255, 255, 255, 0.9);
|
|
162
|
+
padding: 8px 16px;
|
|
163
|
+
border-radius: 4px;
|
|
164
|
+
margin-top: 8px;
|
|
165
|
+
text-align: center;
|
|
166
|
+
max-width: 80%;
|
|
167
|
+
word-break: break-word;
|
|
168
|
+
}
|
|
169
|
+
`; }
|
|
170
|
+
#canvas;
|
|
171
|
+
#eventHub;
|
|
172
|
+
#resizeObserver;
|
|
173
|
+
#history;
|
|
174
|
+
#fileStorage;
|
|
175
|
+
#canvasStorage;
|
|
176
|
+
#saveFrequency;
|
|
177
|
+
#timeoutId;
|
|
178
|
+
#intervalId;
|
|
179
|
+
#onViewportResize;
|
|
180
|
+
#rootDiv;
|
|
181
|
+
#onChange;
|
|
182
|
+
#singleImageMenuOptions;
|
|
183
|
+
#multiImageMenuOptions;
|
|
184
|
+
#canvasImageMenuOptions;
|
|
62
185
|
get singleImageMenuOptions() {
|
|
63
|
-
return
|
|
186
|
+
return this.#singleImageMenuOptions;
|
|
64
187
|
}
|
|
65
188
|
get multiImageMenuOptions() {
|
|
66
|
-
return
|
|
189
|
+
return this.#multiImageMenuOptions;
|
|
67
190
|
}
|
|
68
191
|
get canvasImageMenuOptions() {
|
|
69
|
-
return
|
|
192
|
+
return this.#canvasImageMenuOptions;
|
|
70
193
|
}
|
|
71
194
|
get canvas() {
|
|
72
|
-
return
|
|
195
|
+
return this.#canvas;
|
|
73
196
|
}
|
|
74
197
|
get onCanvasChange() {
|
|
75
|
-
return
|
|
198
|
+
return this.#onChange;
|
|
76
199
|
}
|
|
77
200
|
set onCanvasChange(fn) {
|
|
78
|
-
|
|
201
|
+
this.#onChange = fn;
|
|
79
202
|
}
|
|
80
203
|
get eventHub() {
|
|
81
|
-
return
|
|
204
|
+
return this.#eventHub;
|
|
82
205
|
}
|
|
83
206
|
get rootDiv() {
|
|
84
|
-
return
|
|
207
|
+
return this.#rootDiv;
|
|
85
208
|
}
|
|
86
209
|
// Lifecycle
|
|
87
210
|
connectedCallback() {
|
|
@@ -91,10 +214,10 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
91
214
|
}
|
|
92
215
|
disconnectedCallback() {
|
|
93
216
|
document.removeEventListener("pointerdown", this.handleGlobalPointerDown, true);
|
|
94
|
-
window.removeEventListener("resize",
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
217
|
+
window.removeEventListener("resize", this.#onViewportResize);
|
|
218
|
+
this.#resizeObserver?.disconnect();
|
|
219
|
+
this.#resizeObserver = undefined;
|
|
220
|
+
this.#canvas.destroy();
|
|
98
221
|
super.disconnectedCallback();
|
|
99
222
|
}
|
|
100
223
|
firstUpdated(_changedProperties) {
|
|
@@ -112,7 +235,7 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
112
235
|
changed.has("displayMode");
|
|
113
236
|
if (!sizeChanged)
|
|
114
237
|
return;
|
|
115
|
-
const container =
|
|
238
|
+
const container = this.#rootDiv;
|
|
116
239
|
const canvasEl = this.renderRoot.querySelector("canvas");
|
|
117
240
|
if (container && canvasEl) {
|
|
118
241
|
this.resizeCanvas(container, canvasEl);
|
|
@@ -120,12 +243,12 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
120
243
|
}
|
|
121
244
|
async initCanvas() {
|
|
122
245
|
await this.warmUpStorage();
|
|
123
|
-
|
|
124
|
-
|
|
246
|
+
this.#history = new CanvasHistory();
|
|
247
|
+
this.#eventHub = new EventEmitter();
|
|
125
248
|
const div = document.createElement("div");
|
|
126
249
|
div.style.overflow = "hidden";
|
|
127
250
|
this.renderRoot.appendChild(div);
|
|
128
|
-
|
|
251
|
+
this.#rootDiv = div;
|
|
129
252
|
const canvas = document.createElement("canvas");
|
|
130
253
|
// persistent state set up
|
|
131
254
|
this.assignFileStorage = this.assignFileStorage.bind(this);
|
|
@@ -150,7 +273,7 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
150
273
|
div.appendChild(canvas);
|
|
151
274
|
}
|
|
152
275
|
this.registerSignal();
|
|
153
|
-
|
|
276
|
+
this.#canvas = new Canvas(canvas, this.#history, this.#eventHub, this.debounceSaveToCanvasStorage, this.saveImageFileMetadata, this.getContainerSize);
|
|
154
277
|
try {
|
|
155
278
|
await this.restoreStateFromCanvasStorage();
|
|
156
279
|
}
|
|
@@ -159,23 +282,15 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
159
282
|
}
|
|
160
283
|
// resize canvas to start
|
|
161
284
|
this.resizeCanvas(div, canvas);
|
|
162
|
-
|
|
163
|
-
window.addEventListener("resize",
|
|
285
|
+
this.#onViewportResize = () => this.resizeCanvas(div, canvas);
|
|
286
|
+
window.addEventListener("resize", this.#onViewportResize);
|
|
164
287
|
const basicImageMenuOptions = createBasicImageMenuOptions.bind(this)();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
288
|
+
this.#singleImageMenuOptions = createSingleImageMenuOptions.bind(this)(basicImageMenuOptions.options);
|
|
289
|
+
this.#canvasImageMenuOptions = createCanvasImageMenuOptions.bind(this)();
|
|
290
|
+
this.#multiImageMenuOptions = createMultiImageMenuOptions.bind(this)(basicImageMenuOptions.options);
|
|
168
291
|
this.dispatchEvent(new Event("load"));
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
if (!this.renderRoot.contains(stats.dom)) {
|
|
172
|
-
this.renderRoot.appendChild(stats.dom);
|
|
173
|
-
}
|
|
174
|
-
const animate = () => {
|
|
175
|
-
if (stats) {
|
|
176
|
-
stats.update();
|
|
177
|
-
}
|
|
178
|
-
__classPrivateFieldGet(this, _InfiniteCanvasElement_canvas, "f").render();
|
|
292
|
+
const animate = async () => {
|
|
293
|
+
this.#canvas.render();
|
|
179
294
|
requestAnimationFrame(animate);
|
|
180
295
|
};
|
|
181
296
|
animate();
|
|
@@ -221,25 +336,25 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
221
336
|
}
|
|
222
337
|
// Register signal
|
|
223
338
|
registerSignal() {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
|
|
339
|
+
this.#eventHub.on(LoaderEvent.start, showLoader.bind(this), "spinner");
|
|
340
|
+
this.#eventHub.on(LoaderEvent.done, hideLoader.bind(this));
|
|
341
|
+
this.#eventHub.on(ContextMenuEvent.Open, this.addContextMenu);
|
|
342
|
+
this.#eventHub.on(ContextMenuEvent.Close, this.clearContextMenu);
|
|
343
|
+
this.#eventHub.on(CanvasEvent.Change, () => {
|
|
344
|
+
if (this.#onChange)
|
|
345
|
+
this.#onChange();
|
|
231
346
|
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
347
|
+
this.#eventHub.on(SaveEvent.Save, this.saveToCanvasStorage);
|
|
348
|
+
this.#eventHub.on(SaveEvent.SaveCompleted, () => { });
|
|
349
|
+
this.#eventHub.on(SaveEvent.SaveFailed, () => console.error("Failed to Save!"));
|
|
235
350
|
}
|
|
236
351
|
// need to check if this is good practice
|
|
237
352
|
async warmUpStorage() {
|
|
238
|
-
if (!
|
|
239
|
-
|
|
353
|
+
if (!this.#fileStorage) {
|
|
354
|
+
this.#fileStorage = new DefaultIndexedDbStorage();
|
|
240
355
|
}
|
|
241
356
|
try {
|
|
242
|
-
await
|
|
357
|
+
await this.#fileStorage.readAll();
|
|
243
358
|
}
|
|
244
359
|
catch (err) {
|
|
245
360
|
console.error("Storage warm-up failed", err);
|
|
@@ -249,9 +364,9 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
249
364
|
* @returns An array containing the width and height of the container
|
|
250
365
|
*/
|
|
251
366
|
getContainerSize() {
|
|
252
|
-
if (!
|
|
367
|
+
if (!this.#rootDiv)
|
|
253
368
|
return;
|
|
254
|
-
const rect =
|
|
369
|
+
const rect = this.#rootDiv.getBoundingClientRect();
|
|
255
370
|
return [rect.width, rect.height];
|
|
256
371
|
}
|
|
257
372
|
// Storage & Persistence
|
|
@@ -259,17 +374,17 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
259
374
|
* @param storage Canvas storage stores the positions of all the renderables
|
|
260
375
|
* @param saveFrequency How often should auto save execute
|
|
261
376
|
*/
|
|
262
|
-
assignCanvasStorage(storage, saveFrequency =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
377
|
+
assignCanvasStorage(storage, saveFrequency = this.#saveFrequency) {
|
|
378
|
+
this.#canvasStorage = storage;
|
|
379
|
+
this.#saveFrequency = saveFrequency;
|
|
380
|
+
this.#intervalId && clearInterval(this.#intervalId);
|
|
381
|
+
this.#intervalId = setInterval(this.saveToCanvasStorage, this.#saveFrequency);
|
|
267
382
|
}
|
|
268
383
|
/**
|
|
269
384
|
* @param storage File storage captures the information about the image data that has previously been added. Made more efficient by using SHA of the image data for storage.
|
|
270
385
|
*/
|
|
271
386
|
assignFileStorage(storage) {
|
|
272
|
-
|
|
387
|
+
this.#fileStorage = storage;
|
|
273
388
|
}
|
|
274
389
|
/**
|
|
275
390
|
* Duplicate images will not be written to the database
|
|
@@ -277,13 +392,13 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
277
392
|
* @returns The unique ID that the image has been logged with. This is a hashed version of the image data URL
|
|
278
393
|
*/
|
|
279
394
|
async saveImageFileMetadata(dataURL) {
|
|
280
|
-
if (!
|
|
281
|
-
|
|
395
|
+
if (!this.#fileStorage) {
|
|
396
|
+
this.#fileStorage = new DefaultIndexedDbStorage();
|
|
282
397
|
}
|
|
283
398
|
try {
|
|
284
399
|
const id = await hashStringToId(dataURL);
|
|
285
|
-
if (!(await
|
|
286
|
-
return await
|
|
400
|
+
if (!(await this.#fileStorage.checkIfImageStored(id))) {
|
|
401
|
+
return await this.#fileStorage.write(dataURL);
|
|
287
402
|
}
|
|
288
403
|
else {
|
|
289
404
|
return id;
|
|
@@ -298,11 +413,11 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
298
413
|
* @returns
|
|
299
414
|
*/
|
|
300
415
|
async getImageFileMetadata(fileId) {
|
|
301
|
-
if (!
|
|
302
|
-
|
|
416
|
+
if (!this.#fileStorage) {
|
|
417
|
+
this.#fileStorage = new DefaultIndexedDbStorage();
|
|
303
418
|
}
|
|
304
419
|
try {
|
|
305
|
-
return await
|
|
420
|
+
return await this.#fileStorage.read(fileId);
|
|
306
421
|
}
|
|
307
422
|
catch (err) {
|
|
308
423
|
console.error(err);
|
|
@@ -312,11 +427,11 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
312
427
|
* @returns All file metadata saved in connected storage
|
|
313
428
|
*/
|
|
314
429
|
async getAllImageFileMetdata() {
|
|
315
|
-
if (!
|
|
316
|
-
|
|
430
|
+
if (!this.#fileStorage) {
|
|
431
|
+
this.#fileStorage = new DefaultIndexedDbStorage();
|
|
317
432
|
}
|
|
318
433
|
try {
|
|
319
|
-
return await
|
|
434
|
+
return await this.#fileStorage.readAll();
|
|
320
435
|
}
|
|
321
436
|
catch (err) {
|
|
322
437
|
console.error(err);
|
|
@@ -326,134 +441,134 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
326
441
|
* Schedule the auto save to the canvas storage based on timer
|
|
327
442
|
*/
|
|
328
443
|
debounceSaveToCanvasStorage(timeout = 1000) {
|
|
329
|
-
if (!
|
|
330
|
-
|
|
444
|
+
if (!this.#canvasStorage) {
|
|
445
|
+
this.#canvasStorage = new DefaultLocalStorage(this.name);
|
|
331
446
|
}
|
|
332
|
-
clearTimeout(
|
|
333
|
-
|
|
447
|
+
clearTimeout(this.#timeoutId);
|
|
448
|
+
this.#timeoutId = setTimeout(this.saveToCanvasStorage, timeout);
|
|
334
449
|
}
|
|
335
450
|
async saveToCanvasStorage() {
|
|
336
|
-
if (!
|
|
337
|
-
|
|
451
|
+
if (!this.#canvasStorage) {
|
|
452
|
+
this.#canvasStorage = new DefaultLocalStorage(this.name);
|
|
338
453
|
}
|
|
339
|
-
|
|
340
|
-
.write(serializeCanvas(
|
|
341
|
-
.then(() =>
|
|
342
|
-
.catch(() =>
|
|
454
|
+
this.#canvasStorage
|
|
455
|
+
.write(serializeCanvas(this.#canvas))
|
|
456
|
+
.then(() => this.#eventHub.emit(SaveEvent.SaveCompleted))
|
|
457
|
+
.catch(() => this.#eventHub.emit(SaveEvent.SaveFailed));
|
|
343
458
|
}
|
|
344
459
|
async restoreStateFromCanvasStorage() {
|
|
345
|
-
if (!
|
|
346
|
-
|
|
460
|
+
if (!this.#canvasStorage) {
|
|
461
|
+
this.#canvasStorage = new DefaultLocalStorage(this.name);
|
|
347
462
|
}
|
|
348
|
-
const dataAsString = await
|
|
463
|
+
const dataAsString = await this.#canvasStorage.read();
|
|
349
464
|
const data = JSON.parse(dataAsString);
|
|
350
465
|
if (data) {
|
|
351
|
-
await deserializeCanvas(data,
|
|
466
|
+
await deserializeCanvas(data, this.#canvas, this.getImageFileMetadata);
|
|
352
467
|
}
|
|
353
468
|
}
|
|
354
469
|
// Canvas API
|
|
355
470
|
togglePointerMode() {
|
|
356
|
-
if (!
|
|
471
|
+
if (!this.#canvas)
|
|
357
472
|
return;
|
|
358
|
-
|
|
473
|
+
this.#canvas.changeMode();
|
|
359
474
|
}
|
|
360
475
|
toggleGrid() {
|
|
361
|
-
if (!
|
|
476
|
+
if (!this.#canvas)
|
|
362
477
|
return;
|
|
363
|
-
|
|
478
|
+
this.#canvas.toggleGrid();
|
|
364
479
|
}
|
|
365
480
|
zoomIn() {
|
|
366
|
-
if (!
|
|
481
|
+
if (!this.#canvas)
|
|
367
482
|
return;
|
|
368
|
-
|
|
483
|
+
this.#canvas.updateZoomByFixedAmount(-1);
|
|
369
484
|
}
|
|
370
485
|
zoomOut() {
|
|
371
|
-
if (!
|
|
486
|
+
if (!this.#canvas)
|
|
372
487
|
return;
|
|
373
|
-
|
|
488
|
+
this.#canvas.updateZoomByFixedAmount();
|
|
374
489
|
}
|
|
375
490
|
async addImages(fileList) {
|
|
376
|
-
if (!
|
|
491
|
+
if (!this.#canvas)
|
|
377
492
|
return;
|
|
378
|
-
const rect =
|
|
493
|
+
const rect = this.#rootDiv.getBoundingClientRect();
|
|
379
494
|
const clientX = rect.left + rect.width / 2;
|
|
380
495
|
const clientY = rect.top + rect.height / 2;
|
|
381
|
-
const [wx, wy] = getWorldCoords(clientX, clientY,
|
|
382
|
-
const newImages = await innerAddImages(fileList, (src) =>
|
|
383
|
-
|
|
496
|
+
const [wx, wy] = getWorldCoords(clientX, clientY, this.#canvas);
|
|
497
|
+
const newImages = await innerAddImages(fileList, (src) => this.#canvas.addImageToCanvas(src, wx, wy, 1, 1, true));
|
|
498
|
+
this.#history.push(makeMultiAddChildCommand(this.#canvas, newImages));
|
|
384
499
|
}
|
|
385
500
|
async removeImage(id) {
|
|
386
|
-
if (!
|
|
501
|
+
if (!this.#canvas)
|
|
387
502
|
return;
|
|
388
|
-
const child =
|
|
389
|
-
|
|
390
|
-
|
|
503
|
+
const child = this.#canvas.getChild(id);
|
|
504
|
+
this.#canvas.removeChild(child);
|
|
505
|
+
this.#history.push(makeRemoveChildCommand(this.#canvas, child));
|
|
391
506
|
}
|
|
392
507
|
/**
|
|
393
508
|
* @param url Currently only accept base64 url and the image is automatically added to screen center
|
|
394
509
|
*/
|
|
395
510
|
async addImageFromUrl(url) {
|
|
396
|
-
if (!
|
|
511
|
+
if (!this.#canvas)
|
|
397
512
|
return;
|
|
398
|
-
const rect =
|
|
513
|
+
const rect = this.#rootDiv.getBoundingClientRect();
|
|
399
514
|
const clientX = rect.left + rect.width / 2;
|
|
400
515
|
const clientY = rect.top + rect.height / 2;
|
|
401
|
-
const [wx, wy] = getWorldCoords(clientX, clientY,
|
|
402
|
-
const img = await
|
|
403
|
-
|
|
516
|
+
const [wx, wy] = getWorldCoords(clientX, clientY, this.#canvas);
|
|
517
|
+
const img = await this.#canvas.addImageToCanvas(url, wx, wy, 1, 1, true);
|
|
518
|
+
this.#history.push(makeMultiAddChildCommand(this.#canvas, [img]));
|
|
404
519
|
}
|
|
405
520
|
async copyImage() {
|
|
406
|
-
if (!
|
|
521
|
+
if (!this.#canvas)
|
|
407
522
|
return;
|
|
408
|
-
await copy(
|
|
523
|
+
await copy(this.#canvas.getSelected());
|
|
409
524
|
}
|
|
410
525
|
async pasteImage(e) {
|
|
411
|
-
if (!
|
|
526
|
+
if (!this.#canvas)
|
|
412
527
|
return;
|
|
413
|
-
await paste(e.clientX, e.clientY,
|
|
528
|
+
await paste(e.clientX, e.clientY, this.#canvas, this.#history, false);
|
|
414
529
|
}
|
|
415
530
|
flipVertical() {
|
|
416
|
-
if (!
|
|
531
|
+
if (!this.#canvas)
|
|
417
532
|
return;
|
|
418
|
-
|
|
533
|
+
this.#canvas.selectionManager.flip("vertical");
|
|
419
534
|
}
|
|
420
535
|
flipHorizontal() {
|
|
421
|
-
if (!
|
|
536
|
+
if (!this.#canvas)
|
|
422
537
|
return;
|
|
423
|
-
|
|
538
|
+
this.#canvas.selectionManager.flip("horizontal");
|
|
424
539
|
}
|
|
425
540
|
align(direction) {
|
|
426
|
-
if (!
|
|
541
|
+
if (!this.#canvas)
|
|
427
542
|
return;
|
|
428
|
-
|
|
543
|
+
this.#canvas.selectionManager.alignSelection(direction);
|
|
429
544
|
}
|
|
430
545
|
normalizeSelection(type, mode) {
|
|
431
|
-
if (!
|
|
546
|
+
if (!this.#canvas)
|
|
432
547
|
return;
|
|
433
|
-
|
|
548
|
+
this.#canvas.selectionManager.normalize(type, mode);
|
|
434
549
|
}
|
|
435
550
|
sendShapeToNewZOrder(toFront) {
|
|
436
|
-
if (!
|
|
551
|
+
if (!this.#canvas)
|
|
437
552
|
return;
|
|
438
|
-
|
|
553
|
+
this.#canvas.setShapeZOrder(toFront);
|
|
439
554
|
}
|
|
440
555
|
deleteSelectedImages() {
|
|
441
|
-
if (!
|
|
556
|
+
if (!this.#canvas)
|
|
442
557
|
return;
|
|
443
|
-
|
|
558
|
+
this.#canvas.selectionManager.deleteSelected(this.#canvas);
|
|
444
559
|
}
|
|
445
560
|
async exportCanvas(filename = "infinite-canvas.json") {
|
|
446
|
-
if (!
|
|
561
|
+
if (!this.#canvas)
|
|
447
562
|
return;
|
|
448
|
-
|
|
563
|
+
this.#eventHub.emit(LoaderEvent.start, "spinner");
|
|
449
564
|
const files = await this.getAllImageFileMetdata();
|
|
450
|
-
const data = serializeCanvas(
|
|
565
|
+
const data = serializeCanvas(this.#canvas, files);
|
|
451
566
|
downloadJSON(filename, data);
|
|
452
|
-
|
|
567
|
+
this.#eventHub.emit(LoaderEvent.done);
|
|
453
568
|
}
|
|
454
569
|
async importCanvas(fileList) {
|
|
455
|
-
|
|
456
|
-
if (!
|
|
570
|
+
this.#eventHub.emit(LoaderEvent.start, "spinner");
|
|
571
|
+
if (!this.#canvas)
|
|
457
572
|
return;
|
|
458
573
|
if (!fileList || fileList.length !== 1)
|
|
459
574
|
return;
|
|
@@ -463,182 +578,32 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
463
578
|
!file.name.toLowerCase().endsWith(".json")))
|
|
464
579
|
return;
|
|
465
580
|
const data = await readJSONFile(file);
|
|
466
|
-
await deserializeCanvas(data,
|
|
467
|
-
|
|
468
|
-
|
|
581
|
+
await deserializeCanvas(data, this.#canvas, this.getImageFileMetadata, this.saveImageFileMetadata);
|
|
582
|
+
this.#eventHub.emit(SaveEvent.Save);
|
|
583
|
+
this.#eventHub.emit(LoaderEvent.done);
|
|
469
584
|
}
|
|
470
585
|
clearCanvas() {
|
|
471
|
-
if (!
|
|
586
|
+
if (!this.#canvas)
|
|
472
587
|
return;
|
|
473
|
-
|
|
588
|
+
this.#canvas.clearChildren();
|
|
474
589
|
}
|
|
475
590
|
/**
|
|
476
591
|
* A non-reactive method that captures the number of images added to the canvas
|
|
477
592
|
*/
|
|
478
593
|
getTotalNumberOfChildren() {
|
|
479
|
-
if (!
|
|
594
|
+
if (!this.#canvas)
|
|
480
595
|
return;
|
|
481
|
-
return
|
|
596
|
+
return this.#canvas.totalNumberOfChildren;
|
|
482
597
|
}
|
|
483
598
|
/**
|
|
484
599
|
* A non-reactive method that captures the number of images that are in camera
|
|
485
600
|
*/
|
|
486
601
|
getNumberOfChildrenRendered() {
|
|
487
|
-
if (!
|
|
602
|
+
if (!this.#canvas)
|
|
488
603
|
return;
|
|
489
|
-
return
|
|
604
|
+
return this.#canvas.numberOfChildrenRendered;
|
|
490
605
|
}
|
|
491
606
|
};
|
|
492
|
-
_InfiniteCanvasElement_canvas = new WeakMap();
|
|
493
|
-
_InfiniteCanvasElement_eventHub = new WeakMap();
|
|
494
|
-
_InfiniteCanvasElement_resizeObserver = new WeakMap();
|
|
495
|
-
_InfiniteCanvasElement_history = new WeakMap();
|
|
496
|
-
_InfiniteCanvasElement_fileStorage = new WeakMap();
|
|
497
|
-
_InfiniteCanvasElement_canvasStorage = new WeakMap();
|
|
498
|
-
_InfiniteCanvasElement_saveFrequency = new WeakMap();
|
|
499
|
-
_InfiniteCanvasElement_timeoutId = new WeakMap();
|
|
500
|
-
_InfiniteCanvasElement_intervalId = new WeakMap();
|
|
501
|
-
_InfiniteCanvasElement_onViewportResize = new WeakMap();
|
|
502
|
-
_InfiniteCanvasElement_rootDiv = new WeakMap();
|
|
503
|
-
_InfiniteCanvasElement_onChange = new WeakMap();
|
|
504
|
-
_InfiniteCanvasElement_singleImageMenuOptions = new WeakMap();
|
|
505
|
-
_InfiniteCanvasElement_multiImageMenuOptions = new WeakMap();
|
|
506
|
-
_InfiniteCanvasElement_canvasImageMenuOptions = new WeakMap();
|
|
507
|
-
InfiniteCanvasElement.properties = {
|
|
508
|
-
name: { type: String, reflect: true },
|
|
509
|
-
width: { type: String, reflect: true },
|
|
510
|
-
height: { type: String, reflect: true },
|
|
511
|
-
displayMode: { type: String, reflect: true },
|
|
512
|
-
};
|
|
513
|
-
InfiniteCanvasElement.styles = css `
|
|
514
|
-
:host {
|
|
515
|
-
position: relative;
|
|
516
|
-
overflow: hidden;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
.context-menu {
|
|
520
|
-
position: absolute;
|
|
521
|
-
background: white;
|
|
522
|
-
min-width: 180px;
|
|
523
|
-
background: var(--menu-bg, #fff);
|
|
524
|
-
border-radius: 6px;
|
|
525
|
-
border: 1px solid var(--menu-border, #9f9f9fff);
|
|
526
|
-
box-sizing: border-box;
|
|
527
|
-
padding: 6px 0;
|
|
528
|
-
display: flex;
|
|
529
|
-
gap: 2px;
|
|
530
|
-
flex-direction: column;
|
|
531
|
-
font-family: system-ui, sans-serif;
|
|
532
|
-
animation: fadeInMenu 0.13s cubic-bezier(0.4, 0, 0.2, 1);
|
|
533
|
-
overflow: scroll;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
@keyframes fadeInMenu {
|
|
537
|
-
from {
|
|
538
|
-
opacity: 0;
|
|
539
|
-
transform: translateY(8px);
|
|
540
|
-
}
|
|
541
|
-
to {
|
|
542
|
-
opacity: 1;
|
|
543
|
-
transform: none;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
.context-menu button {
|
|
548
|
-
all: unset;
|
|
549
|
-
display: flex;
|
|
550
|
-
align-items: center;
|
|
551
|
-
box-sizing: border-box;
|
|
552
|
-
width: 100%;
|
|
553
|
-
padding: 8px 18px;
|
|
554
|
-
font-size: 15px;
|
|
555
|
-
color: var(--menu-fg, #222);
|
|
556
|
-
background: none;
|
|
557
|
-
cursor: pointer;
|
|
558
|
-
transition:
|
|
559
|
-
background 0.1s,
|
|
560
|
-
color 0.1s;
|
|
561
|
-
user-select: none;
|
|
562
|
-
outline: none;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
.context-menu button:hover,
|
|
566
|
-
.context-menu button:focus-visible {
|
|
567
|
-
background: var(--menu-hover, #c7d5eaff);
|
|
568
|
-
color: var(--menu-accent, #155290ff);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
.context-menu button:active {
|
|
572
|
-
background: var(--menu-active, #e3eaf3);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
.context-menu button[disabled] {
|
|
576
|
-
color: #aaa;
|
|
577
|
-
cursor: not-allowed;
|
|
578
|
-
background: none;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
.context-menu-divider {
|
|
582
|
-
height: 1px;
|
|
583
|
-
background: var(--menu-divider, #c7d5eaff);
|
|
584
|
-
margin: 6px 12px;
|
|
585
|
-
border: none;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
canvas {
|
|
589
|
-
width: 100%;
|
|
590
|
-
height: 100%;
|
|
591
|
-
outline: none;
|
|
592
|
-
padding: 0;
|
|
593
|
-
margin: 0;
|
|
594
|
-
touch-action: none;
|
|
595
|
-
display: block;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
.canvas-loader {
|
|
599
|
-
position: absolute;
|
|
600
|
-
top: 0;
|
|
601
|
-
left: 0;
|
|
602
|
-
display: flex;
|
|
603
|
-
flex-direction: column;
|
|
604
|
-
align-items: center;
|
|
605
|
-
justify-content: center;
|
|
606
|
-
background: rgba(255, 255, 255, 0.7);
|
|
607
|
-
z-index: 1000;
|
|
608
|
-
pointer-events: all;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
.canvas-loader-spinner {
|
|
612
|
-
width: 48px;
|
|
613
|
-
height: 48px;
|
|
614
|
-
border: 6px solid #e0e0e0;
|
|
615
|
-
border-top: 6px solid #1976d2;
|
|
616
|
-
border-radius: 50%;
|
|
617
|
-
animation: canvas-loader-spin 1s linear infinite;
|
|
618
|
-
margin-bottom: 16px;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
@keyframes canvas-loader-spin {
|
|
622
|
-
0% {
|
|
623
|
-
transform: rotate(0deg);
|
|
624
|
-
}
|
|
625
|
-
100% {
|
|
626
|
-
transform: rotate(360deg);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
.canvas-loader-message {
|
|
631
|
-
font-size: 1.1rem;
|
|
632
|
-
color: #333;
|
|
633
|
-
background: rgba(255, 255, 255, 0.9);
|
|
634
|
-
padding: 8px 16px;
|
|
635
|
-
border-radius: 4px;
|
|
636
|
-
margin-top: 8px;
|
|
637
|
-
text-align: center;
|
|
638
|
-
max-width: 80%;
|
|
639
|
-
word-break: break-word;
|
|
640
|
-
}
|
|
641
|
-
`;
|
|
642
607
|
InfiniteCanvasElement = __decorate([
|
|
643
608
|
customElement("infinite-canvas")
|
|
644
609
|
], InfiniteCanvasElement);
|