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