@excalidraw/excalidraw 0.17.1-3e334a6 → 0.17.1-4689a6b
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/CHANGELOG.md +1 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-M7HSOQ7X.js → chunk-23CKV3WP.js} +3 -1
- package/dist/browser/dev/excalidraw-assets-dev/chunk-23CKV3WP.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-RWZVJAQU.js → chunk-7D5BMEAB.js} +2227 -1976
- package/dist/browser/dev/excalidraw-assets-dev/chunk-7D5BMEAB.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{en-R45KN4KN.js → en-W7TECCRB.js} +2 -2
- package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js → image-JKT6GXZD.js} +2 -2
- package/dist/browser/dev/index.css +20 -0
- package/dist/browser/dev/index.css.map +2 -2
- package/dist/browser/dev/index.js +766 -580
- package/dist/browser/dev/index.js.map +4 -4
- package/dist/browser/prod/excalidraw-assets/chunk-DWOM5R6H.js +55 -0
- package/dist/browser/prod/excalidraw-assets/{chunk-DIHRGRYX.js → chunk-SK23VHAR.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/{en-H6IY7PV6.js → en-SMMH575S.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/image-WDEQS5RL.js +1 -0
- package/dist/browser/prod/index.css +1 -1
- package/dist/browser/prod/index.js +22 -22
- package/dist/{prod/en-N2RZZLK5.json → dev/en-CVBEBUBY.json} +2 -0
- package/dist/dev/index.css +20 -0
- package/dist/dev/index.css.map +2 -2
- package/dist/dev/index.js +2379 -2069
- package/dist/dev/index.js.map +4 -4
- package/dist/excalidraw/actions/actionBoundText.js +4 -1
- package/dist/excalidraw/actions/actionCanvas.js +3 -1
- package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -0
- package/dist/excalidraw/actions/actionExport.d.ts +1 -1
- package/dist/excalidraw/actions/actionFinalize.d.ts +1 -1
- package/dist/excalidraw/actions/actionFinalize.js +3 -3
- package/dist/excalidraw/actions/actionFlip.d.ts +3 -3
- package/dist/excalidraw/actions/actionFlip.js +6 -6
- package/dist/excalidraw/actions/actionGroup.js +4 -2
- package/dist/excalidraw/actions/actionHistory.js +3 -0
- package/dist/excalidraw/actions/actionZindex.d.ts +11 -11
- package/dist/excalidraw/analytics.js +1 -1
- package/dist/excalidraw/components/App.d.ts +13 -3
- package/dist/excalidraw/components/App.js +211 -81
- package/dist/excalidraw/components/CommandPalette/CommandPalette.js +24 -10
- package/dist/excalidraw/components/DarkModeToggle.js +3 -1
- package/dist/excalidraw/components/HelpDialog.js +2 -2
- package/dist/excalidraw/components/RadioGroup.d.ts +2 -1
- package/dist/excalidraw/components/RadioGroup.js +1 -1
- package/dist/excalidraw/components/TTDDialog/MermaidToExcalidraw.js +6 -2
- package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.d.ts +18 -0
- package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.js +9 -0
- package/dist/excalidraw/components/hyperlink/Hyperlink.js +3 -3
- package/dist/excalidraw/components/hyperlink/helpers.js +2 -3
- package/dist/excalidraw/components/icons.d.ts +3 -0
- package/dist/excalidraw/components/icons.js +5 -1
- package/dist/excalidraw/components/main-menu/DefaultItems.d.ts +12 -2
- package/dist/excalidraw/components/main-menu/DefaultItems.js +38 -7
- package/dist/excalidraw/constants.d.ts +0 -3
- package/dist/excalidraw/constants.js +0 -3
- package/dist/excalidraw/data/magic.js +2 -1
- package/dist/excalidraw/data/reconcile.d.ts +6 -0
- package/dist/excalidraw/data/reconcile.js +49 -0
- package/dist/excalidraw/data/restore.d.ts +3 -3
- package/dist/excalidraw/data/restore.js +5 -6
- package/dist/excalidraw/data/transform.d.ts +1 -1
- package/dist/excalidraw/data/transform.js +12 -3
- package/dist/excalidraw/element/binding.d.ts +22 -9
- package/dist/excalidraw/element/binding.js +403 -26
- package/dist/excalidraw/element/bounds.d.ts +0 -1
- package/dist/excalidraw/element/bounds.js +0 -3
- package/dist/excalidraw/element/collision.d.ts +14 -19
- package/dist/excalidraw/element/collision.js +36 -713
- package/dist/excalidraw/element/embeddable.js +18 -43
- package/dist/excalidraw/element/index.d.ts +0 -1
- package/dist/excalidraw/element/index.js +0 -1
- package/dist/excalidraw/element/linearElementEditor.d.ts +10 -10
- package/dist/excalidraw/element/linearElementEditor.js +6 -4
- package/dist/excalidraw/element/newElement.d.ts +1 -1
- package/dist/excalidraw/element/newElement.js +2 -1
- package/dist/excalidraw/element/textElement.d.ts +0 -1
- package/dist/excalidraw/element/textElement.js +0 -30
- package/dist/excalidraw/element/types.d.ts +17 -2
- package/dist/excalidraw/errors.d.ts +3 -0
- package/dist/excalidraw/errors.js +3 -0
- package/dist/excalidraw/fractionalIndex.d.ts +40 -0
- package/dist/excalidraw/fractionalIndex.js +241 -0
- package/dist/excalidraw/frame.d.ts +1 -1
- package/dist/excalidraw/hooks/useCreatePortalContainer.js +2 -1
- package/dist/excalidraw/locales/en.json +2 -0
- package/dist/excalidraw/renderer/helpers.js +2 -2
- package/dist/excalidraw/renderer/interactiveScene.js +1 -1
- package/dist/excalidraw/renderer/renderElement.js +3 -3
- package/dist/excalidraw/renderer/renderSnaps.js +2 -1
- package/dist/excalidraw/scene/Scene.d.ts +7 -6
- package/dist/excalidraw/scene/Scene.js +28 -13
- package/dist/excalidraw/scene/export.js +4 -3
- package/dist/excalidraw/types.d.ts +4 -3
- package/dist/excalidraw/utils.d.ts +1 -0
- package/dist/excalidraw/utils.js +1 -0
- package/dist/excalidraw/zindex.d.ts +2 -2
- package/dist/excalidraw/zindex.js +9 -13
- package/dist/{dev/en-N2RZZLK5.json → prod/en-CVBEBUBY.json} +2 -0
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +36 -36
- package/dist/utils/collision.d.ts +4 -0
- package/dist/utils/collision.js +48 -0
- package/dist/utils/geometry/geometry.d.ts +71 -0
- package/dist/utils/geometry/geometry.js +674 -0
- package/dist/utils/geometry/shape.d.ts +55 -0
- package/dist/utils/geometry/shape.js +149 -0
- package/package.json +2 -1
- package/dist/browser/dev/excalidraw-assets-dev/chunk-M7HSOQ7X.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/chunk-RWZVJAQU.js.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-LL4GORAM.js +0 -55
- package/dist/browser/prod/excalidraw-assets/image-EFCJDJH3.js +0 -1
- /package/dist/browser/dev/excalidraw-assets-dev/{en-R45KN4KN.js.map → en-W7TECCRB.js.map} +0 -0
- /package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js.map → image-JKT6GXZD.js.map} +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { generateNKeysBetween } from "fractional-indexing";
|
|
2
|
+
import { mutateElement } from "./element/mutateElement";
|
|
3
|
+
import { InvalidFractionalIndexError } from "./errors";
|
|
4
|
+
/**
|
|
5
|
+
* Envisioned relation between array order and fractional indices:
|
|
6
|
+
*
|
|
7
|
+
* 1) Array (or array-like ordered data structure) should be used as a cache of elements order, hiding the internal fractional indices implementation.
|
|
8
|
+
* - it's undesirable to to perform reorder for each related operation, thefeore it's necessary to cache the order defined by fractional indices into an ordered data structure
|
|
9
|
+
* - it's easy enough to define the order of the elements from the outside (boundaries), without worrying about the underlying structure of fractional indices (especially for the host apps)
|
|
10
|
+
* - it's necessary to always keep the array support for backwards compatibility (restore) - old scenes, old libraries, supporting multiple excalidraw versions etc.
|
|
11
|
+
* - it's necessary to always keep the fractional indices in sync with the array order
|
|
12
|
+
* - elements with invalid indices should be detected and synced, without altering the already valid indices
|
|
13
|
+
*
|
|
14
|
+
* 2) Fractional indices should be used to reorder the elements, whenever the cached order is expected to be invalidated.
|
|
15
|
+
* - as the fractional indices are encoded as part of the elements, it opens up possibilties for incremental-like APIs
|
|
16
|
+
* - re-order based on fractional indices should be part of (multiplayer) operations such as reconcillitation & undo/redo
|
|
17
|
+
* - technically all the z-index actions could perform also re-order based on fractional indices,but in current state it would not bring much benefits,
|
|
18
|
+
* as it's faster & more efficient to perform re-order based on array manipulation and later synchronisation of moved indices with the array order
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Ensure that all elements have valid fractional indices.
|
|
22
|
+
*
|
|
23
|
+
* @throws `InvalidFractionalIndexError` if invalid index is detected.
|
|
24
|
+
*/
|
|
25
|
+
export const validateFractionalIndices = (indices) => {
|
|
26
|
+
for (const [i, index] of indices.entries()) {
|
|
27
|
+
const predecessorIndex = indices[i - 1];
|
|
28
|
+
const successorIndex = indices[i + 1];
|
|
29
|
+
if (!isValidFractionalIndex(index, predecessorIndex, successorIndex)) {
|
|
30
|
+
throw new InvalidFractionalIndexError(`Fractional indices invariant for element has been compromised - ["${predecessorIndex}", "${index}", "${successorIndex}"] [predecessor, current, successor]`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Order the elements based on the fractional indices.
|
|
36
|
+
* - when fractional indices are identical, break the tie based on the element id
|
|
37
|
+
* - when there is no fractional index in one of the elements, respect the order of the array
|
|
38
|
+
*/
|
|
39
|
+
export const orderByFractionalIndex = (elements) => {
|
|
40
|
+
return elements.sort((a, b) => {
|
|
41
|
+
// in case the indices are not the defined at runtime
|
|
42
|
+
if (isOrderedElement(a) && isOrderedElement(b)) {
|
|
43
|
+
if (a.index < b.index) {
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
else if (a.index > b.index) {
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
// break ties based on the element id
|
|
50
|
+
return a.id < b.id ? -1 : 1;
|
|
51
|
+
}
|
|
52
|
+
// defensively keep the array order
|
|
53
|
+
return 1;
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Synchronizes invalid fractional indices of moved elements with the array order by mutating passed elements.
|
|
58
|
+
* If the synchronization fails or the result is invalid, it fallbacks to `syncInvalidIndices`.
|
|
59
|
+
*/
|
|
60
|
+
export const syncMovedIndices = (elements, movedElements) => {
|
|
61
|
+
try {
|
|
62
|
+
const indicesGroups = getMovedIndicesGroups(elements, movedElements);
|
|
63
|
+
// try generatating indices, throws on invalid movedElements
|
|
64
|
+
const elementsUpdates = generateIndices(elements, indicesGroups);
|
|
65
|
+
// ensure next indices are valid before mutation, throws on invalid ones
|
|
66
|
+
validateFractionalIndices(elements.map((x) => elementsUpdates.get(x)?.index || x.index));
|
|
67
|
+
// split mutation so we don't end up in an incosistent state
|
|
68
|
+
for (const [element, update] of elementsUpdates) {
|
|
69
|
+
mutateElement(element, update, false);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
// fallback to default sync
|
|
74
|
+
syncInvalidIndices(elements);
|
|
75
|
+
}
|
|
76
|
+
return elements;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Synchronizes all invalid fractional indices with the array order by mutating passed elements.
|
|
80
|
+
*
|
|
81
|
+
* WARN: in edge cases it could modify the elements which were not moved, as it's impossible to guess the actually moved elements from the elements array itself.
|
|
82
|
+
*/
|
|
83
|
+
export const syncInvalidIndices = (elements) => {
|
|
84
|
+
const indicesGroups = getInvalidIndicesGroups(elements);
|
|
85
|
+
const elementsUpdates = generateIndices(elements, indicesGroups);
|
|
86
|
+
for (const [element, update] of elementsUpdates) {
|
|
87
|
+
mutateElement(element, update, false);
|
|
88
|
+
}
|
|
89
|
+
return elements;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Get contiguous groups of indices of passed moved elements.
|
|
93
|
+
*
|
|
94
|
+
* NOTE: First and last elements within the groups are indices of lower and upper bounds.
|
|
95
|
+
*/
|
|
96
|
+
const getMovedIndicesGroups = (elements, movedElements) => {
|
|
97
|
+
const indicesGroups = [];
|
|
98
|
+
let i = 0;
|
|
99
|
+
while (i < elements.length) {
|
|
100
|
+
if (movedElements.has(elements[i].id) &&
|
|
101
|
+
!isValidFractionalIndex(elements[i]?.index, elements[i - 1]?.index, elements[i + 1]?.index)) {
|
|
102
|
+
const indicesGroup = [i - 1, i]; // push the lower bound index as the first item
|
|
103
|
+
while (++i < elements.length) {
|
|
104
|
+
if (!(movedElements.has(elements[i].id) &&
|
|
105
|
+
!isValidFractionalIndex(elements[i]?.index, elements[i - 1]?.index, elements[i + 1]?.index))) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
indicesGroup.push(i);
|
|
109
|
+
}
|
|
110
|
+
indicesGroup.push(i); // push the upper bound index as the last item
|
|
111
|
+
indicesGroups.push(indicesGroup);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
i++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return indicesGroups;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Gets contiguous groups of all invalid indices automatically detected inside the elements array.
|
|
121
|
+
*
|
|
122
|
+
* WARN: First and last items within the groups do NOT have to be contiguous, those are the found lower and upper bounds!
|
|
123
|
+
*/
|
|
124
|
+
const getInvalidIndicesGroups = (elements) => {
|
|
125
|
+
const indicesGroups = [];
|
|
126
|
+
// once we find lowerBound / upperBound, it cannot be lower than that, so we cache it for better perf.
|
|
127
|
+
let lowerBound = undefined;
|
|
128
|
+
let upperBound = undefined;
|
|
129
|
+
let lowerBoundIndex = -1;
|
|
130
|
+
let upperBoundIndex = 0;
|
|
131
|
+
/** @returns maybe valid lowerBound */
|
|
132
|
+
const getLowerBound = (index) => {
|
|
133
|
+
const lowerBound = elements[lowerBoundIndex]
|
|
134
|
+
? elements[lowerBoundIndex].index
|
|
135
|
+
: undefined;
|
|
136
|
+
// we are already iterating left to right, therefore there is no need for additional looping
|
|
137
|
+
const candidate = elements[index - 1]?.index;
|
|
138
|
+
if ((!lowerBound && candidate) || // first lowerBound
|
|
139
|
+
(lowerBound && candidate && candidate > lowerBound) // next lowerBound
|
|
140
|
+
) {
|
|
141
|
+
// WARN: candidate's index could be higher or same as the current element's index
|
|
142
|
+
return [candidate, index - 1];
|
|
143
|
+
}
|
|
144
|
+
// cache hit! take the last lower bound
|
|
145
|
+
return [lowerBound, lowerBoundIndex];
|
|
146
|
+
};
|
|
147
|
+
/** @returns always valid upperBound */
|
|
148
|
+
const getUpperBound = (index) => {
|
|
149
|
+
const upperBound = elements[upperBoundIndex]
|
|
150
|
+
? elements[upperBoundIndex].index
|
|
151
|
+
: undefined;
|
|
152
|
+
// cache hit! don't let it find the upper bound again
|
|
153
|
+
if (upperBound && index < upperBoundIndex) {
|
|
154
|
+
return [upperBound, upperBoundIndex];
|
|
155
|
+
}
|
|
156
|
+
// set the current upperBoundIndex as the starting point
|
|
157
|
+
let i = upperBoundIndex;
|
|
158
|
+
while (++i < elements.length) {
|
|
159
|
+
const candidate = elements[i]?.index;
|
|
160
|
+
if ((!upperBound && candidate) || // first upperBound
|
|
161
|
+
(upperBound && candidate && candidate > upperBound) // next upperBound
|
|
162
|
+
) {
|
|
163
|
+
return [candidate, i];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// we reached the end, sky is the limit
|
|
167
|
+
return [undefined, i];
|
|
168
|
+
};
|
|
169
|
+
let i = 0;
|
|
170
|
+
while (i < elements.length) {
|
|
171
|
+
const current = elements[i].index;
|
|
172
|
+
[lowerBound, lowerBoundIndex] = getLowerBound(i);
|
|
173
|
+
[upperBound, upperBoundIndex] = getUpperBound(i);
|
|
174
|
+
if (!isValidFractionalIndex(current, lowerBound, upperBound)) {
|
|
175
|
+
// push the lower bound index as the first item
|
|
176
|
+
const indicesGroup = [lowerBoundIndex, i];
|
|
177
|
+
while (++i < elements.length) {
|
|
178
|
+
const current = elements[i].index;
|
|
179
|
+
const [nextLowerBound, nextLowerBoundIndex] = getLowerBound(i);
|
|
180
|
+
const [nextUpperBound, nextUpperBoundIndex] = getUpperBound(i);
|
|
181
|
+
if (isValidFractionalIndex(current, nextLowerBound, nextUpperBound)) {
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
// assign bounds only for the moved elements
|
|
185
|
+
[lowerBound, lowerBoundIndex] = [nextLowerBound, nextLowerBoundIndex];
|
|
186
|
+
[upperBound, upperBoundIndex] = [nextUpperBound, nextUpperBoundIndex];
|
|
187
|
+
indicesGroup.push(i);
|
|
188
|
+
}
|
|
189
|
+
// push the upper bound index as the last item
|
|
190
|
+
indicesGroup.push(upperBoundIndex);
|
|
191
|
+
indicesGroups.push(indicesGroup);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
i++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return indicesGroups;
|
|
198
|
+
};
|
|
199
|
+
const isValidFractionalIndex = (index, predecessor, successor) => {
|
|
200
|
+
if (!index) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (predecessor && successor) {
|
|
204
|
+
return predecessor < index && index < successor;
|
|
205
|
+
}
|
|
206
|
+
if (!predecessor && successor) {
|
|
207
|
+
// first element
|
|
208
|
+
return index < successor;
|
|
209
|
+
}
|
|
210
|
+
if (predecessor && !successor) {
|
|
211
|
+
// last element
|
|
212
|
+
return predecessor < index;
|
|
213
|
+
}
|
|
214
|
+
// only element in the array
|
|
215
|
+
return !!index;
|
|
216
|
+
};
|
|
217
|
+
const generateIndices = (elements, indicesGroups) => {
|
|
218
|
+
const elementsUpdates = new Map();
|
|
219
|
+
for (const indices of indicesGroups) {
|
|
220
|
+
const lowerBoundIndex = indices.shift();
|
|
221
|
+
const upperBoundIndex = indices.pop();
|
|
222
|
+
const fractionalIndices = generateNKeysBetween(elements[lowerBoundIndex]?.index, elements[upperBoundIndex]?.index, indices.length);
|
|
223
|
+
for (let i = 0; i < indices.length; i++) {
|
|
224
|
+
const element = elements[indices[i]];
|
|
225
|
+
elementsUpdates.set(element, {
|
|
226
|
+
index: fractionalIndices[i],
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return elementsUpdates;
|
|
231
|
+
};
|
|
232
|
+
const isOrderedElement = (element) => {
|
|
233
|
+
// for now it's sufficient whether the index is there
|
|
234
|
+
// meaning, the element was already ordered in the past
|
|
235
|
+
// meaning, it is not a newly inserted element, not an unrestored element, etc.
|
|
236
|
+
// it does not have to mean that the index itself is valid
|
|
237
|
+
if (element.index) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
};
|
|
@@ -2,7 +2,7 @@ import { ElementsMap, ElementsMapOrArray, ExcalidrawElement, ExcalidrawFrameLike
|
|
|
2
2
|
import { AppClassProperties, AppState, StaticCanvasAppState } from "./types";
|
|
3
3
|
import type { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
|
|
4
4
|
import { ReadonlySetLike } from "./utility-types";
|
|
5
|
-
export declare const bindElementsToFramesAfterDuplication: (nextElements: ExcalidrawElement[], oldElements: readonly ExcalidrawElement[], oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>) => void;
|
|
5
|
+
export declare const bindElementsToFramesAfterDuplication: (nextElements: readonly ExcalidrawElement[], oldElements: readonly ExcalidrawElement[], oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>) => void;
|
|
6
6
|
export declare function isElementIntersectingFrame(element: ExcalidrawElement, frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap): boolean;
|
|
7
7
|
export declare const getElementsCompletelyInFrame: (elements: readonly ExcalidrawElement[], frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap) => ExcalidrawElement[];
|
|
8
8
|
export declare const isElementContainingFrame: (elements: readonly ExcalidrawElement[], element: ExcalidrawElement, frame: ExcalidrawFrameLikeElement, elementsMap: ElementsMap) => boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useLayoutEffect } from "react";
|
|
2
2
|
import { useDevice, useExcalidrawContainer } from "../components/App";
|
|
3
|
+
import { THEME } from "../constants";
|
|
3
4
|
import { useUIAppState } from "../context/ui-appState";
|
|
4
5
|
export const useCreatePortalContainer = (opts) => {
|
|
5
6
|
const [div, setDiv] = useState(null);
|
|
@@ -11,7 +12,7 @@ export const useCreatePortalContainer = (opts) => {
|
|
|
11
12
|
div.className = "";
|
|
12
13
|
div.classList.add("excalidraw", ...(opts?.className?.split(/\s+/) || []));
|
|
13
14
|
div.classList.toggle("excalidraw--mobile", device.editor.isMobile);
|
|
14
|
-
div.classList.toggle("theme--dark", theme ===
|
|
15
|
+
div.classList.toggle("theme--dark", theme === THEME.DARK);
|
|
15
16
|
}
|
|
16
17
|
}, [div, theme, device.editor.isMobile, opts?.className]);
|
|
17
18
|
useLayoutEffect(() => {
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"showStroke": "Show stroke color picker",
|
|
111
111
|
"showBackground": "Show background color picker",
|
|
112
112
|
"toggleTheme": "Toggle light/dark theme",
|
|
113
|
+
"theme": "Theme",
|
|
113
114
|
"personalLib": "Personal Library",
|
|
114
115
|
"excalidrawLib": "Excalidraw Library",
|
|
115
116
|
"decreaseFontSize": "Decrease font size",
|
|
@@ -180,6 +181,7 @@
|
|
|
180
181
|
"fullScreen": "Full screen",
|
|
181
182
|
"darkMode": "Dark mode",
|
|
182
183
|
"lightMode": "Light mode",
|
|
184
|
+
"systemMode": "System mode",
|
|
183
185
|
"zenMode": "Zen mode",
|
|
184
186
|
"objectsSnapMode": "Snap to objects",
|
|
185
187
|
"exitZenMode": "Exit zen mode",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { THEME_FILTER } from "../constants";
|
|
1
|
+
import { THEME, THEME_FILTER } from "../constants";
|
|
2
2
|
export const fillCircle = (context, cx, cy, radius, stroke = true) => {
|
|
3
3
|
context.beginPath();
|
|
4
4
|
context.arc(cx, cy, radius, 0, Math.PI * 2);
|
|
@@ -15,7 +15,7 @@ export const bootstrapCanvas = ({ canvas, scale, normalizedWidth, normalizedHeig
|
|
|
15
15
|
const context = canvas.getContext("2d");
|
|
16
16
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
17
17
|
context.scale(scale, scale);
|
|
18
|
-
if (isExporting && theme ===
|
|
18
|
+
if (isExporting && theme === THEME.DARK) {
|
|
19
19
|
context.filter = THEME_FILTER;
|
|
20
20
|
}
|
|
21
21
|
// Paint background
|
|
@@ -8,7 +8,7 @@ import { OMIT_SIDES_FOR_FRAME, shouldShowBoundingBox, } from "../element/transfo
|
|
|
8
8
|
import { arrayToMap, throttleRAF } from "../utils";
|
|
9
9
|
import { DEFAULT_TRANSFORM_HANDLE_SPACING, FRAME_STYLE } from "../constants";
|
|
10
10
|
import { renderSnaps } from "../renderer/renderSnaps";
|
|
11
|
-
import { maxBindingGap } from "../element/
|
|
11
|
+
import { maxBindingGap, } from "../element/binding";
|
|
12
12
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
|
13
13
|
import { bootstrapCanvas, fillCircle, getNormalizedCanvasDimensions, } from "./helpers";
|
|
14
14
|
import oc from "open-color";
|
|
@@ -4,7 +4,7 @@ import { distance, getFontString, isRTL } from "../utils";
|
|
|
4
4
|
import { getCornerRadius, isRightAngle } from "../math";
|
|
5
5
|
import rough from "roughjs/bin/rough";
|
|
6
6
|
import { getDefaultAppState } from "../appState";
|
|
7
|
-
import { BOUND_TEXT_PADDING, ELEMENT_READY_TO_ERASE_OPACITY, FRAME_STYLE, MIME_TYPES, } from "../constants";
|
|
7
|
+
import { BOUND_TEXT_PADDING, ELEMENT_READY_TO_ERASE_OPACITY, FRAME_STYLE, MIME_TYPES, THEME, } from "../constants";
|
|
8
8
|
import { getStroke } from "perfect-freehand";
|
|
9
9
|
import { getBoundTextElement, getContainerCoords, getContainerElement, getLineHeightInPx, getBoundTextMaxHeight, getBoundTextMaxWidth, getVerticalOffset, } from "../element/textElement";
|
|
10
10
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
|
@@ -19,7 +19,7 @@ const defaultAppState = getDefaultAppState();
|
|
|
19
19
|
const isPendingImageElement = (element, renderConfig) => isInitializedImageElement(element) &&
|
|
20
20
|
!renderConfig.imageCache.has(element.fileId);
|
|
21
21
|
const shouldResetImageFilter = (element, renderConfig, appState) => {
|
|
22
|
-
return (appState.theme ===
|
|
22
|
+
return (appState.theme === THEME.DARK &&
|
|
23
23
|
isInitializedImageElement(element) &&
|
|
24
24
|
!isPendingImageElement(element, renderConfig) &&
|
|
25
25
|
renderConfig.imageCache.get(element.fileId)?.mimeType !== MIME_TYPES.svg);
|
|
@@ -352,7 +352,7 @@ export const renderElement = (element, elementsMap, allElementsMap, rc, context,
|
|
|
352
352
|
// TODO change later to only affect AI frames
|
|
353
353
|
if (isMagicFrameElement(element)) {
|
|
354
354
|
context.strokeStyle =
|
|
355
|
-
appState.theme ===
|
|
355
|
+
appState.theme === THEME.LIGHT ? "#7affd7" : "#1d8264";
|
|
356
356
|
}
|
|
357
357
|
if (FRAME_STYLE.radius && context.roundRect) {
|
|
358
358
|
context.beginPath();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { THEME } from "../constants";
|
|
1
2
|
const SNAP_COLOR_LIGHT = "#ff6b6b";
|
|
2
3
|
const SNAP_COLOR_DARK = "#ff0000";
|
|
3
4
|
const SNAP_WIDTH = 1;
|
|
@@ -9,7 +10,7 @@ export const renderSnaps = (context, appState) => {
|
|
|
9
10
|
// in dark mode, we need to adjust the color to account for color inversion.
|
|
10
11
|
// Don't change if zen mode, because we draw only crosses, we want the
|
|
11
12
|
// colors to be more visible
|
|
12
|
-
const snapColor = appState.theme ===
|
|
13
|
+
const snapColor = appState.theme === THEME.LIGHT || appState.zenModeEnabled
|
|
13
14
|
? SNAP_COLOR_LIGHT
|
|
14
15
|
: SNAP_COLOR_DARK;
|
|
15
16
|
// in zen mode make the cross more visible since we don't draw the lines
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExcalidrawElement, NonDeletedExcalidrawElement, NonDeleted, ExcalidrawFrameLikeElement, ElementsMapOrArray } from "../element/types";
|
|
1
|
+
import { ExcalidrawElement, NonDeletedExcalidrawElement, NonDeleted, ExcalidrawFrameLikeElement, ElementsMapOrArray, OrderedExcalidrawElement, Ordered } from "../element/types";
|
|
2
2
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
|
3
3
|
import { AppState } from "../types";
|
|
4
4
|
type ElementIdKey = InstanceType<typeof LinearElementEditor>["elementId"];
|
|
@@ -20,10 +20,10 @@ declare class Scene {
|
|
|
20
20
|
private elementsMap;
|
|
21
21
|
private selectedElementsCache;
|
|
22
22
|
private versionNonce;
|
|
23
|
-
getElementsMapIncludingDeleted(): Map<string, ExcalidrawElement
|
|
24
|
-
getNonDeletedElementsMap(): Map<string, NonDeletedExcalidrawElement
|
|
25
|
-
getElementsIncludingDeleted(): readonly
|
|
26
|
-
getNonDeletedElements(): readonly NonDeletedExcalidrawElement[];
|
|
23
|
+
getElementsMapIncludingDeleted(): Map<string, Ordered<ExcalidrawElement>> & import("../utility-types").MakeBrand<"SceneElementsMap">;
|
|
24
|
+
getNonDeletedElementsMap(): Map<string, Ordered<NonDeletedExcalidrawElement>> & import("../utility-types").MakeBrand<"NonDeletedSceneElementsMap">;
|
|
25
|
+
getElementsIncludingDeleted(): readonly OrderedExcalidrawElement[];
|
|
26
|
+
getNonDeletedElements(): readonly Ordered<NonDeletedExcalidrawElement>[];
|
|
27
27
|
getFramesIncludingDeleted(): readonly ExcalidrawFrameLikeElement[];
|
|
28
28
|
getSelectedElements(opts: {
|
|
29
29
|
selectedElementIds: AppState["selectedElementIds"];
|
|
@@ -59,7 +59,8 @@ declare class Scene {
|
|
|
59
59
|
destroy(): void;
|
|
60
60
|
insertElementAtIndex(element: ExcalidrawElement, index: number): void;
|
|
61
61
|
insertElementsAtIndex(elements: ExcalidrawElement[], index: number): void;
|
|
62
|
-
|
|
62
|
+
insertElement: (element: ExcalidrawElement) => void;
|
|
63
|
+
insertElements: (elements: ExcalidrawElement[]) => void;
|
|
63
64
|
getElementIndex(elementId: string): number;
|
|
64
65
|
getContainerElement: (element: (ExcalidrawElement & {
|
|
65
66
|
containerId: ExcalidrawElement["id"] | null;
|
|
@@ -2,7 +2,10 @@ import { isNonDeletedElement } from "../element";
|
|
|
2
2
|
import { isFrameLikeElement } from "../element/typeChecks";
|
|
3
3
|
import { getSelectedElements } from "./selection";
|
|
4
4
|
import { randomInteger } from "../random";
|
|
5
|
+
import { syncInvalidIndices, syncMovedIndices, validateFractionalIndices, } from "../fractionalIndex";
|
|
6
|
+
import { arrayToMap } from "../utils";
|
|
5
7
|
import { toBrandedType } from "../utils";
|
|
8
|
+
import { ENV } from "../constants";
|
|
6
9
|
const getNonDeletedElements = (allElements) => {
|
|
7
10
|
const elementsMap = new Map();
|
|
8
11
|
const elements = [];
|
|
@@ -59,6 +62,7 @@ class Scene {
|
|
|
59
62
|
callbacks = new Set();
|
|
60
63
|
nonDeletedElements = [];
|
|
61
64
|
nonDeletedElementsMap = toBrandedType(new Map());
|
|
65
|
+
// ideally all elements within the scene should be wrapped around with `Ordered` type, but right now there is no real benefit doing so
|
|
62
66
|
elements = [];
|
|
63
67
|
nonDeletedFramesLikes = [];
|
|
64
68
|
frames = [];
|
|
@@ -151,12 +155,17 @@ class Scene {
|
|
|
151
155
|
return didChange;
|
|
152
156
|
}
|
|
153
157
|
replaceAllElements(nextElements) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
const _nextElements =
|
|
159
|
+
// ts doesn't like `Array.isArray` of `instanceof Map`
|
|
160
|
+
nextElements instanceof Array
|
|
161
|
+
? nextElements
|
|
162
|
+
: Array.from(nextElements.values());
|
|
159
163
|
const nextFrameLikes = [];
|
|
164
|
+
if (import.meta.env.DEV || import.meta.env.MODE === ENV.TEST) {
|
|
165
|
+
// throw on invalid indices in test / dev to potentially detect cases were we forgot to sync moved elements
|
|
166
|
+
validateFractionalIndices(_nextElements.map((x) => x.index));
|
|
167
|
+
}
|
|
168
|
+
this.elements = syncInvalidIndices(_nextElements);
|
|
160
169
|
this.elementsMap.clear();
|
|
161
170
|
this.elements.forEach((element) => {
|
|
162
171
|
if (isFrameLikeElement(element)) {
|
|
@@ -191,8 +200,8 @@ class Scene {
|
|
|
191
200
|
};
|
|
192
201
|
}
|
|
193
202
|
destroy() {
|
|
194
|
-
this.nonDeletedElements = [];
|
|
195
203
|
this.elements = [];
|
|
204
|
+
this.nonDeletedElements = [];
|
|
196
205
|
this.nonDeletedFramesLikes = [];
|
|
197
206
|
this.frames = [];
|
|
198
207
|
this.elementsMap.clear();
|
|
@@ -217,6 +226,7 @@ class Scene {
|
|
|
217
226
|
element,
|
|
218
227
|
...this.elements.slice(index),
|
|
219
228
|
];
|
|
229
|
+
syncMovedIndices(nextElements, arrayToMap([element]));
|
|
220
230
|
this.replaceAllElements(nextElements);
|
|
221
231
|
}
|
|
222
232
|
insertElementsAtIndex(elements, index) {
|
|
@@ -228,15 +238,20 @@ class Scene {
|
|
|
228
238
|
...elements,
|
|
229
239
|
...this.elements.slice(index),
|
|
230
240
|
];
|
|
241
|
+
syncMovedIndices(nextElements, arrayToMap(elements));
|
|
231
242
|
this.replaceAllElements(nextElements);
|
|
232
243
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
244
|
+
insertElement = (element) => {
|
|
245
|
+
const index = element.frameId
|
|
246
|
+
? this.getElementIndex(element.frameId)
|
|
247
|
+
: this.elements.length;
|
|
248
|
+
this.insertElementAtIndex(element, index);
|
|
249
|
+
};
|
|
250
|
+
insertElements = (elements) => {
|
|
251
|
+
const index = elements[0].frameId
|
|
252
|
+
? this.getElementIndex(elements[0].frameId)
|
|
253
|
+
: this.elements.length;
|
|
254
|
+
this.insertElementsAtIndex(elements, index);
|
|
240
255
|
};
|
|
241
256
|
getElementIndex(elementId) {
|
|
242
257
|
return this.elements.findIndex((element) => element.id === elementId);
|
|
@@ -2,7 +2,7 @@ import rough from "roughjs/bin/rough";
|
|
|
2
2
|
import { getCommonBounds, getElementAbsoluteCoords, } from "../element/bounds";
|
|
3
3
|
import { renderSceneToSvg } from "../renderer/staticSvgScene";
|
|
4
4
|
import { arrayToMap, distance, getFontString, toBrandedType } from "../utils";
|
|
5
|
-
import { DEFAULT_EXPORT_PADDING, FONT_FAMILY, FRAME_STYLE, SVG_NS, THEME_FILTER, } from "../constants";
|
|
5
|
+
import { DEFAULT_EXPORT_PADDING, FONT_FAMILY, FRAME_STYLE, SVG_NS, THEME, THEME_FILTER, } from "../constants";
|
|
6
6
|
import { getDefaultAppState } from "../appState";
|
|
7
7
|
import { serializeAsJSON } from "../data/json";
|
|
8
8
|
import { getInitializedImageElements, updateImageCache, } from "../element/image";
|
|
@@ -10,6 +10,7 @@ import { getElementsOverlappingFrame, getFrameLikeElements, getFrameLikeTitle, g
|
|
|
10
10
|
import { newTextElement } from "../element";
|
|
11
11
|
import { newElementWith } from "../element/mutateElement";
|
|
12
12
|
import { isFrameElement, isFrameLikeElement } from "../element/typeChecks";
|
|
13
|
+
import { syncInvalidIndices } from "../fractionalIndex";
|
|
13
14
|
import { renderStaticScene } from "../renderer/staticScene";
|
|
14
15
|
const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
|
|
15
16
|
const truncateText = (element, maxWidth) => {
|
|
@@ -128,7 +129,7 @@ export const exportToCanvas = async (elements, appState, files, { exportBackgrou
|
|
|
128
129
|
canvas,
|
|
129
130
|
rc: rough.canvas(canvas),
|
|
130
131
|
elementsMap: toBrandedType(arrayToMap(elementsForRender)),
|
|
131
|
-
allElementsMap: toBrandedType(arrayToMap(elements)),
|
|
132
|
+
allElementsMap: toBrandedType(arrayToMap(syncInvalidIndices(elements))),
|
|
132
133
|
visibleElements: elementsForRender,
|
|
133
134
|
scale,
|
|
134
135
|
appState: {
|
|
@@ -139,7 +140,7 @@ export const exportToCanvas = async (elements, appState, files, { exportBackgrou
|
|
|
139
140
|
scrollY: -minY + exportPadding,
|
|
140
141
|
zoom: defaultAppState.zoom,
|
|
141
142
|
shouldCacheIgnoreZoom: false,
|
|
142
|
-
theme: appState.exportWithDarkMode ?
|
|
143
|
+
theme: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT,
|
|
143
144
|
},
|
|
144
145
|
renderConfig: {
|
|
145
146
|
canvasBackgroundColor: viewBackgroundColor,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { PointerType, ExcalidrawLinearElement, NonDeletedExcalidrawElement, NonDeleted, TextAlign, ExcalidrawElement, GroupId, ExcalidrawBindableElement, Arrowhead, ChartType, FontFamilyValues, FileId, ExcalidrawImageElement, Theme, StrokeRoundness, ExcalidrawEmbeddableElement, ExcalidrawMagicFrameElement, ExcalidrawFrameLikeElement, ExcalidrawElementType, ExcalidrawIframeLikeElement } from "./element/types";
|
|
2
|
+
import { PointerType, ExcalidrawLinearElement, NonDeletedExcalidrawElement, NonDeleted, TextAlign, ExcalidrawElement, GroupId, ExcalidrawBindableElement, Arrowhead, ChartType, FontFamilyValues, FileId, ExcalidrawImageElement, Theme, StrokeRoundness, ExcalidrawEmbeddableElement, ExcalidrawMagicFrameElement, ExcalidrawFrameLikeElement, ExcalidrawElementType, ExcalidrawIframeLikeElement, OrderedExcalidrawElement } from "./element/types";
|
|
3
3
|
import { Action } from "./actions/types";
|
|
4
4
|
import { Point as RoughPoint } from "roughjs/bin/geometry";
|
|
5
5
|
import { LinearElementEditor } from "./element/linearElementEditor";
|
|
@@ -331,7 +331,7 @@ export type OnUserFollowedPayload = {
|
|
|
331
331
|
action: "FOLLOW" | "UNFOLLOW";
|
|
332
332
|
};
|
|
333
333
|
export interface ExcalidrawProps {
|
|
334
|
-
onChange?: (elements: readonly
|
|
334
|
+
onChange?: (elements: readonly OrderedExcalidrawElement[], appState: AppState, files: BinaryFiles) => void;
|
|
335
335
|
initialData?: MaybePromise<ExcalidrawInitialDataState | null>;
|
|
336
336
|
excalidrawAPI?: (api: ExcalidrawImperativeAPI) => void;
|
|
337
337
|
isCollaborating?: boolean;
|
|
@@ -449,6 +449,7 @@ export type AppClassProperties = {
|
|
|
449
449
|
setOpenDialog: App["setOpenDialog"];
|
|
450
450
|
insertEmbeddableElement: App["insertEmbeddableElement"];
|
|
451
451
|
onMagicframeToolSelect: App["onMagicframeToolSelect"];
|
|
452
|
+
getElementShape: App["getElementShape"];
|
|
452
453
|
getName: App["getName"];
|
|
453
454
|
};
|
|
454
455
|
export type PointerDownState = Readonly<{
|
|
@@ -550,7 +551,7 @@ export type Device = Readonly<{
|
|
|
550
551
|
};
|
|
551
552
|
isTouchScreen: boolean;
|
|
552
553
|
}>;
|
|
553
|
-
type FrameNameBounds = {
|
|
554
|
+
export type FrameNameBounds = {
|
|
554
555
|
x: number;
|
|
555
556
|
y: number;
|
|
556
557
|
width: number;
|
|
@@ -161,6 +161,7 @@ export declare const arrayToMapWithIndex: <T extends {
|
|
|
161
161
|
id: string;
|
|
162
162
|
}>(elements: readonly T[]) => Map<string, [element: T, index: number]>;
|
|
163
163
|
export declare const isTestEnv: () => boolean;
|
|
164
|
+
export declare const isDevEnv: () => boolean;
|
|
164
165
|
export declare const wrapEvent: <T extends Event>(name: EVENT, nativeEvent: T) => CustomEvent<{
|
|
165
166
|
nativeEvent: T;
|
|
166
167
|
}>;
|
package/dist/excalidraw/utils.js
CHANGED
|
@@ -446,6 +446,7 @@ export const arrayToMapWithIndex = (elements) => elements.reduce((acc, element,
|
|
|
446
446
|
return acc;
|
|
447
447
|
}, new Map());
|
|
448
448
|
export const isTestEnv = () => import.meta.env.MODE === "test";
|
|
449
|
+
export const isDevEnv = () => import.meta.env.MODE === "development";
|
|
449
450
|
export const wrapEvent = (name, nativeEvent) => {
|
|
450
451
|
return new CustomEvent(name, {
|
|
451
452
|
detail: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ExcalidrawElement } from "./element/types";
|
|
2
2
|
import { AppState } from "./types";
|
|
3
|
-
export declare const moveOneLeft: (allElements: readonly ExcalidrawElement[], appState: AppState) => ExcalidrawElement[];
|
|
4
|
-
export declare const moveOneRight: (allElements: readonly ExcalidrawElement[], appState: AppState) => ExcalidrawElement[];
|
|
3
|
+
export declare const moveOneLeft: (allElements: readonly ExcalidrawElement[], appState: AppState) => readonly ExcalidrawElement[];
|
|
4
|
+
export declare const moveOneRight: (allElements: readonly ExcalidrawElement[], appState: AppState) => readonly ExcalidrawElement[];
|
|
5
5
|
export declare const moveAllLeft: (allElements: readonly ExcalidrawElement[], appState: AppState) => readonly ExcalidrawElement[] | ExcalidrawElement[];
|
|
6
6
|
export declare const moveAllRight: (allElements: readonly ExcalidrawElement[], appState: AppState) => readonly ExcalidrawElement[] | ExcalidrawElement[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { bumpVersion } from "./element/mutateElement";
|
|
2
1
|
import { isFrameLikeElement } from "./element/typeChecks";
|
|
2
|
+
import { syncMovedIndices } from "./fractionalIndex";
|
|
3
3
|
import { getElementsInGroup } from "./groups";
|
|
4
4
|
import { getSelectedElements } from "./scene";
|
|
5
5
|
import Scene from "./scene/Scene";
|
|
@@ -173,9 +173,9 @@ containingFrame) => {
|
|
|
173
173
|
const getTargetElementsMap = (elements, indices) => {
|
|
174
174
|
return indices.reduce((acc, index) => {
|
|
175
175
|
const element = elements[index];
|
|
176
|
-
acc
|
|
176
|
+
acc.set(element.id, element);
|
|
177
177
|
return acc;
|
|
178
|
-
},
|
|
178
|
+
}, new Map());
|
|
179
179
|
};
|
|
180
180
|
const shiftElementsByOne = (elements, appState, direction) => {
|
|
181
181
|
const indicesToMove = getIndicesToMove(elements, appState);
|
|
@@ -226,12 +226,8 @@ const shiftElementsByOne = (elements, appState, direction) => {
|
|
|
226
226
|
...trailingElements,
|
|
227
227
|
];
|
|
228
228
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return bumpVersion(element);
|
|
232
|
-
}
|
|
233
|
-
return element;
|
|
234
|
-
});
|
|
229
|
+
syncMovedIndices(elements, targetElementsMap);
|
|
230
|
+
return elements;
|
|
235
231
|
};
|
|
236
232
|
const shiftElementsToEnd = (elements, appState, direction, containingFrame, elementsToBeMoved) => {
|
|
237
233
|
const indicesToMove = getIndicesToMove(elements, appState, elementsToBeMoved);
|
|
@@ -279,12 +275,10 @@ const shiftElementsToEnd = (elements, appState, direction, containingFrame, elem
|
|
|
279
275
|
displacedElements.push(elements[index]);
|
|
280
276
|
}
|
|
281
277
|
}
|
|
282
|
-
const targetElements =
|
|
283
|
-
return bumpVersion(element);
|
|
284
|
-
});
|
|
278
|
+
const targetElements = Array.from(targetElementsMap.values());
|
|
285
279
|
const leadingElements = elements.slice(0, leadingIndex);
|
|
286
280
|
const trailingElements = elements.slice(trailingIndex + 1);
|
|
287
|
-
|
|
281
|
+
const nextElements = direction === "left"
|
|
288
282
|
? [
|
|
289
283
|
...leadingElements,
|
|
290
284
|
...targetElements,
|
|
@@ -297,6 +291,8 @@ const shiftElementsToEnd = (elements, appState, direction, containingFrame, elem
|
|
|
297
291
|
...targetElements,
|
|
298
292
|
...trailingElements,
|
|
299
293
|
];
|
|
294
|
+
syncMovedIndices(nextElements, targetElementsMap);
|
|
295
|
+
return nextElements;
|
|
300
296
|
};
|
|
301
297
|
function shiftElementsAccountingForFrames(allElements, appState, direction, shiftFunction) {
|
|
302
298
|
const elementsToMove = arrayToMap(getSelectedElements(allElements, appState, {
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"showStroke": "Show stroke color picker",
|
|
111
111
|
"showBackground": "Show background color picker",
|
|
112
112
|
"toggleTheme": "Toggle light/dark theme",
|
|
113
|
+
"theme": "Theme",
|
|
113
114
|
"personalLib": "Personal Library",
|
|
114
115
|
"excalidrawLib": "Excalidraw Library",
|
|
115
116
|
"decreaseFontSize": "Decrease font size",
|
|
@@ -180,6 +181,7 @@
|
|
|
180
181
|
"fullScreen": "Full screen",
|
|
181
182
|
"darkMode": "Dark mode",
|
|
182
183
|
"lightMode": "Light mode",
|
|
184
|
+
"systemMode": "System mode",
|
|
183
185
|
"zenMode": "Zen mode",
|
|
184
186
|
"objectsSnapMode": "Snap to objects",
|
|
185
187
|
"exitZenMode": "Exit zen mode",
|