gitmaps 1.1.0 → 1.1.1
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 +267 -118
- package/app/[...slug]/page.client.tsx +1 -0
- package/app/[...slug]/page.tsx +6 -0
- package/app/analytics.db +0 -0
- package/app/api/analytics/route.ts +64 -0
- package/app/api/auth/positions/route.ts +95 -33
- package/app/api/build-info/route.ts +19 -0
- package/app/api/chat/route.ts +13 -2
- package/app/api/og-image/route.ts +14 -0
- package/app/api/repo/file-content/route.ts +73 -20
- package/app/api/repo/load/route.test.ts +62 -0
- package/app/api/repo/load/route.ts +41 -1
- package/app/api/repo/pdf-thumb/route.ts +127 -0
- package/app/api/repo/resolve-slug/route.ts +51 -0
- package/app/api/repo/tree/route.ts +188 -104
- package/app/api/version/route.ts +26 -0
- package/app/globals.css +5706 -4938
- package/app/layout.tsx +1279 -490
- package/app/lib/auto-arrange.test.ts +158 -0
- package/app/lib/auto-arrange.ts +147 -0
- package/app/lib/canvas-export.ts +358 -358
- package/app/lib/canvas.ts +625 -564
- package/app/lib/cards.tsx +1361 -916
- package/app/lib/chat.tsx +65 -9
- package/app/lib/code-editor.ts +86 -2
- package/app/lib/context.test.ts +32 -0
- package/app/lib/context.ts +19 -3
- package/app/lib/cursor-sharing.ts +34 -0
- package/app/lib/events.tsx +71 -93
- package/app/lib/export-canvas.ts +287 -0
- package/app/lib/file-card-plugin.ts +148 -148
- package/app/lib/file-modal.tsx +49 -0
- package/app/lib/file-preview.ts +486 -427
- package/app/lib/github-import.test.ts +424 -0
- package/app/lib/initial-route-hydration.test.ts +283 -0
- package/app/lib/initial-route-hydration.ts +202 -0
- package/app/lib/landing-reset.test.ts +99 -0
- package/app/lib/landing-reset.ts +106 -0
- package/app/lib/landing-shell.test.ts +75 -0
- package/app/lib/large-repo-optimization.ts +37 -0
- package/app/lib/layout-snapshots.ts +320 -0
- package/app/lib/loading.test.ts +69 -0
- package/app/lib/loading.tsx +160 -45
- package/app/lib/mount-cleanup.test.ts +52 -0
- package/app/lib/mount-cleanup.ts +34 -0
- package/app/lib/mount-init.test.ts +123 -0
- package/app/lib/mount-init.ts +107 -0
- package/app/lib/mount-lifecycle.test.ts +39 -0
- package/app/lib/mount-lifecycle.ts +12 -0
- package/app/lib/mount-route-wiring.test.ts +87 -0
- package/app/lib/mount-route-wiring.ts +84 -0
- package/app/lib/multi-repo.ts +14 -0
- package/app/lib/onboarding-tutorial.ts +278 -0
- package/app/lib/positions.ts +190 -121
- package/app/lib/recent-commits.test.ts +869 -0
- package/app/lib/recent-commits.ts +227 -0
- package/app/lib/repo-handoff.test.ts +23 -0
- package/app/lib/repo-handoff.ts +16 -0
- package/app/lib/repo-progressive.ts +119 -0
- package/app/lib/repo-select.test.ts +61 -0
- package/app/lib/repo-select.ts +74 -0
- package/app/lib/repo.tsx +1383 -987
- package/app/lib/role.ts +228 -0
- package/app/lib/route-catchall.test.ts +27 -0
- package/app/lib/route-repo-entry.test.ts +95 -0
- package/app/lib/route-repo-entry.ts +36 -0
- package/app/lib/router-contract.test.ts +22 -0
- package/app/lib/router-contract.ts +19 -0
- package/app/lib/shared-layout.test.ts +86 -0
- package/app/lib/shared-layout.ts +82 -0
- package/app/lib/status-bar.test.ts +118 -0
- package/app/lib/status-bar.ts +365 -128
- package/app/lib/sync-controls.test.ts +43 -0
- package/app/lib/sync-controls.tsx +303 -0
- package/app/lib/test-dom.ts +145 -0
- package/app/lib/test-fixtures/router-contract/[...slug]/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/api/health/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/api/version/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/galaxy-canvas/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/page.tsx +3 -0
- package/app/lib/transclusion-smoke.test.ts +163 -0
- package/app/lib/tutorial.ts +301 -0
- package/app/lib/version.ts +93 -0
- package/app/lib/viewport-culling.ts +740 -735
- package/app/lib/virtual-files.ts +456 -0
- package/app/lib/webgl-text.ts +189 -0
- package/app/lib/{galaxydraw-bridge.ts → xydraw-bridge.ts} +485 -482
- package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
- package/app/og-image.png +0 -0
- package/app/page.client.tsx +70 -269
- package/app/page.tsx +15 -16
- package/app/state/machine.js +13 -0
- package/package.json +16 -7
- package/server.ts +10 -0
- package/app/[owner]/[repo]/page.tsx +0 -6
- package/app/[slug]/page.tsx +0 -6
- package/packages/galaxydraw/README.md +0 -296
- package/packages/galaxydraw/banner.png +0 -0
- package/packages/galaxydraw/demo/build-static.ts +0 -100
- package/packages/galaxydraw/demo/client.ts +0 -154
- package/packages/galaxydraw/demo/dist/client.js +0 -8
- package/packages/galaxydraw/demo/index.html +0 -256
- package/packages/galaxydraw/demo/server.ts +0 -96
- package/packages/galaxydraw/dist/index.js +0 -984
- package/packages/galaxydraw/dist/index.js.map +0 -16
- package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
- package/packages/galaxydraw/package.json +0 -49
- package/packages/galaxydraw/perf.test.ts +0 -284
- package/packages/galaxydraw/src/core/cards.ts +0 -435
- package/packages/galaxydraw/src/core/engine.ts +0 -339
- package/packages/galaxydraw/src/core/events.ts +0 -81
- package/packages/galaxydraw/src/core/layout.ts +0 -136
- package/packages/galaxydraw/src/core/minimap.ts +0 -216
- package/packages/galaxydraw/src/core/state.ts +0 -177
- package/packages/galaxydraw/src/core/viewport.ts +0 -106
- package/packages/galaxydraw/src/galaxydraw.css +0 -166
- package/packages/galaxydraw/src/index.ts +0 -40
- package/packages/galaxydraw/tsconfig.json +0 -30
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CardManager — Manages cards (containers/widgets) on the canvas
|
|
3
|
-
*
|
|
4
|
-
* Handles:
|
|
5
|
-
* - Card creation with custom content (via plugins)
|
|
6
|
-
* - Drag, resize, z-order
|
|
7
|
-
* - Selection (single, multi, rect-select)
|
|
8
|
-
* - Collapse/expand
|
|
9
|
-
* - Virtualized deferred rendering
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { CanvasState, ViewportRect } from './state';
|
|
13
|
-
import type { EventBus } from './events';
|
|
14
|
-
|
|
15
|
-
// ─── Types ───────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
export interface CardData {
|
|
18
|
-
id: string;
|
|
19
|
-
x: number;
|
|
20
|
-
y: number;
|
|
21
|
-
width: number;
|
|
22
|
-
height: number;
|
|
23
|
-
collapsed?: boolean;
|
|
24
|
-
/** Arbitrary data attached by the consumer */
|
|
25
|
-
meta?: Record<string, any>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface CardOptions {
|
|
29
|
-
/** Default card width */
|
|
30
|
-
defaultWidth?: number;
|
|
31
|
-
/** Default card height */
|
|
32
|
-
defaultHeight?: number;
|
|
33
|
-
/** Minimum card width when resizing */
|
|
34
|
-
minWidth?: number;
|
|
35
|
-
/** Minimum card height when resizing */
|
|
36
|
-
minHeight?: number;
|
|
37
|
-
/** Grid snap size (0 = no snap) */
|
|
38
|
-
gridSize?: number;
|
|
39
|
-
/** Corner hit area for resize handle */
|
|
40
|
-
cornerSize?: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* CardPlugin — how consumers define custom card types.
|
|
45
|
-
*
|
|
46
|
-
* Example: GitMaps provides a FileCardPlugin that renders source code.
|
|
47
|
-
* WARMAPS provides a MapCardPlugin that renders MapLibre.
|
|
48
|
-
*/
|
|
49
|
-
export interface CardPlugin {
|
|
50
|
-
/** Unique type identifier */
|
|
51
|
-
type: string;
|
|
52
|
-
/** Create the DOM element for this card */
|
|
53
|
-
render(data: CardData): HTMLElement;
|
|
54
|
-
/** Called when the card is resized */
|
|
55
|
-
onResize?(card: HTMLElement, width: number, height: number): void;
|
|
56
|
-
/** Called when the card is destroyed */
|
|
57
|
-
onDestroy?(card: HTMLElement): void;
|
|
58
|
-
/** Should wheel events be passed through? (e.g. maps, scrollable content) */
|
|
59
|
-
consumesWheel?(target: HTMLElement): boolean;
|
|
60
|
-
/** Should mouse events be passed through? (e.g. maps, interactive widgets) */
|
|
61
|
-
consumesMouse?(target: HTMLElement): boolean;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const DEFAULT_OPTIONS: Required<CardOptions> = {
|
|
65
|
-
defaultWidth: 400,
|
|
66
|
-
defaultHeight: 300,
|
|
67
|
-
minWidth: 200,
|
|
68
|
-
minHeight: 150,
|
|
69
|
-
gridSize: 0,
|
|
70
|
-
cornerSize: 40,
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// ─── CardManager ─────────────────────────────────────────
|
|
74
|
-
|
|
75
|
-
export class CardManager {
|
|
76
|
-
/** Active DOM cards: id → element */
|
|
77
|
-
readonly cards = new Map<string, HTMLElement>();
|
|
78
|
-
/** Deferred cards (not yet in DOM): id → data */
|
|
79
|
-
readonly deferred = new Map<string, CardData & { plugin?: string }>();
|
|
80
|
-
/** Currently selected card IDs */
|
|
81
|
-
readonly selected = new Set<string>();
|
|
82
|
-
|
|
83
|
-
private topZ = 10;
|
|
84
|
-
private plugins = new Map<string, CardPlugin>();
|
|
85
|
-
private opts: Required<CardOptions>;
|
|
86
|
-
|
|
87
|
-
constructor(
|
|
88
|
-
private state: CanvasState,
|
|
89
|
-
private bus: EventBus,
|
|
90
|
-
private canvas: HTMLElement,
|
|
91
|
-
options?: CardOptions,
|
|
92
|
-
) {
|
|
93
|
-
this.opts = { ...DEFAULT_OPTIONS, ...options };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ─── Plugin registration ─────────────────────────────
|
|
97
|
-
|
|
98
|
-
registerPlugin(plugin: CardPlugin) {
|
|
99
|
-
this.plugins.set(plugin.type, plugin);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ─── Card lifecycle ──────────────────────────────────
|
|
103
|
-
|
|
104
|
-
/** Create a card and add it to the canvas */
|
|
105
|
-
create(type: string, data: Partial<CardData> & { id: string }): HTMLElement | null {
|
|
106
|
-
const plugin = this.plugins.get(type);
|
|
107
|
-
if (!plugin) {
|
|
108
|
-
console.warn(`[galaxydraw] No plugin registered for card type "${type}"`);
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const full: CardData = {
|
|
113
|
-
x: data.x ?? 0,
|
|
114
|
-
y: data.y ?? 0,
|
|
115
|
-
width: data.width ?? this.opts.defaultWidth,
|
|
116
|
-
height: data.height ?? this.opts.defaultHeight,
|
|
117
|
-
collapsed: data.collapsed ?? false,
|
|
118
|
-
meta: data.meta ?? {},
|
|
119
|
-
...data,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const el = plugin.render(full);
|
|
123
|
-
el.classList.add('gd-card');
|
|
124
|
-
el.dataset.cardId = full.id;
|
|
125
|
-
el.dataset.cardType = type;
|
|
126
|
-
el.style.left = `${full.x}px`;
|
|
127
|
-
el.style.top = `${full.y}px`;
|
|
128
|
-
el.style.width = `${full.width}px`;
|
|
129
|
-
if (!full.collapsed) {
|
|
130
|
-
el.style.height = `${full.height}px`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
this.canvas.appendChild(el);
|
|
134
|
-
this.cards.set(full.id, el);
|
|
135
|
-
this.bringToFront(el);
|
|
136
|
-
|
|
137
|
-
this.setupDrag(el);
|
|
138
|
-
this.setupResize(el, type);
|
|
139
|
-
|
|
140
|
-
this.bus.emit('card:create', { id: full.id, x: full.x, y: full.y });
|
|
141
|
-
return el;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** Remove a card from the canvas and clean up */
|
|
145
|
-
remove(id: string) {
|
|
146
|
-
const el = this.cards.get(id);
|
|
147
|
-
if (!el) {
|
|
148
|
-
this.deferred.delete(id);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const type = el.dataset.cardType;
|
|
153
|
-
if (type) {
|
|
154
|
-
this.plugins.get(type)?.onDestroy?.(el);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
el.remove();
|
|
158
|
-
this.cards.delete(id);
|
|
159
|
-
this.selected.delete(id);
|
|
160
|
-
this.bus.emit('card:remove', { id });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Defer a card (store data, don't create DOM until it enters viewport) */
|
|
164
|
-
defer(type: string, data: CardData) {
|
|
165
|
-
this.deferred.set(data.id, { ...data, plugin: type });
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** Materialize deferred cards that overlap the given world rect */
|
|
169
|
-
materializeInRect(worldRect: ViewportRect): number {
|
|
170
|
-
let count = 0;
|
|
171
|
-
const toRemove: string[] = [];
|
|
172
|
-
|
|
173
|
-
for (const [id, entry] of this.deferred) {
|
|
174
|
-
const { x, y, width, height, plugin } = entry as any;
|
|
175
|
-
const w = width || this.opts.defaultWidth;
|
|
176
|
-
const h = height || this.opts.defaultHeight;
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
x + w > worldRect.left &&
|
|
180
|
-
x < worldRect.right &&
|
|
181
|
-
y + h > worldRect.top &&
|
|
182
|
-
y < worldRect.bottom
|
|
183
|
-
) {
|
|
184
|
-
if (plugin) {
|
|
185
|
-
this.create(plugin, entry);
|
|
186
|
-
}
|
|
187
|
-
toRemove.push(id);
|
|
188
|
-
count++;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
for (const id of toRemove) {
|
|
193
|
-
this.deferred.delete(id);
|
|
194
|
-
}
|
|
195
|
-
return count;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** Remove all cards and deferred entries */
|
|
199
|
-
clear() {
|
|
200
|
-
for (const [id, el] of this.cards) {
|
|
201
|
-
const type = el.dataset.cardType;
|
|
202
|
-
if (type) this.plugins.get(type)?.onDestroy?.(el);
|
|
203
|
-
el.remove();
|
|
204
|
-
}
|
|
205
|
-
this.cards.clear();
|
|
206
|
-
this.deferred.clear();
|
|
207
|
-
this.selected.clear();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ─── Z-order ─────────────────────────────────────────
|
|
211
|
-
|
|
212
|
-
bringToFront(el: HTMLElement) {
|
|
213
|
-
this.topZ++;
|
|
214
|
-
el.style.zIndex = String(this.topZ);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// ─── Selection ───────────────────────────────────────
|
|
218
|
-
|
|
219
|
-
select(id: string, multi = false) {
|
|
220
|
-
if (!multi) {
|
|
221
|
-
this.deselectAll();
|
|
222
|
-
}
|
|
223
|
-
this.selected.add(id);
|
|
224
|
-
this.cards.get(id)?.classList.add('gd-card--selected');
|
|
225
|
-
this.bus.emit('card:select', { ids: [...this.selected] });
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
deselect(id: string) {
|
|
229
|
-
this.selected.delete(id);
|
|
230
|
-
this.cards.get(id)?.classList.remove('gd-card--selected');
|
|
231
|
-
this.bus.emit('card:deselect', { ids: [id] });
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
deselectAll() {
|
|
235
|
-
for (const id of this.selected) {
|
|
236
|
-
this.cards.get(id)?.classList.remove('gd-card--selected');
|
|
237
|
-
}
|
|
238
|
-
const prev = [...this.selected];
|
|
239
|
-
this.selected.clear();
|
|
240
|
-
if (prev.length > 0) {
|
|
241
|
-
this.bus.emit('card:deselect', { ids: prev });
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// ─── Collapse ────────────────────────────────────────
|
|
246
|
-
|
|
247
|
-
toggleCollapse(id: string) {
|
|
248
|
-
const el = this.cards.get(id);
|
|
249
|
-
if (!el) return;
|
|
250
|
-
const collapsed = el.classList.toggle('gd-card--collapsed');
|
|
251
|
-
this.bus.emit('card:collapse', { id, collapsed });
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// ─── Drag interaction ────────────────────────────────
|
|
255
|
-
|
|
256
|
-
private setupDrag(card: HTMLElement) {
|
|
257
|
-
let dragging = false;
|
|
258
|
-
let dragStarted = false;
|
|
259
|
-
let startScreenX = 0, startScreenY = 0;
|
|
260
|
-
let startWorldX = 0, startWorldY = 0;
|
|
261
|
-
let moveGroup: { el: HTMLElement; startX: number; startY: number }[] = [];
|
|
262
|
-
const DRAG_THRESHOLD = 5;
|
|
263
|
-
|
|
264
|
-
card.addEventListener('mousedown', (e: MouseEvent) => {
|
|
265
|
-
if (e.button !== 0) return;
|
|
266
|
-
// Don't start drag from interactive elements
|
|
267
|
-
if ((e.target as HTMLElement).closest('button, a, input, textarea, select, .connect-btn')) return;
|
|
268
|
-
|
|
269
|
-
// Don't prevent default yet — allow body scroll until threshold is exceeded
|
|
270
|
-
dragging = true;
|
|
271
|
-
dragStarted = false;
|
|
272
|
-
startScreenX = e.clientX;
|
|
273
|
-
startScreenY = e.clientY;
|
|
274
|
-
this.bringToFront(card);
|
|
275
|
-
|
|
276
|
-
const world = this.state.screenToWorld(e.clientX, e.clientY);
|
|
277
|
-
startWorldX = world.x;
|
|
278
|
-
startWorldY = world.y;
|
|
279
|
-
|
|
280
|
-
// Build move group: if this card is selected and there are other selected cards, move them all
|
|
281
|
-
const cardId = card.dataset.cardId || card.dataset.path || '';
|
|
282
|
-
moveGroup = [];
|
|
283
|
-
|
|
284
|
-
// Collect all selected card IDs from this.selected
|
|
285
|
-
const allSelectedIds = new Set<string>(this.selected);
|
|
286
|
-
|
|
287
|
-
if (allSelectedIds.has(cardId) && allSelectedIds.size > 1) {
|
|
288
|
-
// Multi-drag: move all selected cards
|
|
289
|
-
for (const selId of allSelectedIds) {
|
|
290
|
-
const el = this.cards.get(selId);
|
|
291
|
-
if (el) {
|
|
292
|
-
moveGroup.push({
|
|
293
|
-
el,
|
|
294
|
-
startX: parseFloat(el.style.left) || 0,
|
|
295
|
-
startY: parseFloat(el.style.top) || 0,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
// Single card drag
|
|
301
|
-
moveGroup = [{
|
|
302
|
-
el: card,
|
|
303
|
-
startX: parseFloat(card.style.left) || 0,
|
|
304
|
-
startY: parseFloat(card.style.top) || 0,
|
|
305
|
-
}];
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const onMove = (ev: MouseEvent) => {
|
|
309
|
-
if (!dragging) return;
|
|
310
|
-
|
|
311
|
-
// Check drag threshold before starting actual drag
|
|
312
|
-
if (!dragStarted) {
|
|
313
|
-
const screenDist = Math.sqrt(
|
|
314
|
-
(ev.clientX - startScreenX) ** 2 + (ev.clientY - startScreenY) ** 2
|
|
315
|
-
);
|
|
316
|
-
if (screenDist < DRAG_THRESHOLD) return;
|
|
317
|
-
dragStarted = true;
|
|
318
|
-
card.classList.add('gd-card--dragging');
|
|
319
|
-
// Prevent default only after threshold is met to avoid text selection etc.
|
|
320
|
-
ev.preventDefault();
|
|
321
|
-
} else {
|
|
322
|
-
// Continue preventing default for subsequent moves if drag has started
|
|
323
|
-
ev.preventDefault();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const curr = this.state.screenToWorld(ev.clientX, ev.clientY);
|
|
327
|
-
const dx = curr.x - startWorldX;
|
|
328
|
-
const dy = curr.y - startWorldY;
|
|
329
|
-
|
|
330
|
-
for (const info of moveGroup) {
|
|
331
|
-
let newX = info.startX + dx;
|
|
332
|
-
let newY = info.startY + dy;
|
|
333
|
-
|
|
334
|
-
// Snap to grid
|
|
335
|
-
if (this.opts.gridSize > 0 && ev.shiftKey) {
|
|
336
|
-
newX = Math.round(newX / this.opts.gridSize) * this.opts.gridSize;
|
|
337
|
-
newY = Math.round(newY / this.opts.gridSize) * this.opts.gridSize;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
info.el.style.left = `${newX}px`;
|
|
341
|
-
info.el.style.top = `${newY}px`;
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
const onUp = () => {
|
|
346
|
-
dragging = false;
|
|
347
|
-
card.classList.remove('gd-card--dragging');
|
|
348
|
-
window.removeEventListener('mousemove', onMove);
|
|
349
|
-
window.removeEventListener('mouseup', onUp);
|
|
350
|
-
|
|
351
|
-
// Only emit move events if drag actually started
|
|
352
|
-
if (dragStarted) {
|
|
353
|
-
for (const info of moveGroup) {
|
|
354
|
-
const x = parseFloat(info.el.style.left) || 0;
|
|
355
|
-
const y = parseFloat(info.el.style.top) || 0;
|
|
356
|
-
const id = info.el.dataset.cardId || info.el.dataset.path || '';
|
|
357
|
-
this.bus.emit('card:move', { id, x, y });
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
window.addEventListener('mousemove', onMove);
|
|
363
|
-
window.addEventListener('mouseup', onUp);
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// ─── Resize interaction ──────────────────────────────
|
|
368
|
-
|
|
369
|
-
private setupResize(card: HTMLElement, type: string) {
|
|
370
|
-
const handle = document.createElement('div');
|
|
371
|
-
handle.className = 'gd-resize-handle';
|
|
372
|
-
card.appendChild(handle);
|
|
373
|
-
|
|
374
|
-
let resizing = false;
|
|
375
|
-
let startW = 0, startH = 0, startX = 0, startY = 0;
|
|
376
|
-
|
|
377
|
-
handle.addEventListener('mousedown', (e: MouseEvent) => {
|
|
378
|
-
e.preventDefault();
|
|
379
|
-
e.stopPropagation();
|
|
380
|
-
resizing = true;
|
|
381
|
-
startW = card.offsetWidth;
|
|
382
|
-
startH = card.offsetHeight;
|
|
383
|
-
startX = e.clientX;
|
|
384
|
-
startY = e.clientY;
|
|
385
|
-
card.classList.add('gd-card--resizing');
|
|
386
|
-
|
|
387
|
-
const onMove = (ev: MouseEvent) => {
|
|
388
|
-
if (!resizing) return;
|
|
389
|
-
const dw = (ev.clientX - startX) / this.state.zoom;
|
|
390
|
-
const dh = (ev.clientY - startY) / this.state.zoom;
|
|
391
|
-
const w = Math.max(this.opts.minWidth, startW + dw);
|
|
392
|
-
const h = Math.max(this.opts.minHeight, startH + dh);
|
|
393
|
-
card.style.width = `${w}px`;
|
|
394
|
-
card.style.height = `${h}px`;
|
|
395
|
-
this.plugins.get(type)?.onResize?.(card, w, h);
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
const onUp = () => {
|
|
399
|
-
resizing = false;
|
|
400
|
-
card.classList.remove('gd-card--resizing');
|
|
401
|
-
window.removeEventListener('mousemove', onMove);
|
|
402
|
-
window.removeEventListener('mouseup', onUp);
|
|
403
|
-
this.bus.emit('card:resize', {
|
|
404
|
-
id: card.dataset.cardId!,
|
|
405
|
-
width: card.offsetWidth,
|
|
406
|
-
height: card.offsetHeight,
|
|
407
|
-
});
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
window.addEventListener('mousemove', onMove);
|
|
411
|
-
window.addEventListener('mouseup', onUp);
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// ─── Queries ─────────────────────────────────────────
|
|
416
|
-
|
|
417
|
-
/** Check if a plugin says it handles wheel events for a target element */
|
|
418
|
-
consumesWheel(target: HTMLElement): boolean {
|
|
419
|
-
// Walk up to find the card — check both .gd-card (engine-created) and [data-card-type] (externally managed)
|
|
420
|
-
const card = (target.closest('.gd-card') || target.closest('[data-card-type]')) as HTMLElement | null;
|
|
421
|
-
if (!card) return false;
|
|
422
|
-
const type = card.dataset.cardType;
|
|
423
|
-
if (!type) return false;
|
|
424
|
-
return this.plugins.get(type)?.consumesWheel?.(target) ?? false;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/** Check if a plugin says it handles mouse events for a target element */
|
|
428
|
-
consumesMouse(target: HTMLElement): boolean {
|
|
429
|
-
const card = (target.closest('.gd-card') || target.closest('[data-card-type]')) as HTMLElement | null;
|
|
430
|
-
if (!card) return false;
|
|
431
|
-
const type = card.dataset.cardType;
|
|
432
|
-
if (!type) return false;
|
|
433
|
-
return this.plugins.get(type)?.consumesMouse?.(target) ?? false;
|
|
434
|
-
}
|
|
435
|
-
}
|