@1agh/maude 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/apps/studio/acp/bridge.ts +285 -0
- package/apps/studio/acp/env.ts +48 -0
- package/apps/studio/acp/index.ts +132 -0
- package/apps/studio/acp/probe.ts +112 -0
- package/apps/studio/acp/transcript.ts +149 -0
- package/apps/studio/ai-banner.tsx +0 -1
- package/apps/studio/annotations-align.ts +149 -0
- package/apps/studio/annotations-bindings.ts +197 -0
- package/apps/studio/annotations-context-toolbar.tsx +436 -186
- package/apps/studio/annotations-groups.ts +270 -0
- package/apps/studio/annotations-layer.tsx +1714 -1661
- package/apps/studio/annotations-model.ts +2077 -0
- package/apps/studio/annotations-snap.ts +125 -0
- package/apps/studio/api.ts +433 -182
- package/apps/studio/bin/_png-playwright.mjs +1 -1
- package/apps/studio/bin/annotate.mjs +732 -0
- package/apps/studio/bin/annotate.sh +17 -0
- package/apps/studio/bin/chat-open.sh +44 -0
- package/apps/studio/bin/read-annotations.mjs +152 -17
- package/apps/studio/build.ts +1 -1
- package/apps/studio/canvas-arrowheads.ts +78 -9
- package/apps/studio/canvas-cursors.ts +2 -0
- package/apps/studio/canvas-edit.ts +257 -7
- package/apps/studio/canvas-icons.tsx +105 -0
- package/apps/studio/canvas-lib.tsx +112 -19
- package/apps/studio/canvas-list-watch.ts +177 -0
- package/apps/studio/canvas-shell.tsx +326 -9
- package/apps/studio/client/app.jsx +3579 -250
- package/apps/studio/client/canvas-url.js +5 -0
- package/apps/studio/client/github.js +99 -0
- package/apps/studio/client/index.html +1 -1
- package/apps/studio/client/panels/ChatPanel.jsx +770 -0
- package/apps/studio/client/panels/CollabModelInfographic.jsx +71 -0
- package/apps/studio/client/panels/CreateProject.jsx +334 -0
- package/apps/studio/client/panels/DiffView.jsx +590 -0
- package/apps/studio/client/panels/GitPanel.jsx +767 -0
- package/apps/studio/client/panels/IdentityBar.jsx +294 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +563 -0
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +349 -0
- package/apps/studio/client/panels/acp-runtime.js +286 -0
- package/apps/studio/client/panels/chat-markdown.jsx +138 -0
- package/apps/studio/client/panels/git-grouping.js +86 -0
- package/apps/studio/client/styles/0-reset.css +4 -0
- package/apps/studio/client/styles/3-shell-maude.css +1326 -14
- package/apps/studio/client/styles/4-components.css +39 -0
- package/apps/studio/client/styles/5-maude-overrides.css +41 -2
- package/apps/studio/client/styles/6-acp-chat.css +771 -0
- package/apps/studio/client/styles/_index.css +2 -0
- package/apps/studio/client/tour/collab-tour.js +61 -0
- package/apps/studio/client/tour/overlay.jsx +116 -18
- package/apps/studio/client/tour/usage-tour.js +23 -10
- package/apps/studio/client/whats-new.jsx +25 -10
- package/apps/studio/collab/registry.ts +13 -0
- package/apps/studio/collab/room.ts +36 -0
- package/apps/studio/commands/annotation-strokes-command.ts +1 -1
- package/apps/studio/commands/move-artboards-command.ts +1 -1
- package/apps/studio/comments-overlay.tsx +7 -5
- package/apps/studio/context-menu.tsx +4 -3
- package/apps/studio/contextual-toolbar.tsx +14 -0
- package/apps/studio/cursors-overlay.tsx +37 -21
- package/apps/studio/dist/client.bundle.js +31795 -1829
- package/apps/studio/dist/comment-mount.js +97 -9
- package/apps/studio/dist/styles.css +7938 -1599
- package/apps/studio/dom-selection.ts +115 -0
- package/apps/studio/export-dialog.tsx +3 -3
- package/apps/studio/git/endpoints.ts +338 -0
- package/apps/studio/git/service.ts +1334 -0
- package/apps/studio/git/watch.ts +97 -0
- package/apps/studio/github/endpoints.ts +358 -0
- package/apps/studio/github/service.ts +231 -0
- package/apps/studio/github/token.ts +53 -0
- package/apps/studio/history.ts +20 -3
- package/apps/studio/hmr-broadcast.ts +9 -2
- package/apps/studio/http.ts +508 -6
- package/apps/studio/input-router.tsx +7 -0
- package/apps/studio/inspect.ts +37 -8
- package/apps/studio/participants-chrome.tsx +70 -16
- package/apps/studio/paths.ts +12 -0
- package/apps/studio/scaffold-design.ts +57 -0
- package/apps/studio/server.ts +70 -4
- package/apps/studio/sync/agent.ts +311 -57
- package/apps/studio/sync/codec.ts +69 -0
- package/apps/studio/sync/cold-start.ts +198 -0
- package/apps/studio/sync/connection-state.ts +58 -2
- package/apps/studio/sync/hub-link.ts +137 -0
- package/apps/studio/sync/index.ts +563 -221
- package/apps/studio/sync/journal.ts +190 -0
- package/apps/studio/sync/migrate-seed.ts +202 -44
- package/apps/studio/sync/projection.ts +22 -3
- package/apps/studio/sync/status.ts +15 -4
- package/apps/studio/test/acp-bridge.test.ts +127 -0
- package/apps/studio/test/acp-env.test.ts +65 -0
- package/apps/studio/test/acp-origin-gate.test.ts +78 -0
- package/apps/studio/test/acp-transcript.test.ts +112 -0
- package/apps/studio/test/activity.test.ts +1 -7
- package/apps/studio/test/annotate-write.test.ts +184 -0
- package/apps/studio/test/annotations-align.test.ts +88 -0
- package/apps/studio/test/annotations-bindings.test.ts +124 -0
- package/apps/studio/test/annotations-groups.test.ts +231 -0
- package/apps/studio/test/annotations-snap.test.ts +79 -0
- package/apps/studio/test/canvas-create-api.test.ts +72 -0
- package/apps/studio/test/canvas-edit.test.ts +181 -1
- package/apps/studio/test/canvas-list-watch.test.ts +322 -0
- package/apps/studio/test/canvas-meta-api.test.ts +161 -27
- package/apps/studio/test/canvas-origin-gate.test.ts +43 -0
- package/apps/studio/test/chat-markdown.test.tsx +58 -0
- package/apps/studio/test/collab-bridge.test.ts +0 -2
- package/apps/studio/test/collab-room.test.ts +2 -7
- package/apps/studio/test/collab-session-survival.test.tsx +176 -0
- package/apps/studio/test/csp-canvas-shell.test.ts +5 -0
- package/apps/studio/test/csrf-write-guard.test.ts +78 -0
- package/apps/studio/test/editing-presence.test.ts +103 -0
- package/apps/studio/test/figjam-v3-model.test.ts +342 -0
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +1 -0
- package/apps/studio/test/fixtures/mock-acp-agent.mjs +45 -0
- package/apps/studio/test/git-api.test.ts +0 -0
- package/apps/studio/test/git-branches.test.ts +106 -0
- package/apps/studio/test/git-grouping.test.ts +106 -0
- package/apps/studio/test/git-watch.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +465 -0
- package/apps/studio/test/hub-link.test.ts +69 -0
- package/apps/studio/test/participants-chrome.test.ts +36 -1
- package/apps/studio/test/shared-doc-migrate.test.ts +224 -14
- package/apps/studio/test/sync-agent.test.ts +272 -2
- package/apps/studio/test/sync-codec.test.ts +65 -0
- package/apps/studio/test/sync-cold-seed-dedup.test.ts +187 -0
- package/apps/studio/test/sync-cold-start.test.ts +304 -0
- package/apps/studio/test/sync-connection-state.test.ts +68 -0
- package/apps/studio/test/sync-hardening.test.ts +0 -8
- package/apps/studio/test/sync-hubs-config.test.ts +0 -1
- package/apps/studio/test/sync-incident-replay.test.ts +211 -0
- package/apps/studio/test/sync-journal.test.ts +176 -0
- package/apps/studio/test/sync-runtime.test.ts +360 -5
- package/apps/studio/test/sync-status.test.ts +66 -0
- package/apps/studio/test/tour-overlay.test.tsx +18 -0
- package/apps/studio/test/use-annotation-resize.test.ts +159 -0
- package/apps/studio/test/use-artboard-drag.test.ts +0 -1
- package/apps/studio/test/use-collab.test.ts +0 -0
- package/apps/studio/test/use-tool-mode.test.tsx +5 -2
- package/apps/studio/tool-palette.tsx +30 -11
- package/apps/studio/undo-hud.tsx +0 -1
- package/apps/studio/use-agent-presence.tsx +4 -2
- package/apps/studio/use-annotation-resize.tsx +484 -54
- package/apps/studio/use-annotation-selection.tsx +16 -4
- package/apps/studio/use-canvas-activity.tsx +2 -2
- package/apps/studio/use-chrome-visibility.tsx +66 -0
- package/apps/studio/use-collab.tsx +434 -202
- package/apps/studio/use-selection-set.tsx +9 -0
- package/apps/studio/use-tool-mode.tsx +5 -1
- package/apps/studio/whats-new.json +208 -10
- package/apps/studio/whats-new.schema.json +18 -2
- package/apps/studio/ws.ts +44 -1
- package/cli/commands/design-link.test.mjs +84 -0
- package/cli/commands/design.mjs +3 -1
- package/cli/commands/design.test.mjs +5 -1
- package/cli/lib/design-link.mjs +51 -1
- package/cli/lib/gitignore-block.mjs +16 -3
- package/cli/lib/gitignore-block.test.mjs +13 -1
- package/package.json +11 -9
- package/plugins/design/dependencies.json +17 -0
- package/plugins/design/templates/_shell.html +58 -12
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file annotations-groups.ts — FigJam v3 grouping + duplicate + z-order
|
|
3
|
+
* @scope apps/studio/annotations-groups.ts
|
|
4
|
+
* @purpose Pure helpers over the Stroke model (no React, no DOM) for the
|
|
5
|
+
* group/ungroup, duplicate (Cmd+D / Alt+drag / paste), and z-order
|
|
6
|
+
* manipulations. Group model = Excalidraw-style flat tag-array:
|
|
7
|
+
* every stroke carries `groupIds` ordered DEEPEST → SHALLOWEST and
|
|
8
|
+
* the OUTERMOST group id is the LAST element. There is no group
|
|
9
|
+
* node — selection logic expands a clicked stroke to everything
|
|
10
|
+
* sharing its outermost group id. tldraw's lifecycle rules are
|
|
11
|
+
* copied as `normalizeGroups` (empty group self-deletes,
|
|
12
|
+
* single-member group auto-dissolves).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { gid, rid, type Stroke, translateOne } from './annotations-model.ts';
|
|
16
|
+
|
|
17
|
+
/** The stroke's outermost (shallowest) group id, or null when ungrouped. */
|
|
18
|
+
export function outermostGroupOf(s: Stroke): string | null {
|
|
19
|
+
const ids = s.groupIds;
|
|
20
|
+
if (!ids || ids.length === 0) return null;
|
|
21
|
+
return ids[ids.length - 1] ?? null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Shallow-copy a stroke with its `groupIds` key dropped entirely (so an
|
|
25
|
+
* ungrouped stroke round-trips byte-identically — no `groupIds: []`). */
|
|
26
|
+
function stripGroupIds(s: Stroke): Stroke {
|
|
27
|
+
const { groupIds: _drop, ...rest } = s;
|
|
28
|
+
return rest as Stroke;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Selection-expansion (FigJam: clicking a group member selects the whole
|
|
33
|
+
* group). Expands every id whose stroke belongs to a group to ALL strokes
|
|
34
|
+
* sharing that outermost group id. Result is in document (z) order, deduped.
|
|
35
|
+
*/
|
|
36
|
+
export function expandIdsToGroups(ids: readonly string[], strokes: readonly Stroke[]): string[] {
|
|
37
|
+
const want = new Set(ids);
|
|
38
|
+
const groups = new Set<string>();
|
|
39
|
+
for (const s of strokes) {
|
|
40
|
+
if (!want.has(s.id)) continue;
|
|
41
|
+
const g = outermostGroupOf(s);
|
|
42
|
+
if (g) groups.add(g);
|
|
43
|
+
}
|
|
44
|
+
const out: string[] = [];
|
|
45
|
+
for (const s of strokes) {
|
|
46
|
+
const g = outermostGroupOf(s);
|
|
47
|
+
if (want.has(s.id) || (g != null && groups.has(g))) out.push(s.id);
|
|
48
|
+
}
|
|
49
|
+
// Preserve ids that don't resolve to a live stroke (defensive — a stale
|
|
50
|
+
// selection entry must not silently disappear from the caller's set).
|
|
51
|
+
for (const id of ids) if (!out.includes(id)) out.push(id);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface GroupResult {
|
|
56
|
+
strokes: Stroke[];
|
|
57
|
+
groupId: string;
|
|
58
|
+
/** Document-order ids of every member of the new group. */
|
|
59
|
+
memberIds: string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Group the (expanded) selection under a fresh outermost group id. Members are
|
|
64
|
+
* reordered CONTIGUOUS — the block lands where the topmost member sat, so the
|
|
65
|
+
* group's z-position matches FigJam (group adopts the front-most member's
|
|
66
|
+
* layer; relative order inside is preserved). Returns null when fewer than two
|
|
67
|
+
* members resolve (nothing to group).
|
|
68
|
+
*/
|
|
69
|
+
export function groupStrokes(
|
|
70
|
+
strokes: readonly Stroke[],
|
|
71
|
+
ids: readonly string[],
|
|
72
|
+
newId: string = gid()
|
|
73
|
+
): GroupResult | null {
|
|
74
|
+
const memberIds = new Set(
|
|
75
|
+
expandIdsToGroups(ids, strokes).filter((id) => strokes.some((s) => s.id === id))
|
|
76
|
+
);
|
|
77
|
+
if (memberIds.size < 2) return null;
|
|
78
|
+
const members: Stroke[] = [];
|
|
79
|
+
const rest: Stroke[] = [];
|
|
80
|
+
let lastMemberIdx = -1;
|
|
81
|
+
strokes.forEach((s, i) => {
|
|
82
|
+
if (memberIds.has(s.id)) {
|
|
83
|
+
members.push({ ...s, groupIds: [...(s.groupIds ?? []), newId] });
|
|
84
|
+
lastMemberIdx = i;
|
|
85
|
+
} else {
|
|
86
|
+
rest.push(s);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// Insert the contiguous block after every non-member that sat below the
|
|
90
|
+
// topmost member — the group keeps the front-most member's z position.
|
|
91
|
+
let insertAt = 0;
|
|
92
|
+
for (let i = 0; i < lastMemberIdx; i++) {
|
|
93
|
+
const s = strokes[i];
|
|
94
|
+
if (s && !memberIds.has(s.id)) insertAt++;
|
|
95
|
+
}
|
|
96
|
+
const out = [...rest.slice(0, insertAt), ...members, ...rest.slice(insertAt)];
|
|
97
|
+
return { strokes: out, groupId: newId, memberIds: members.map((m) => m.id) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Dissolve the outermost group of every selected stroke (Cmd+Shift+G). Nested
|
|
102
|
+
* groups surface one level per invocation — FigJam/tldraw semantics.
|
|
103
|
+
*/
|
|
104
|
+
export function ungroupStrokes(strokes: readonly Stroke[], ids: readonly string[]): Stroke[] {
|
|
105
|
+
const sel = new Set(ids);
|
|
106
|
+
const dissolve = new Set<string>();
|
|
107
|
+
for (const s of strokes) {
|
|
108
|
+
if (!sel.has(s.id)) continue;
|
|
109
|
+
const g = outermostGroupOf(s);
|
|
110
|
+
if (g) dissolve.add(g);
|
|
111
|
+
}
|
|
112
|
+
if (dissolve.size === 0) return [...strokes];
|
|
113
|
+
return strokes.map((s) => {
|
|
114
|
+
const g = outermostGroupOf(s);
|
|
115
|
+
if (!g || !dissolve.has(g)) return s;
|
|
116
|
+
const remaining = (s.groupIds ?? []).slice(0, -1);
|
|
117
|
+
return remaining.length ? { ...s, groupIds: remaining } : stripGroupIds(s);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* tldraw lifecycle rules — drop every group id with fewer than two members
|
|
123
|
+
* (covers both the empty group after a delete and the singleton left behind by
|
|
124
|
+
* a partial erase). Run after any deletion. Returns the input array when
|
|
125
|
+
* nothing changed (referential no-op).
|
|
126
|
+
*/
|
|
127
|
+
export function normalizeGroups(strokes: readonly Stroke[]): Stroke[] {
|
|
128
|
+
const counts = new Map<string, number>();
|
|
129
|
+
for (const s of strokes) {
|
|
130
|
+
for (const g of s.groupIds ?? []) counts.set(g, (counts.get(g) ?? 0) + 1);
|
|
131
|
+
}
|
|
132
|
+
const dead = new Set<string>();
|
|
133
|
+
for (const [g, n] of counts) if (n < 2) dead.add(g);
|
|
134
|
+
if (dead.size === 0) return strokes as Stroke[];
|
|
135
|
+
return strokes.map((s) => {
|
|
136
|
+
if (!s.groupIds?.some((g) => dead.has(g))) return s;
|
|
137
|
+
const keep = s.groupIds.filter((g) => !dead.has(g));
|
|
138
|
+
return keep.length ? { ...s, groupIds: keep } : stripGroupIds(s);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface DuplicateResult {
|
|
143
|
+
strokes: Stroke[];
|
|
144
|
+
/** Ids of the selectable clones (anchored-text clones excluded — they're
|
|
145
|
+
* selected through their host, FigJam semantics). */
|
|
146
|
+
newIds: string[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Clone the (expanded) selection with fresh ids, remapping every internal
|
|
151
|
+
* reference: `groupIds` (each old group → one fresh id, so the copy is its own
|
|
152
|
+
* group), anchored-text `anchorId`, and arrow binds between co-duplicated
|
|
153
|
+
* strokes. A bind to a NON-duplicated host is kept pointing at the original
|
|
154
|
+
* (Excalidraw/FigJam: duplicating a bound arrow keeps the attachment). Clones
|
|
155
|
+
* append at the end of the array (top of z) offset by (dx, dy).
|
|
156
|
+
*/
|
|
157
|
+
export function duplicateStrokes(
|
|
158
|
+
strokes: readonly Stroke[],
|
|
159
|
+
ids: readonly string[],
|
|
160
|
+
dx: number,
|
|
161
|
+
dy: number,
|
|
162
|
+
makeId: () => string = rid,
|
|
163
|
+
makeGroupId: () => string = gid
|
|
164
|
+
): DuplicateResult {
|
|
165
|
+
const dupSet = new Set(expandIdsToGroups(ids, strokes));
|
|
166
|
+
// Anchored text travels with its host even though it's never independently
|
|
167
|
+
// selected — clone it alongside a duplicated host.
|
|
168
|
+
for (const s of strokes) {
|
|
169
|
+
if (s.tool === 'text' && s.anchorId && dupSet.has(s.anchorId)) dupSet.add(s.id);
|
|
170
|
+
}
|
|
171
|
+
const idMap = new Map<string, string>();
|
|
172
|
+
const groupMap = new Map<string, string>();
|
|
173
|
+
const clones: Stroke[] = [];
|
|
174
|
+
for (const s of strokes) {
|
|
175
|
+
if (!dupSet.has(s.id)) continue;
|
|
176
|
+
const nid = makeId();
|
|
177
|
+
idMap.set(s.id, nid);
|
|
178
|
+
const moved = translateOne(structuredClone(s) as Stroke, dx, dy);
|
|
179
|
+
const clone: Stroke = { ...moved, id: nid };
|
|
180
|
+
if (clone.groupIds) {
|
|
181
|
+
clone.groupIds = clone.groupIds.map((g) => {
|
|
182
|
+
let mapped = groupMap.get(g);
|
|
183
|
+
if (!mapped) {
|
|
184
|
+
mapped = makeGroupId();
|
|
185
|
+
groupMap.set(g, mapped);
|
|
186
|
+
}
|
|
187
|
+
return mapped;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
clones.push(clone);
|
|
191
|
+
}
|
|
192
|
+
for (const c of clones) {
|
|
193
|
+
if (c.tool === 'text' && c.anchorId) {
|
|
194
|
+
const mapped = idMap.get(c.anchorId);
|
|
195
|
+
if (mapped) c.anchorId = mapped;
|
|
196
|
+
}
|
|
197
|
+
if (c.tool === 'arrow') {
|
|
198
|
+
if (c.startBind) {
|
|
199
|
+
const mapped = idMap.get(c.startBind.hostId);
|
|
200
|
+
if (mapped) c.startBind = { ...c.startBind, hostId: mapped };
|
|
201
|
+
}
|
|
202
|
+
if (c.endBind) {
|
|
203
|
+
const mapped = idMap.get(c.endBind.hostId);
|
|
204
|
+
if (mapped) c.endBind = { ...c.endBind, hostId: mapped };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
strokes: [...strokes, ...clones],
|
|
210
|
+
newIds: clones.filter((c) => !(c.tool === 'text' && c.anchorId)).map((c) => c.id),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export type ZOrderOp = 'front' | 'back' | 'forward' | 'backward';
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Z-order = array order (render order; no fractional index needed under the
|
|
218
|
+
* whole-SVG LWW sync). Units move together: a maximal run of strokes sharing
|
|
219
|
+
* one outermost group id is one unit, an ungrouped stroke is its own unit —
|
|
220
|
+
* so `]` on a group hops the whole NEIGHBOURING group, never interleaves.
|
|
221
|
+
*/
|
|
222
|
+
export function reorderStrokes(
|
|
223
|
+
strokes: readonly Stroke[],
|
|
224
|
+
ids: readonly string[],
|
|
225
|
+
op: ZOrderOp
|
|
226
|
+
): Stroke[] {
|
|
227
|
+
const sel = new Set(expandIdsToGroups(ids, strokes));
|
|
228
|
+
if (sel.size === 0) return [...strokes];
|
|
229
|
+
const units: Stroke[][] = [];
|
|
230
|
+
for (const s of strokes) {
|
|
231
|
+
const g = outermostGroupOf(s);
|
|
232
|
+
const last = units[units.length - 1];
|
|
233
|
+
const lastFirst = last?.[0];
|
|
234
|
+
if (last && lastFirst && g != null && outermostGroupOf(lastFirst) === g) last.push(s);
|
|
235
|
+
else units.push([s]);
|
|
236
|
+
}
|
|
237
|
+
const isSel = (u: Stroke[]): boolean => u.some((s) => sel.has(s.id));
|
|
238
|
+
if (op === 'front') {
|
|
239
|
+
const rest = units.filter((u) => !isSel(u));
|
|
240
|
+
const selUnits = units.filter(isSel);
|
|
241
|
+
return [...rest, ...selUnits].flat();
|
|
242
|
+
}
|
|
243
|
+
if (op === 'back') {
|
|
244
|
+
const rest = units.filter((u) => !isSel(u));
|
|
245
|
+
const selUnits = units.filter(isSel);
|
|
246
|
+
return [...selUnits, ...rest].flat();
|
|
247
|
+
}
|
|
248
|
+
const arr = [...units];
|
|
249
|
+
if (op === 'forward') {
|
|
250
|
+
for (let i = arr.length - 2; i >= 0; i--) {
|
|
251
|
+
const cur = arr[i];
|
|
252
|
+
const above = arr[i + 1];
|
|
253
|
+
if (cur && above && isSel(cur) && !isSel(above)) {
|
|
254
|
+
arr[i] = above;
|
|
255
|
+
arr[i + 1] = cur;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
// backward
|
|
260
|
+
for (let i = 1; i < arr.length; i++) {
|
|
261
|
+
const cur = arr[i];
|
|
262
|
+
const below = arr[i - 1];
|
|
263
|
+
if (cur && below && isSel(cur) && !isSel(below)) {
|
|
264
|
+
arr[i] = below;
|
|
265
|
+
arr[i - 1] = cur;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return arr.flat();
|
|
270
|
+
}
|