gitmaps 1.0.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 +265 -122
- package/app/[...slug]/page.client.tsx +1 -0
- package/app/[...slug]/page.tsx +6 -0
- package/app/[owner]/[repo]/page.client.tsx +5 -0
- package/app/[slug]/page.client.tsx +5 -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/manifest.json/route.ts +20 -0
- package/app/api/og-image/route.ts +14 -0
- package/app/api/pwa-icon/route.ts +14 -0
- package/app/api/repo/clone-stream/route.ts +20 -12
- package/app/api/repo/file-content/route.ts +73 -20
- package/app/api/repo/imports/route.ts +21 -3
- package/app/api/repo/list/route.ts +30 -0
- 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/repo/upload/route.ts +6 -9
- package/app/api/sw.js/route.ts +70 -0
- package/app/api/version/route.ts +26 -0
- package/app/galaxy-canvas/page.client.tsx +2 -0
- package/app/galaxy-canvas/page.tsx +5 -0
- package/app/globals.css +5844 -4694
- package/app/icon.png +0 -0
- package/app/layout.tsx +1284 -467
- 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-text.ts +4 -72
- package/app/lib/canvas.ts +625 -564
- package/app/lib/card-arrangement.ts +21 -7
- package/app/lib/card-context-menu.tsx +2 -2
- package/app/lib/card-groups.ts +9 -2
- package/app/lib/cards.tsx +1361 -914
- package/app/lib/chat.tsx +65 -9
- package/app/lib/code-editor.ts +86 -2
- package/app/lib/connections.tsx +34 -43
- 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 +76 -73
- package/app/lib/export-canvas.ts +287 -0
- package/app/lib/file-card-plugin.ts +148 -134
- package/app/lib/file-modal.tsx +49 -0
- package/app/lib/file-preview.ts +486 -400
- package/app/lib/github-import.test.ts +424 -0
- package/app/lib/global-search.ts +48 -27
- 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/layers.tsx +17 -18
- 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/perf-overlay.ts +78 -0
- package/app/lib/positions.ts +191 -122
- 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 -977
- 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/shortcuts-panel.ts +2 -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 -728
- 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 -477
- package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
- package/app/og-image.png +0 -0
- package/app/page.client.tsx +70 -215
- package/app/page.tsx +27 -92
- package/app/state/machine.js +13 -0
- package/banner.png +0 -0
- package/package.json +17 -8
- package/server.ts +11 -1
- package/app/api/connections/route.ts +0 -72
- package/app/api/positions/route.ts +0 -80
- package/app/api/repo/browse/route.ts +0 -55
- package/app/lib/pr-review.ts +0 -374
- 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,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["..\\src\\core\\state.ts", "..\\src\\core\\cards.ts", "..\\src\\core\\viewport.ts", "..\\src\\core\\events.ts", "..\\src\\core\\engine.ts", "..\\src\\core\\layout.ts", "..\\src\\core\\minimap.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\r\n * CanvasState — Reactive state container for the infinite canvas\r\n *\r\n * Tracks zoom level, pan offset, and provides world↔screen\r\n * coordinate conversion utilities.\r\n */\r\n\r\nexport interface CanvasStateSnapshot {\r\n zoom: number;\r\n offsetX: number;\r\n offsetY: number;\r\n}\r\n\r\nexport interface ViewportRect {\r\n left: number;\r\n top: number;\r\n right: number;\r\n bottom: number;\r\n width: number;\r\n height: number;\r\n}\r\n\r\nexport class CanvasState {\r\n zoom = 1;\r\n offsetX = 0;\r\n offsetY = 0;\r\n\r\n private viewportEl: HTMLElement | null = null;\r\n private contentEl: HTMLElement | null = null;\r\n private listeners = new Set<() => void>();\r\n\r\n // ─── Zoom limits ─────────────────────────────────────\r\n readonly MIN_ZOOM = 0.05;\r\n readonly MAX_ZOOM = 5;\r\n\r\n constructor(viewport?: HTMLElement, content?: HTMLElement) {\r\n this.viewportEl = viewport ?? null;\r\n this.contentEl = content ?? null;\r\n }\r\n\r\n /** Bind to DOM elements after mount */\r\n bind(viewport: HTMLElement, content: HTMLElement) {\r\n this.viewportEl = viewport;\r\n this.contentEl = content;\r\n this.applyTransform();\r\n }\r\n\r\n /** Get a frozen snapshot of current state */\r\n snapshot(): CanvasStateSnapshot {\r\n return { zoom: this.zoom, offsetX: this.offsetX, offsetY: this.offsetY };\r\n }\r\n\r\n /** Subscribe to state changes */\r\n subscribe(fn: () => void): () => void {\r\n this.listeners.add(fn);\r\n return () => this.listeners.delete(fn);\r\n }\r\n\r\n private notify() {\r\n for (const fn of this.listeners) fn();\r\n }\r\n\r\n // ─── Transform ───────────────────────────────────────\r\n\r\n /** Apply current state to the content element's CSS transform */\r\n applyTransform() {\r\n if (!this.contentEl) return;\r\n this.contentEl.style.transform =\r\n `translate(${this.offsetX}px, ${this.offsetY}px) scale(${this.zoom})`;\r\n }\r\n\r\n /** Set zoom + offset, clamping zoom to limits */\r\n set(zoom: number, offsetX: number, offsetY: number) {\r\n this.zoom = Math.max(this.MIN_ZOOM, Math.min(this.MAX_ZOOM, zoom));\r\n this.offsetX = offsetX;\r\n this.offsetY = offsetY;\r\n this.applyTransform();\r\n this.notify();\r\n }\r\n\r\n /** Pan by delta pixels */\r\n pan(dx: number, dy: number) {\r\n this.offsetX += dx;\r\n this.offsetY += dy;\r\n this.applyTransform();\r\n this.notify();\r\n }\r\n\r\n /** Center the viewport on a world coordinate */\r\n panTo(worldX: number, worldY: number) {\r\n if (!this.viewportEl) return;\r\n const vpW = this.viewportEl.clientWidth;\r\n const vpH = this.viewportEl.clientHeight;\r\n this.offsetX = vpW / 2 - worldX * this.zoom;\r\n this.offsetY = vpH / 2 - worldY * this.zoom;\r\n this.applyTransform();\r\n this.notify();\r\n }\r\n\r\n /** Zoom toward a screen point (e.g. cursor position) */\r\n zoomToward(screenX: number, screenY: number, factor: number) {\r\n const newZoom = Math.max(this.MIN_ZOOM, Math.min(this.MAX_ZOOM, this.zoom * factor));\r\n if (newZoom === this.zoom) return;\r\n\r\n const rect = this.viewportEl?.getBoundingClientRect();\r\n const mouseX = screenX - (rect?.left ?? 0);\r\n const mouseY = screenY - (rect?.top ?? 0);\r\n\r\n // Convert screen point to world coordinates at current zoom\r\n const worldX = (mouseX - this.offsetX) / this.zoom;\r\n const worldY = (mouseY - this.offsetY) / this.zoom;\r\n\r\n // Update zoom and recalculate offset to keep world point under cursor\r\n this.zoom = newZoom;\r\n this.offsetX = mouseX - worldX * newZoom;\r\n this.offsetY = mouseY - worldY * newZoom;\r\n\r\n this.applyTransform();\r\n this.notify();\r\n }\r\n\r\n // ─── Coordinate conversion ───────────────────────────\r\n\r\n /** Screen pixel → world coordinate */\r\n screenToWorld(screenX: number, screenY: number): { x: number; y: number } {\r\n const rect = this.viewportEl?.getBoundingClientRect();\r\n const localX = screenX - (rect?.left ?? 0);\r\n const localY = screenY - (rect?.top ?? 0);\r\n return {\r\n x: (localX - this.offsetX) / this.zoom,\r\n y: (localY - this.offsetY) / this.zoom,\r\n };\r\n }\r\n\r\n /** World coordinate → screen pixel */\r\n worldToScreen(worldX: number, worldY: number): { x: number; y: number } {\r\n const rect = this.viewportEl?.getBoundingClientRect();\r\n return {\r\n x: worldX * this.zoom + this.offsetX + (rect?.left ?? 0),\r\n y: worldY * this.zoom + this.offsetY + (rect?.top ?? 0),\r\n };\r\n }\r\n\r\n /** Get the visible world-space rectangle (with optional margin in screen px) */\r\n getVisibleWorldRect(margin = 0): ViewportRect | null {\r\n if (!this.viewportEl) return null;\r\n const vpW = this.viewportEl.clientWidth;\r\n const vpH = this.viewportEl.clientHeight;\r\n\r\n const left = (-this.offsetX - margin) / this.zoom;\r\n const top = (-this.offsetY - margin) / this.zoom;\r\n const right = (vpW - this.offsetX + margin) / this.zoom;\r\n const bottom = (vpH - this.offsetY + margin) / this.zoom;\r\n\r\n return { left, top, right, bottom, width: right - left, height: bottom - top };\r\n }\r\n\r\n /** Fit a world-space bounding box into the viewport */\r\n fitRect(worldLeft: number, worldTop: number, worldRight: number, worldBottom: number, padding = 60) {\r\n if (!this.viewportEl) return;\r\n const vpW = this.viewportEl.clientWidth;\r\n const vpH = this.viewportEl.clientHeight;\r\n\r\n const w = worldRight - worldLeft + padding * 2;\r\n const h = worldBottom - worldTop + padding * 2;\r\n const zoom = Math.min(vpW / w, vpH / h, this.MAX_ZOOM);\r\n\r\n this.set(\r\n zoom,\r\n (vpW - w * zoom) / 2 - (worldLeft - padding) * zoom,\r\n (vpH - h * zoom) / 2 - (worldTop - padding) * zoom,\r\n );\r\n }\r\n}\r\n",
|
|
6
|
-
"/**\r\n * CardManager — Manages cards (containers/widgets) on the canvas\r\n *\r\n * Handles:\r\n * - Card creation with custom content (via plugins)\r\n * - Drag, resize, z-order\r\n * - Selection (single, multi, rect-select)\r\n * - Collapse/expand\r\n * - Virtualized deferred rendering\r\n */\r\n\r\nimport type { CanvasState, ViewportRect } from './state';\r\nimport type { EventBus } from './events';\r\n\r\n// ─── Types ───────────────────────────────────────────────\r\n\r\nexport interface CardData {\r\n id: string;\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n collapsed?: boolean;\r\n /** Arbitrary data attached by the consumer */\r\n meta?: Record<string, any>;\r\n}\r\n\r\nexport interface CardOptions {\r\n /** Default card width */\r\n defaultWidth?: number;\r\n /** Default card height */\r\n defaultHeight?: number;\r\n /** Minimum card width when resizing */\r\n minWidth?: number;\r\n /** Minimum card height when resizing */\r\n minHeight?: number;\r\n /** Grid snap size (0 = no snap) */\r\n gridSize?: number;\r\n /** Corner hit area for resize handle */\r\n cornerSize?: number;\r\n}\r\n\r\n/**\r\n * CardPlugin — how consumers define custom card types.\r\n *\r\n * Example: GitMaps provides a FileCardPlugin that renders source code.\r\n * WARMAPS provides a MapCardPlugin that renders MapLibre.\r\n */\r\nexport interface CardPlugin {\r\n /** Unique type identifier */\r\n type: string;\r\n /** Create the DOM element for this card */\r\n render(data: CardData): HTMLElement;\r\n /** Called when the card is resized */\r\n onResize?(card: HTMLElement, width: number, height: number): void;\r\n /** Called when the card is destroyed */\r\n onDestroy?(card: HTMLElement): void;\r\n /** Should wheel events be passed through? (e.g. maps, scrollable content) */\r\n consumesWheel?(target: HTMLElement): boolean;\r\n /** Should mouse events be passed through? (e.g. maps, interactive widgets) */\r\n consumesMouse?(target: HTMLElement): boolean;\r\n}\r\n\r\nconst DEFAULT_OPTIONS: Required<CardOptions> = {\r\n defaultWidth: 400,\r\n defaultHeight: 300,\r\n minWidth: 200,\r\n minHeight: 150,\r\n gridSize: 0,\r\n cornerSize: 40,\r\n};\r\n\r\n// ─── CardManager ─────────────────────────────────────────\r\n\r\nexport class CardManager {\r\n /** Active DOM cards: id → element */\r\n readonly cards = new Map<string, HTMLElement>();\r\n /** Deferred cards (not yet in DOM): id → data */\r\n readonly deferred = new Map<string, CardData & { plugin?: string }>();\r\n /** Currently selected card IDs */\r\n readonly selected = new Set<string>();\r\n\r\n private topZ = 10;\r\n private plugins = new Map<string, CardPlugin>();\r\n private opts: Required<CardOptions>;\r\n\r\n constructor(\r\n private state: CanvasState,\r\n private bus: EventBus,\r\n private canvas: HTMLElement,\r\n options?: CardOptions,\r\n ) {\r\n this.opts = { ...DEFAULT_OPTIONS, ...options };\r\n }\r\n\r\n // ─── Plugin registration ─────────────────────────────\r\n\r\n registerPlugin(plugin: CardPlugin) {\r\n this.plugins.set(plugin.type, plugin);\r\n }\r\n\r\n // ─── Card lifecycle ──────────────────────────────────\r\n\r\n /** Create a card and add it to the canvas */\r\n create(type: string, data: Partial<CardData> & { id: string }): HTMLElement | null {\r\n const plugin = this.plugins.get(type);\r\n if (!plugin) {\r\n console.warn(`[galaxydraw] No plugin registered for card type \"${type}\"`);\r\n return null;\r\n }\r\n\r\n const full: CardData = {\r\n x: data.x ?? 0,\r\n y: data.y ?? 0,\r\n width: data.width ?? this.opts.defaultWidth,\r\n height: data.height ?? this.opts.defaultHeight,\r\n collapsed: data.collapsed ?? false,\r\n meta: data.meta ?? {},\r\n ...data,\r\n };\r\n\r\n const el = plugin.render(full);\r\n el.classList.add('gd-card');\r\n el.dataset.cardId = full.id;\r\n el.dataset.cardType = type;\r\n el.style.left = `${full.x}px`;\r\n el.style.top = `${full.y}px`;\r\n el.style.width = `${full.width}px`;\r\n if (!full.collapsed) {\r\n el.style.height = `${full.height}px`;\r\n }\r\n\r\n this.canvas.appendChild(el);\r\n this.cards.set(full.id, el);\r\n this.bringToFront(el);\r\n\r\n this.setupDrag(el);\r\n this.setupResize(el, type);\r\n\r\n this.bus.emit('card:create', { id: full.id, x: full.x, y: full.y });\r\n return el;\r\n }\r\n\r\n /** Remove a card from the canvas and clean up */\r\n remove(id: string) {\r\n const el = this.cards.get(id);\r\n if (!el) {\r\n this.deferred.delete(id);\r\n return;\r\n }\r\n\r\n const type = el.dataset.cardType;\r\n if (type) {\r\n this.plugins.get(type)?.onDestroy?.(el);\r\n }\r\n\r\n el.remove();\r\n this.cards.delete(id);\r\n this.selected.delete(id);\r\n this.bus.emit('card:remove', { id });\r\n }\r\n\r\n /** Defer a card (store data, don't create DOM until it enters viewport) */\r\n defer(type: string, data: CardData) {\r\n this.deferred.set(data.id, { ...data, plugin: type });\r\n }\r\n\r\n /** Materialize deferred cards that overlap the given world rect */\r\n materializeInRect(worldRect: ViewportRect): number {\r\n let count = 0;\r\n const toRemove: string[] = [];\r\n\r\n for (const [id, entry] of this.deferred) {\r\n const { x, y, width, height, plugin } = entry as any;\r\n const w = width || this.opts.defaultWidth;\r\n const h = height || this.opts.defaultHeight;\r\n\r\n if (\r\n x + w > worldRect.left &&\r\n x < worldRect.right &&\r\n y + h > worldRect.top &&\r\n y < worldRect.bottom\r\n ) {\r\n if (plugin) {\r\n this.create(plugin, entry);\r\n }\r\n toRemove.push(id);\r\n count++;\r\n }\r\n }\r\n\r\n for (const id of toRemove) {\r\n this.deferred.delete(id);\r\n }\r\n return count;\r\n }\r\n\r\n /** Remove all cards and deferred entries */\r\n clear() {\r\n for (const [id, el] of this.cards) {\r\n const type = el.dataset.cardType;\r\n if (type) this.plugins.get(type)?.onDestroy?.(el);\r\n el.remove();\r\n }\r\n this.cards.clear();\r\n this.deferred.clear();\r\n this.selected.clear();\r\n }\r\n\r\n // ─── Z-order ─────────────────────────────────────────\r\n\r\n bringToFront(el: HTMLElement) {\r\n this.topZ++;\r\n el.style.zIndex = String(this.topZ);\r\n }\r\n\r\n // ─── Selection ───────────────────────────────────────\r\n\r\n select(id: string, multi = false) {\r\n if (!multi) {\r\n this.deselectAll();\r\n }\r\n this.selected.add(id);\r\n this.cards.get(id)?.classList.add('gd-card--selected');\r\n this.bus.emit('card:select', { ids: [...this.selected] });\r\n }\r\n\r\n deselect(id: string) {\r\n this.selected.delete(id);\r\n this.cards.get(id)?.classList.remove('gd-card--selected');\r\n this.bus.emit('card:deselect', { ids: [id] });\r\n }\r\n\r\n deselectAll() {\r\n for (const id of this.selected) {\r\n this.cards.get(id)?.classList.remove('gd-card--selected');\r\n }\r\n const prev = [...this.selected];\r\n this.selected.clear();\r\n if (prev.length > 0) {\r\n this.bus.emit('card:deselect', { ids: prev });\r\n }\r\n }\r\n\r\n // ─── Collapse ────────────────────────────────────────\r\n\r\n toggleCollapse(id: string) {\r\n const el = this.cards.get(id);\r\n if (!el) return;\r\n const collapsed = el.classList.toggle('gd-card--collapsed');\r\n this.bus.emit('card:collapse', { id, collapsed });\r\n }\r\n\r\n // ─── Drag interaction ────────────────────────────────\r\n\r\n private setupDrag(card: HTMLElement) {\r\n const header = card.querySelector('.gd-card-header') as HTMLElement | null;\r\n const handle = header || card;\r\n\r\n let dragging = false;\r\n let startWorldX = 0, startWorldY = 0;\r\n let cardStartX = 0, cardStartY = 0;\r\n\r\n handle.addEventListener('mousedown', (e: MouseEvent) => {\r\n if (e.button !== 0) return;\r\n // Don't start drag if clicking inside content\r\n if (header && e.target !== header && !header.contains(e.target as Node)) return;\r\n\r\n e.preventDefault();\r\n dragging = true;\r\n this.bringToFront(card);\r\n\r\n const world = this.state.screenToWorld(e.clientX, e.clientY);\r\n cardStartX = parseFloat(card.style.left) || 0;\r\n cardStartY = parseFloat(card.style.top) || 0;\r\n startWorldX = world.x;\r\n startWorldY = world.y;\r\n card.classList.add('gd-card--dragging');\r\n\r\n const onMove = (ev: MouseEvent) => {\r\n if (!dragging) return;\r\n const curr = this.state.screenToWorld(ev.clientX, ev.clientY);\r\n let newX = cardStartX + (curr.x - startWorldX);\r\n let newY = cardStartY + (curr.y - startWorldY);\r\n\r\n // Snap to grid\r\n if (this.opts.gridSize > 0 && ev.shiftKey) {\r\n newX = Math.round(newX / this.opts.gridSize) * this.opts.gridSize;\r\n newY = Math.round(newY / this.opts.gridSize) * this.opts.gridSize;\r\n }\r\n\r\n card.style.left = `${newX}px`;\r\n card.style.top = `${newY}px`;\r\n };\r\n\r\n const onUp = () => {\r\n dragging = false;\r\n card.classList.remove('gd-card--dragging');\r\n window.removeEventListener('mousemove', onMove);\r\n window.removeEventListener('mouseup', onUp);\r\n\r\n const x = parseFloat(card.style.left) || 0;\r\n const y = parseFloat(card.style.top) || 0;\r\n this.bus.emit('card:move', { id: card.dataset.cardId!, x, y });\r\n };\r\n\r\n window.addEventListener('mousemove', onMove);\r\n window.addEventListener('mouseup', onUp);\r\n });\r\n }\r\n\r\n // ─── Resize interaction ──────────────────────────────\r\n\r\n private setupResize(card: HTMLElement, type: string) {\r\n const handle = document.createElement('div');\r\n handle.className = 'gd-resize-handle';\r\n card.appendChild(handle);\r\n\r\n let resizing = false;\r\n let startW = 0, startH = 0, startX = 0, startY = 0;\r\n\r\n handle.addEventListener('mousedown', (e: MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n resizing = true;\r\n startW = card.offsetWidth;\r\n startH = card.offsetHeight;\r\n startX = e.clientX;\r\n startY = e.clientY;\r\n card.classList.add('gd-card--resizing');\r\n\r\n const onMove = (ev: MouseEvent) => {\r\n if (!resizing) return;\r\n const dw = (ev.clientX - startX) / this.state.zoom;\r\n const dh = (ev.clientY - startY) / this.state.zoom;\r\n const w = Math.max(this.opts.minWidth, startW + dw);\r\n const h = Math.max(this.opts.minHeight, startH + dh);\r\n card.style.width = `${w}px`;\r\n card.style.height = `${h}px`;\r\n this.plugins.get(type)?.onResize?.(card, w, h);\r\n };\r\n\r\n const onUp = () => {\r\n resizing = false;\r\n card.classList.remove('gd-card--resizing');\r\n window.removeEventListener('mousemove', onMove);\r\n window.removeEventListener('mouseup', onUp);\r\n this.bus.emit('card:resize', {\r\n id: card.dataset.cardId!,\r\n width: card.offsetWidth,\r\n height: card.offsetHeight,\r\n });\r\n };\r\n\r\n window.addEventListener('mousemove', onMove);\r\n window.addEventListener('mouseup', onUp);\r\n });\r\n }\r\n\r\n // ─── Queries ─────────────────────────────────────────\r\n\r\n /** Check if a plugin says it handles wheel events for a target element */\r\n consumesWheel(target: HTMLElement): boolean {\r\n // Walk up to find the card — check both .gd-card (engine-created) and [data-card-type] (externally managed)\r\n const card = (target.closest('.gd-card') || target.closest('[data-card-type]')) as HTMLElement | null;\r\n if (!card) return false;\r\n const type = card.dataset.cardType;\r\n if (!type) return false;\r\n return this.plugins.get(type)?.consumesWheel?.(target) ?? false;\r\n }\r\n\r\n /** Check if a plugin says it handles mouse events for a target element */\r\n consumesMouse(target: HTMLElement): boolean {\r\n const card = (target.closest('.gd-card') || target.closest('[data-card-type]')) as HTMLElement | null;\r\n if (!card) return false;\r\n const type = card.dataset.cardType;\r\n if (!type) return false;\r\n return this.plugins.get(type)?.consumesMouse?.(target) ?? false;\r\n }\r\n}\r\n",
|
|
7
|
-
"/**\r\n * ViewportCuller — Hide/show cards based on viewport visibility\r\n *\r\n * Uses content-visibility: hidden for off-screen cards (keeps dimensions\r\n * for layout stability) and materializes deferred cards on demand.\r\n */\r\n\r\nimport type { CanvasState } from './state';\r\nimport type { CardManager } from './cards';\r\nimport type { EventBus } from './events';\r\n\r\nexport interface CullResult {\r\n shown: number;\r\n culled: number;\r\n materialized: number;\r\n total: number;\r\n}\r\n\r\nexport class ViewportCuller {\r\n private rafPending = false;\r\n private enabled = true;\r\n /** Margin in screen pixels beyond the viewport for pre-rendering */\r\n margin = 500;\r\n\r\n constructor(\r\n private state: CanvasState,\r\n private cards: CardManager,\r\n private bus: EventBus,\r\n ) { }\r\n\r\n /** Enable/disable culling */\r\n setEnabled(enabled: boolean) {\r\n this.enabled = enabled;\r\n }\r\n\r\n /** Schedule a culling pass on the next animation frame */\r\n schedule() {\r\n if (this.rafPending || !this.enabled) return;\r\n this.rafPending = true;\r\n requestAnimationFrame(() => {\r\n this.rafPending = false;\r\n this.perform();\r\n });\r\n }\r\n\r\n /** Perform immediate culling pass */\r\n perform(): CullResult {\r\n const result: CullResult = { shown: 0, culled: 0, materialized: 0, total: 0 };\r\n if (!this.enabled) return result;\r\n\r\n const worldRect = this.state.getVisibleWorldRect(this.margin);\r\n if (!worldRect) return result;\r\n\r\n // 1. Cull/show existing DOM cards\r\n for (const [id, card] of this.cards.cards) {\r\n const visible = this.isCardInRect(card, worldRect);\r\n const wasCulled = card.dataset.culled === 'true';\r\n\r\n if (visible && wasCulled) {\r\n card.style.contentVisibility = '';\r\n card.style.visibility = '';\r\n card.dataset.culled = 'false';\r\n result.shown++;\r\n } else if (!visible && !wasCulled) {\r\n card.style.contentVisibility = 'hidden';\r\n card.style.visibility = 'hidden';\r\n card.dataset.culled = 'true';\r\n result.culled++;\r\n } else if (visible) {\r\n result.shown++;\r\n } else {\r\n result.culled++;\r\n }\r\n }\r\n\r\n // 2. Materialize deferred cards in viewport\r\n if (this.cards.deferred.size > 0) {\r\n result.materialized = this.cards.materializeInRect(worldRect);\r\n }\r\n\r\n result.total = this.cards.cards.size + this.cards.deferred.size;\r\n\r\n if (result.materialized > 0) {\r\n this.bus.emit('viewport:cull', result);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /** Force all cards visible (for operations that need to measure everything) */\r\n uncullAll() {\r\n for (const [, card] of this.cards.cards) {\r\n card.style.contentVisibility = '';\r\n card.style.visibility = '';\r\n card.dataset.culled = 'false';\r\n }\r\n }\r\n\r\n private isCardInRect(card: HTMLElement, rect: { left: number; top: number; right: number; bottom: number }): boolean {\r\n const x = parseFloat(card.style.left) || 0;\r\n const y = parseFloat(card.style.top) || 0;\r\n const w = card.offsetWidth || 400;\r\n const h = card.offsetHeight || 300;\r\n return x + w > rect.left && x < rect.right && y + h > rect.top && y < rect.bottom;\r\n }\r\n}\r\n",
|
|
8
|
-
"/**\r\n * EventBus — Type-safe pub/sub for canvas events\r\n *\r\n * Every action in the canvas (pan, zoom, card move, select, etc.)\r\n * flows through the event bus. This enables plugins to react to\r\n * state changes without tight coupling.\r\n */\r\n\r\nexport interface GalaxyDrawEvent {\r\n 'canvas:pan': { offsetX: number; offsetY: number };\r\n 'canvas:zoom': { zoom: number; centerX: number; centerY: number };\r\n 'canvas:resize': { width: number; height: number };\r\n\r\n 'card:create': { id: string; x: number; y: number };\r\n 'card:move': { id: string; x: number; y: number };\r\n 'card:resize': { id: string; width: number; height: number };\r\n 'card:select': { ids: string[] };\r\n 'card:deselect': { ids: string[] };\r\n 'card:remove': { id: string };\r\n 'card:collapse': { id: string; collapsed: boolean };\r\n 'card:focus': { id: string };\r\n\r\n 'layout:save': { layouts: any[] };\r\n 'layout:restore': { layouts: any[] };\r\n 'layout:reset': {};\r\n\r\n 'viewport:cull': { shown: number; culled: number; materialized: number };\r\n\r\n 'mode:change': { mode: 'simple' | 'advanced' };\r\n}\r\n\r\nexport type EventHandler<K extends keyof GalaxyDrawEvent> = (data: GalaxyDrawEvent[K]) => void;\r\n\r\nexport class EventBus {\r\n private handlers = new Map<string, Set<Function>>();\r\n\r\n on<K extends keyof GalaxyDrawEvent>(event: K, handler: EventHandler<K>): () => void {\r\n if (!this.handlers.has(event)) {\r\n this.handlers.set(event, new Set());\r\n }\r\n this.handlers.get(event)!.add(handler);\r\n\r\n // Return unsubscribe function\r\n return () => {\r\n this.handlers.get(event)?.delete(handler);\r\n };\r\n }\r\n\r\n once<K extends keyof GalaxyDrawEvent>(event: K, handler: EventHandler<K>): () => void {\r\n const wrapper = (data: GalaxyDrawEvent[K]) => {\r\n unsub();\r\n handler(data);\r\n };\r\n const unsub = this.on(event, wrapper as any);\r\n return unsub;\r\n }\r\n\r\n emit<K extends keyof GalaxyDrawEvent>(event: K, data: GalaxyDrawEvent[K]): void {\r\n const handlers = this.handlers.get(event);\r\n if (!handlers) return;\r\n for (const handler of handlers) {\r\n try {\r\n handler(data);\r\n } catch (err) {\r\n console.error(`[galaxydraw] Event handler error for \"${event}\":`, err);\r\n }\r\n }\r\n }\r\n\r\n off<K extends keyof GalaxyDrawEvent>(event: K, handler?: EventHandler<K>): void {\r\n if (handler) {\r\n this.handlers.get(event)?.delete(handler);\r\n } else {\r\n this.handlers.delete(event);\r\n }\r\n }\r\n\r\n clear(): void {\r\n this.handlers.clear();\r\n }\r\n}\r\n",
|
|
9
|
-
"/**\r\n * GalaxyDraw — Main engine class\r\n *\r\n * Creates an infinite canvas with pan/zoom/drag support.\r\n * Supports two control modes:\r\n * - Simple: click-drag = pan canvas (WARMAPS style)\r\n * - Advanced: click-drag = select, Space+drag = pan (GitMaps style)\r\n *\r\n * Usage:\r\n * const gd = new GalaxyDraw(containerEl, { mode: 'simple' });\r\n * gd.cards.registerPlugin(myPlugin);\r\n * gd.cards.create('widget', { id: 'w1', x: 100, y: 100 });\r\n */\r\n\r\nimport { CanvasState } from './state';\r\nimport { CardManager } from './cards';\r\nimport type { CardOptions, CardPlugin } from './cards';\r\nimport { ViewportCuller } from './viewport';\r\nimport { EventBus } from './events';\r\n\r\nexport type ControlMode = 'simple' | 'advanced';\r\n\r\nexport interface GalaxyDrawOptions {\r\n /** Control scheme: 'simple' = drag to pan, 'advanced' = space+drag to pan */\r\n mode?: ControlMode;\r\n /** Card defaults */\r\n cards?: CardOptions;\r\n /** Viewport culling margin in px */\r\n cullMargin?: number;\r\n /** Enable minimap */\r\n minimap?: boolean;\r\n /** Custom CSS class added to the root */\r\n className?: string;\r\n}\r\n\r\nexport class GalaxyDraw {\r\n readonly state: CanvasState;\r\n readonly cards: CardManager;\r\n readonly culler: ViewportCuller;\r\n readonly bus: EventBus;\r\n\r\n private mode: ControlMode;\r\n private viewport: HTMLElement;\r\n private canvas: HTMLElement;\r\n private spaceHeld = false;\r\n private isDragging = false;\r\n private dragStartX = 0;\r\n private dragStartY = 0;\r\n private cleanupFns: (() => void)[] = [];\r\n\r\n // Touch state\r\n private touchStartX = 0;\r\n private touchStartY = 0;\r\n private lastPinchDist = 0;\r\n\r\n constructor(container: HTMLElement, options?: GalaxyDrawOptions) {\r\n this.mode = options?.mode ?? 'simple';\r\n this.bus = new EventBus();\r\n\r\n // ── Create DOM structure ──\r\n this.viewport = document.createElement('div');\r\n this.viewport.className = `gd-viewport ${options?.className ?? ''}`.trim();\r\n this.viewport.style.cssText = 'position:relative;width:100%;height:100%;overflow:hidden;';\r\n\r\n this.canvas = document.createElement('div');\r\n this.canvas.className = 'gd-canvas';\r\n this.canvas.style.cssText = 'position:absolute;top:0;left:0;transform-origin:0 0;will-change:transform;';\r\n\r\n this.viewport.appendChild(this.canvas);\r\n container.appendChild(this.viewport);\r\n\r\n // ── Init subsystems ──\r\n this.state = new CanvasState();\r\n this.state.bind(this.viewport, this.canvas);\r\n\r\n this.cards = new CardManager(this.state, this.bus, this.canvas, options?.cards);\r\n this.culler = new ViewportCuller(this.state, this.cards, this.bus);\r\n if (options?.cullMargin) this.culler.margin = options.cullMargin;\r\n\r\n // ── Wire up interactions ──\r\n this.setupWheel();\r\n this.setupMouse();\r\n this.setupTouch();\r\n this.setupKeyboard();\r\n\r\n // ── Cull on state change ──\r\n const unsub = this.state.subscribe(() => this.culler.schedule());\r\n this.cleanupFns.push(unsub);\r\n }\r\n\r\n // ─── Public API ──────────────────────────────────────\r\n\r\n /** Switch control mode at runtime */\r\n setMode(mode: ControlMode) {\r\n this.mode = mode;\r\n this.bus.emit('mode:change', { mode });\r\n }\r\n\r\n getMode(): ControlMode {\r\n return this.mode;\r\n }\r\n\r\n /** Register a card plugin */\r\n registerPlugin(plugin: CardPlugin) {\r\n this.cards.registerPlugin(plugin);\r\n }\r\n\r\n /** Fit all cards into view */\r\n fitAll(padding = 60) {\r\n this.culler.uncullAll();\r\n if (this.cards.cards.size === 0) return;\r\n\r\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\r\n for (const [, card] of this.cards.cards) {\r\n const x = parseFloat(card.style.left) || 0;\r\n const y = parseFloat(card.style.top) || 0;\r\n const w = card.offsetWidth || 400;\r\n const h = card.offsetHeight || 300;\r\n minX = Math.min(minX, x);\r\n minY = Math.min(minY, y);\r\n maxX = Math.max(maxX, x + w);\r\n maxY = Math.max(maxY, y + h);\r\n }\r\n\r\n this.state.fitRect(minX, minY, maxX, maxY, padding);\r\n }\r\n\r\n /** Get the viewport DOM element (for appending overlays, minimap, etc.) */\r\n getViewport(): HTMLElement {\r\n return this.viewport;\r\n }\r\n\r\n /** Get the canvas DOM element */\r\n getCanvas(): HTMLElement {\r\n return this.canvas;\r\n }\r\n\r\n /** Destroy the engine, remove all listeners and DOM */\r\n destroy() {\r\n this.cleanupFns.forEach(fn => fn());\r\n this.cleanupFns = [];\r\n this.cards.clear();\r\n this.bus.clear();\r\n this.viewport.remove();\r\n }\r\n\r\n // ─── Wheel zoom ──────────────────────────────────────\r\n\r\n private setupWheel() {\r\n this.viewport.addEventListener('wheel', (e) => {\r\n const target = e.target as HTMLElement;\r\n\r\n // Let card plugins handle their own scroll (maps, feeds, etc.)\r\n if (this.cards.consumesWheel(target)) return;\r\n\r\n // Let scrollable card bodies scroll naturally\r\n const cardEl = target.closest('.gd-card') || target.closest('[data-card-type]');\r\n if (cardEl) {\r\n const scrollBody = (target.closest('.gd-card-body') || target.closest('.wm-container-body')) as HTMLElement | null;\r\n if (scrollBody && scrollBody.scrollHeight > scrollBody.clientHeight) {\r\n const atTop = scrollBody.scrollTop <= 0 && e.deltaY < 0;\r\n const atBottom = scrollBody.scrollTop + scrollBody.clientHeight >= scrollBody.scrollHeight - 1 && e.deltaY > 0;\r\n if (!atTop && !atBottom) return;\r\n }\r\n }\r\n\r\n e.preventDefault();\r\n const factor = e.deltaY < 0 ? 1.08 : 1 / 1.08;\r\n this.state.zoomToward(e.clientX, e.clientY, factor);\r\n }, { passive: false });\r\n }\r\n\r\n // ─── Mouse interactions ──────────────────────────────\r\n\r\n private setupMouse() {\r\n this.viewport.addEventListener('mousedown', (e) => {\r\n const target = e.target as HTMLElement;\r\n\r\n // Let card plugins handle their own mouse (maps, interactive widgets)\r\n if (this.cards.consumesMouse(target)) return;\r\n\r\n // Card header drag is handled by CardManager\r\n if (target.closest('.gd-card-header') || target.closest('.wm-container-header') || target.closest('.gd-resize-handle')) return;\r\n\r\n // Click on card = bring to front + select\r\n const card = (target.closest('.gd-card') || target.closest('[data-card-type]')) as HTMLElement | null;\r\n if (card && e.button === 0) {\r\n const id = card.dataset.cardId;\r\n if (id) {\r\n this.cards.bringToFront(card);\r\n this.cards.select(id, e.shiftKey);\r\n }\r\n // In advanced mode, clicking a card body doesn't pan\r\n if (this.mode === 'advanced') return;\r\n }\r\n\r\n // ── Pan logic ──\r\n const shouldPan =\r\n e.button === 1 || // middle click always pans\r\n (this.mode === 'simple' && e.button === 0 && !card) || // simple: left click on empty canvas\r\n (this.mode === 'advanced' && this.spaceHeld); // advanced: space held\r\n\r\n if (shouldPan) {\r\n this.isDragging = true;\r\n this.dragStartX = e.clientX - this.state.offsetX;\r\n this.dragStartY = e.clientY - this.state.offsetY;\r\n this.viewport.style.cursor = 'grabbing';\r\n e.preventDefault();\r\n }\r\n });\r\n\r\n window.addEventListener('mousemove', (e) => {\r\n if (this.isDragging) {\r\n this.state.set(\r\n this.state.zoom,\r\n e.clientX - this.dragStartX,\r\n e.clientY - this.dragStartY,\r\n );\r\n }\r\n });\r\n\r\n window.addEventListener('mouseup', () => {\r\n if (this.isDragging) {\r\n this.isDragging = false;\r\n this.viewport.style.cursor = '';\r\n }\r\n });\r\n }\r\n\r\n // ─── Touch interactions ──────────────────────────────\r\n\r\n private setupTouch() {\r\n const onTouchStart = (e: TouchEvent) => {\r\n const target = e.touches[0]?.target as HTMLElement;\r\n if (!target) return;\r\n\r\n // Let card plugins handle their own touch\r\n if (this.cards.consumesMouse(target)) return;\r\n\r\n if (e.touches.length === 1) {\r\n // Single finger = pan (in simple mode or space held)\r\n const touch = e.touches[0];\r\n const card = target.closest('.gd-card') || target.closest('[data-card-type]');\r\n\r\n const shouldPan =\r\n (this.mode === 'simple' && !card) ||\r\n (this.mode === 'advanced' && this.spaceHeld);\r\n\r\n if (shouldPan) {\r\n this.isDragging = true;\r\n this.touchStartX = touch.clientX - this.state.offsetX;\r\n this.touchStartY = touch.clientY - this.state.offsetY;\r\n e.preventDefault();\r\n }\r\n } else if (e.touches.length === 2) {\r\n // Two fingers = pinch zoom\r\n this.isDragging = false;\r\n const dx = e.touches[0].clientX - e.touches[1].clientX;\r\n const dy = e.touches[0].clientY - e.touches[1].clientY;\r\n this.lastPinchDist = Math.sqrt(dx * dx + dy * dy);\r\n e.preventDefault();\r\n }\r\n };\r\n\r\n const onTouchMove = (e: TouchEvent) => {\r\n if (this.isDragging && e.touches.length === 1) {\r\n const touch = e.touches[0];\r\n this.state.set(\r\n this.state.zoom,\r\n touch.clientX - this.touchStartX,\r\n touch.clientY - this.touchStartY,\r\n );\r\n e.preventDefault();\r\n }\r\n\r\n if (e.touches.length === 2) {\r\n const dx = e.touches[0].clientX - e.touches[1].clientX;\r\n const dy = e.touches[0].clientY - e.touches[1].clientY;\r\n const dist = Math.sqrt(dx * dx + dy * dy);\r\n\r\n if (this.lastPinchDist > 0) {\r\n const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2;\r\n const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2;\r\n const factor = dist / this.lastPinchDist;\r\n this.state.zoomToward(midX, midY, factor);\r\n }\r\n\r\n this.lastPinchDist = dist;\r\n e.preventDefault();\r\n }\r\n };\r\n\r\n const onTouchEnd = () => {\r\n this.isDragging = false;\r\n this.lastPinchDist = 0;\r\n };\r\n\r\n this.viewport.addEventListener('touchstart', onTouchStart, { passive: false });\r\n this.viewport.addEventListener('touchmove', onTouchMove, { passive: false });\r\n this.viewport.addEventListener('touchend', onTouchEnd);\r\n this.cleanupFns.push(() => {\r\n this.viewport.removeEventListener('touchstart', onTouchStart);\r\n this.viewport.removeEventListener('touchmove', onTouchMove);\r\n this.viewport.removeEventListener('touchend', onTouchEnd);\r\n });\r\n }\r\n\r\n // ─── Keyboard ────────────────────────────────────────\r\n\r\n private setupKeyboard() {\r\n const onKeyDown = (e: KeyboardEvent) => {\r\n if (e.code === 'Space' && !e.repeat) {\r\n const tag = (e.target as HTMLElement).tagName;\r\n if (tag === 'INPUT' || tag === 'TEXTAREA') return;\r\n e.preventDefault();\r\n this.spaceHeld = true;\r\n this.viewport.classList.add('gd-space-pan');\r\n }\r\n };\r\n\r\n const onKeyUp = (e: KeyboardEvent) => {\r\n if (e.code === 'Space') {\r\n this.spaceHeld = false;\r\n this.viewport.classList.remove('gd-space-pan');\r\n if (this.isDragging) {\r\n this.isDragging = false;\r\n this.viewport.style.cursor = '';\r\n }\r\n }\r\n };\r\n\r\n window.addEventListener('keydown', onKeyDown);\r\n window.addEventListener('keyup', onKeyUp);\r\n this.cleanupFns.push(() => {\r\n window.removeEventListener('keydown', onKeyDown);\r\n window.removeEventListener('keyup', onKeyUp);\r\n });\r\n }\r\n}\r\n",
|
|
10
|
-
"/**\r\n * LayoutManager — Persist and restore card positions/sizes\r\n *\r\n * Supports dual storage:\r\n * - localStorage (always available)\r\n * - Optional server-side provider (for multi-device sync)\r\n */\r\n\r\nimport type { CardManager, CardData } from './cards';\r\nimport type { EventBus } from './events';\r\n\r\nexport interface LayoutData {\r\n id: string;\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n collapsed?: boolean;\r\n}\r\n\r\n/** Override this to add server-side persistence */\r\nexport interface LayoutProvider {\r\n save(key: string, layouts: LayoutData[]): Promise<void>;\r\n load(key: string): Promise<LayoutData[]>;\r\n}\r\n\r\nexport class LayoutManager {\r\n private saveTimer: ReturnType<typeof setTimeout> | null = null;\r\n private debounceMs = 300;\r\n private provider: LayoutProvider | null = null;\r\n\r\n constructor(\r\n private cards: CardManager,\r\n private bus: EventBus,\r\n private storagePrefix = 'galaxydraw',\r\n ) {\r\n // Auto-save on card move/resize\r\n this.bus.on('card:move', () => this.debounceSave());\r\n this.bus.on('card:resize', () => this.debounceSave());\r\n }\r\n\r\n /** Set a custom persistence provider (e.g. server-side) */\r\n setProvider(provider: LayoutProvider) {\r\n this.provider = provider;\r\n }\r\n\r\n /** Save current card layouts */\r\n async save(key: string) {\r\n const layouts: LayoutData[] = [];\r\n for (const [id, el] of this.cards.cards) {\r\n layouts.push({\r\n id,\r\n x: parseFloat(el.style.left) || 0,\r\n y: parseFloat(el.style.top) || 0,\r\n width: el.offsetWidth,\r\n height: el.offsetHeight,\r\n collapsed: el.classList.contains('gd-card--collapsed'),\r\n });\r\n }\r\n\r\n // Save to localStorage\r\n const lsKey = `${this.storagePrefix}:layout:${key}`;\r\n try {\r\n localStorage.setItem(lsKey, JSON.stringify(layouts));\r\n } catch { }\r\n\r\n // Save to provider\r\n if (this.provider) {\r\n try {\r\n await this.provider.save(key, layouts);\r\n } catch (err) {\r\n console.warn('[galaxydraw] Layout save to provider failed:', err);\r\n }\r\n }\r\n\r\n this.bus.emit('layout:save', { layouts });\r\n }\r\n\r\n /** Load layouts from storage */\r\n async load(key: string): Promise<LayoutData[]> {\r\n // Try provider first\r\n if (this.provider) {\r\n try {\r\n const remote = await this.provider.load(key);\r\n if (remote.length > 0) return remote;\r\n } catch { }\r\n }\r\n\r\n // Fall back to localStorage\r\n const lsKey = `${this.storagePrefix}:layout:${key}`;\r\n try {\r\n const raw = localStorage.getItem(lsKey);\r\n if (raw) return JSON.parse(raw);\r\n } catch { }\r\n\r\n return [];\r\n }\r\n\r\n /** Apply loaded layouts to existing cards */\r\n apply(layouts: LayoutData[]) {\r\n const layoutMap = new Map(layouts.map(l => [l.id, l]));\r\n\r\n for (const [id, el] of this.cards.cards) {\r\n const layout = layoutMap.get(id);\r\n if (!layout) continue;\r\n\r\n el.style.left = `${layout.x}px`;\r\n el.style.top = `${layout.y}px`;\r\n el.style.width = `${layout.width}px`;\r\n el.style.height = `${layout.height}px`;\r\n if (layout.collapsed) {\r\n el.classList.add('gd-card--collapsed');\r\n }\r\n }\r\n\r\n this.bus.emit('layout:restore', { layouts });\r\n }\r\n\r\n /** Clear saved layouts */\r\n reset(key: string) {\r\n const lsKey = `${this.storagePrefix}:layout:${key}`;\r\n try { localStorage.removeItem(lsKey); } catch { }\r\n this.bus.emit('layout:reset', {});\r\n }\r\n\r\n private _currentKey = '';\r\n setCurrentKey(key: string) { this._currentKey = key; }\r\n\r\n private debounceSave() {\r\n if (!this._currentKey) return;\r\n if (this.saveTimer) clearTimeout(this.saveTimer);\r\n this.saveTimer = setTimeout(() => {\r\n this.save(this._currentKey);\r\n }, this.debounceMs);\r\n }\r\n}\r\n",
|
|
11
|
-
"/**\r\n * Minimap — Small overview of the entire canvas\r\n *\r\n * Renders a proportional view of all card positions as colored\r\n * rectangles. Click/drag on the minimap to navigate.\r\n */\r\n\r\nimport type { CanvasState } from './state';\r\nimport type { CardManager } from './cards';\r\n\r\nexport class Minimap {\r\n private el: HTMLElement;\r\n private mapCanvas: HTMLCanvasElement;\r\n private ctx2d: CanvasRenderingContext2D | null;\r\n private rafPending = false;\r\n\r\n /** Minimap dimensions */\r\n width = 180;\r\n height = 120;\r\n\r\n constructor(\r\n private state: CanvasState,\r\n private cards: CardManager,\r\n container: HTMLElement,\r\n ) {\r\n this.el = document.createElement('div');\r\n this.el.className = 'gd-minimap';\r\n this.el.style.cssText = `\r\n position: absolute;\r\n bottom: 12px;\r\n right: 12px;\r\n width: ${this.width}px;\r\n height: ${this.height}px;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n backdrop-filter: blur(12px);\r\n background: rgba(0, 0, 0, 0.5);\r\n border: 1px solid rgba(255, 255, 255, 0.1);\r\n cursor: pointer;\r\n z-index: 999;\r\n `;\r\n\r\n this.mapCanvas = document.createElement('canvas');\r\n this.mapCanvas.width = this.width;\r\n this.mapCanvas.height = this.height;\r\n this.el.appendChild(this.mapCanvas);\r\n container.appendChild(this.el);\r\n\r\n this.ctx2d = this.mapCanvas.getContext('2d');\r\n\r\n // Click to navigate\r\n this.el.addEventListener('mousedown', (e) => this.handleClick(e));\r\n\r\n // Auto-rebuild on state change\r\n this.state.subscribe(() => this.scheduleRebuild());\r\n }\r\n\r\n /** Schedule a redraw */\r\n scheduleRebuild() {\r\n if (this.rafPending) return;\r\n this.rafPending = true;\r\n requestAnimationFrame(() => {\r\n this.rafPending = false;\r\n this.rebuild();\r\n });\r\n }\r\n\r\n /** Force immediate redraw */\r\n rebuild() {\r\n const ctx = this.ctx2d;\r\n if (!ctx) return;\r\n ctx.clearRect(0, 0, this.width, this.height);\r\n\r\n // Compute world bounding box of all cards\r\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\r\n for (const [, card] of this.cards.cards) {\r\n const x = parseFloat(card.style.left) || 0;\r\n const y = parseFloat(card.style.top) || 0;\r\n const w = card.offsetWidth || 400;\r\n const h = card.offsetHeight || 300;\r\n minX = Math.min(minX, x);\r\n minY = Math.min(minY, y);\r\n maxX = Math.max(maxX, x + w);\r\n maxY = Math.max(maxY, y + h);\r\n }\r\n\r\n // Include deferred cards in bounding box\r\n for (const [, data] of this.cards.deferred) {\r\n minX = Math.min(minX, data.x);\r\n minY = Math.min(minY, data.y);\r\n maxX = Math.max(maxX, data.x + data.width);\r\n maxY = Math.max(maxY, data.y + data.height);\r\n }\r\n\r\n if (minX === Infinity) return; // no cards\r\n\r\n const pad = 50;\r\n const worldW = maxX - minX + pad * 2;\r\n const worldH = maxY - minY + pad * 2;\r\n const scale = Math.min(this.width / worldW, this.height / worldH);\r\n\r\n const ox = (this.width - worldW * scale) / 2;\r\n const oy = (this.height - worldH * scale) / 2;\r\n\r\n // Draw card dots\r\n ctx.fillStyle = 'rgba(147, 130, 255, 0.6)';\r\n for (const [, card] of this.cards.cards) {\r\n const x = (parseFloat(card.style.left) || 0) - minX + pad;\r\n const y = (parseFloat(card.style.top) || 0) - minY + pad;\r\n const w = card.offsetWidth || 400;\r\n const h = card.offsetHeight || 300;\r\n ctx.fillRect(ox + x * scale, oy + y * scale, Math.max(2, w * scale), Math.max(2, h * scale));\r\n }\r\n\r\n // Draw deferred card dots (dimmer)\r\n ctx.fillStyle = 'rgba(147, 130, 255, 0.2)';\r\n for (const [, data] of this.cards.deferred) {\r\n const x = data.x - minX + pad;\r\n const y = data.y - minY + pad;\r\n ctx.fillRect(ox + x * scale, oy + y * scale, Math.max(2, data.width * scale), Math.max(2, data.height * scale));\r\n }\r\n\r\n // Draw viewport rect\r\n const vp = this.state.getVisibleWorldRect();\r\n if (vp) {\r\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';\r\n ctx.lineWidth = 1.5;\r\n const rx = ox + (vp.left - minX + pad) * scale;\r\n const ry = oy + (vp.top - minY + pad) * scale;\r\n const rw = vp.width * scale;\r\n const rh = vp.height * scale;\r\n ctx.strokeRect(rx, ry, rw, rh);\r\n }\r\n }\r\n\r\n private handleClick(e: MouseEvent) {\r\n e.stopPropagation();\r\n e.preventDefault();\r\n\r\n // Compute the same bounding box used in rebuild()\r\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\r\n for (const [, card] of this.cards.cards) {\r\n const x = parseFloat(card.style.left) || 0;\r\n const y = parseFloat(card.style.top) || 0;\r\n const w = card.offsetWidth || 400;\r\n const h = card.offsetHeight || 300;\r\n minX = Math.min(minX, x);\r\n minY = Math.min(minY, y);\r\n maxX = Math.max(maxX, x + w);\r\n maxY = Math.max(maxY, y + h);\r\n }\r\n for (const [, data] of this.cards.deferred) {\r\n minX = Math.min(minX, data.x);\r\n minY = Math.min(minY, data.y);\r\n maxX = Math.max(maxX, data.x + data.width);\r\n maxY = Math.max(maxY, data.y + data.height);\r\n }\r\n if (minX === Infinity) return;\r\n\r\n const pad = 50;\r\n const worldW = maxX - minX + pad * 2;\r\n const worldH = maxY - minY + pad * 2;\r\n const scale = Math.min(this.width / worldW, this.height / worldH);\r\n const ox = (this.width - worldW * scale) / 2;\r\n const oy = (this.height - worldH * scale) / 2;\r\n\r\n // Convert minimap click → world coordinates\r\n const rect = this.el.getBoundingClientRect();\r\n const clickX = e.clientX - rect.left;\r\n const clickY = e.clientY - rect.top;\r\n\r\n const worldX = (clickX - ox) / scale + minX - pad;\r\n const worldY = (clickY - oy) / scale + minY - pad;\r\n\r\n // Center the viewport on this world position\r\n const vp = this.state.getVisibleWorldRect();\r\n if (vp) {\r\n const vpWorldW = vp.width;\r\n const vpWorldH = vp.height;\r\n const newOffsetX = -(worldX - vpWorldW / 2) * this.state.zoom;\r\n const newOffsetY = -(worldY - vpWorldH / 2) * this.state.zoom;\r\n this.state.set(this.state.zoom, newOffsetX, newOffsetY);\r\n }\r\n\r\n // Support drag on the minimap\r\n const onMove = (ev: MouseEvent) => {\r\n const mx = ev.clientX - rect.left;\r\n const my = ev.clientY - rect.top;\r\n const wx = (mx - ox) / scale + minX - pad;\r\n const wy = (my - oy) / scale + minY - pad;\r\n const v = this.state.getVisibleWorldRect();\r\n if (v) {\r\n this.state.set(\r\n this.state.zoom,\r\n -(wx - v.width / 2) * this.state.zoom,\r\n -(wy - v.height / 2) * this.state.zoom,\r\n );\r\n }\r\n };\r\n const onUp = () => {\r\n window.removeEventListener('mousemove', onMove);\r\n window.removeEventListener('mouseup', onUp);\r\n };\r\n window.addEventListener('mousemove', onMove);\r\n window.addEventListener('mouseup', onUp);\r\n }\r\n\r\n /** Show/hide the minimap */\r\n setVisible(visible: boolean) {\r\n this.el.style.display = visible ? '' : 'none';\r\n }\r\n\r\n destroy() {\r\n this.el.remove();\r\n }\r\n}\r\n"
|
|
12
|
-
],
|
|
13
|
-
"mappings": "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",
|
|
14
|
-
"debugId": "A4ED09BD658332D164756E2164756E21",
|
|
15
|
-
"names": []
|
|
16
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "galaxydraw",
|
|
3
|
-
"version": "0.2.0",
|
|
4
|
-
"description": "Infinite canvas framework for spatial applications. Zero dependencies, ~31KB.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "src/index.ts",
|
|
7
|
-
"types": "src/index.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"bun": "./src/index.ts",
|
|
11
|
-
"import": "./dist/index.js",
|
|
12
|
-
"types": "./src/index.ts"
|
|
13
|
-
},
|
|
14
|
-
"./css": "./src/galaxydraw.css"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"dist",
|
|
18
|
-
"src"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "bun build src/index.ts --outdir dist --format esm --sourcemap=external",
|
|
22
|
-
"dev": "bun run --watch demo/server.ts",
|
|
23
|
-
"demo:build": "bun run demo/build-static.ts",
|
|
24
|
-
"prepublishOnly": "bun run build"
|
|
25
|
-
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"canvas",
|
|
28
|
-
"infinite-canvas",
|
|
29
|
-
"spatial",
|
|
30
|
-
"pan",
|
|
31
|
-
"zoom",
|
|
32
|
-
"drag",
|
|
33
|
-
"cards",
|
|
34
|
-
"dashboard",
|
|
35
|
-
"visualization",
|
|
36
|
-
"touch",
|
|
37
|
-
"pinch-to-zoom"
|
|
38
|
-
],
|
|
39
|
-
"repository": {
|
|
40
|
-
"type": "git",
|
|
41
|
-
"url": "https://github.com/7flash/git-on-canvas",
|
|
42
|
-
"directory": "packages/galaxydraw"
|
|
43
|
-
},
|
|
44
|
-
"license": "MIT",
|
|
45
|
-
"author": "7flash",
|
|
46
|
-
"devDependencies": {
|
|
47
|
-
"typescript": "^5.4.0"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GalaxyDraw Performance Benchmarks
|
|
3
|
-
*
|
|
4
|
-
* Pure logic profiling — no DOM required.
|
|
5
|
-
* Measures the critical code paths for large repositories (1K–10K cards):
|
|
6
|
-
* - Card defer (bulk Map registration)
|
|
7
|
-
* - materializeInRect (viewport intersection scan)
|
|
8
|
-
* - CanvasState coordinate conversions at scale
|
|
9
|
-
* - getVisibleWorldRect + fitRect computations
|
|
10
|
-
*
|
|
11
|
-
* Run: bun test packages/galaxydraw/perf.test.ts
|
|
12
|
-
*/
|
|
13
|
-
import { describe, expect, test } from 'bun:test';
|
|
14
|
-
import { CanvasState } from './src/core/state';
|
|
15
|
-
import { CardManager } from './src/core/cards';
|
|
16
|
-
import { EventBus } from './src/core/events';
|
|
17
|
-
import type { CardPlugin, CardData } from './src/core/cards';
|
|
18
|
-
|
|
19
|
-
// ── Synthetic file positions (grid layout like GitMaps) ──
|
|
20
|
-
function generateFileGrid(count: number, colWidth = 600, rowHeight = 720, gap = 20) {
|
|
21
|
-
const cols = Math.ceil(Math.sqrt(count));
|
|
22
|
-
return Array.from({ length: count }, (_, i) => ({
|
|
23
|
-
id: `file-${i}`,
|
|
24
|
-
x: (i % cols) * (colWidth + gap),
|
|
25
|
-
y: Math.floor(i / cols) * (rowHeight + gap),
|
|
26
|
-
width: colWidth,
|
|
27
|
-
height: rowHeight,
|
|
28
|
-
}));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ── Timing utility ────────────────────────────────────────
|
|
32
|
-
function bench(label: string, fn: () => void, iterations = 5): number {
|
|
33
|
-
// Dry run (warm up JIT)
|
|
34
|
-
fn();
|
|
35
|
-
const start = performance.now();
|
|
36
|
-
for (let i = 0; i < iterations; i++) fn();
|
|
37
|
-
const avg = (performance.now() - start) / iterations;
|
|
38
|
-
console.log(` ⏱ ${label}: ${avg.toFixed(3)}ms`);
|
|
39
|
-
return avg;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ── AABB intersection (same algorithm as materializeInRect) ──
|
|
43
|
-
function intersectsViewport(
|
|
44
|
-
cardX: number, cardY: number, cardW: number, cardH: number,
|
|
45
|
-
vLeft: number, vTop: number, vRight: number, vBottom: number,
|
|
46
|
-
): boolean {
|
|
47
|
-
return cardX + cardW > vLeft && cardX < vRight && cardY + cardH > vTop && cardY < vBottom;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ──────────────────────────────────────────────────────────
|
|
51
|
-
describe('Performance: CanvasState', () => {
|
|
52
|
-
test('screenToWorld × 100K', () => {
|
|
53
|
-
const state = new CanvasState();
|
|
54
|
-
state.zoom = 0.35;
|
|
55
|
-
state.offsetX = -2400;
|
|
56
|
-
state.offsetY = -1800;
|
|
57
|
-
|
|
58
|
-
const OPS = 100_000;
|
|
59
|
-
const t = bench(`screenToWorld × ${OPS.toLocaleString()}`, () => {
|
|
60
|
-
for (let i = 0; i < OPS; i++) {
|
|
61
|
-
state.screenToWorld(i * 0.02, i * 0.01);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
expect(t).toBeLessThan(50);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('worldToScreen × 100K', () => {
|
|
68
|
-
const state = new CanvasState();
|
|
69
|
-
state.set(0.25, -5000, -3000);
|
|
70
|
-
|
|
71
|
-
const OPS = 100_000;
|
|
72
|
-
const t = bench(`worldToScreen × ${OPS.toLocaleString()}`, () => {
|
|
73
|
-
for (let i = 0; i < OPS; i++) {
|
|
74
|
-
state.worldToScreen(i * 10, i * 7);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
expect(t).toBeLessThan(50);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('pan() × 100K', () => {
|
|
81
|
-
const state = new CanvasState();
|
|
82
|
-
const OPS = 100_000;
|
|
83
|
-
const t = bench(`pan() × ${OPS.toLocaleString()}`, () => {
|
|
84
|
-
for (let i = 0; i < OPS; i++) {
|
|
85
|
-
state.pan(1, 1);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
expect(t).toBeLessThan(50);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('set() with subscriber × 100K', () => {
|
|
92
|
-
const state = new CanvasState();
|
|
93
|
-
let notified = 0;
|
|
94
|
-
state.subscribe(() => { notified++; });
|
|
95
|
-
|
|
96
|
-
const OPS = 100_000;
|
|
97
|
-
const t = bench(`set() + subscriber × ${OPS.toLocaleString()}`, () => {
|
|
98
|
-
for (let i = 0; i < OPS; i++) {
|
|
99
|
-
state.set(1 + (i % 100) * 0.01, i, i >> 1);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
expect(t).toBeLessThan(50);
|
|
103
|
-
expect(notified).toBeGreaterThan(0);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('fitRect × 10K', () => {
|
|
107
|
-
const state = new CanvasState();
|
|
108
|
-
const OPS = 10_000;
|
|
109
|
-
const t = bench(`fitRect() × ${OPS.toLocaleString()}`, () => {
|
|
110
|
-
for (let i = 0; i < OPS; i++) {
|
|
111
|
-
state.fitRect(0, 0, 1000 + i, 800 + i, 60);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
expect(t).toBeLessThan(50);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// ──────────────────────────────────────────────────────────
|
|
119
|
-
describe('Performance: Viewport intersection scan', () => {
|
|
120
|
-
// This exercises the exact same AABB logic used by materializeInRect
|
|
121
|
-
// and ViewportCuller.perform — but without DOM.
|
|
122
|
-
|
|
123
|
-
const SIZES = [100, 500, 1_000, 5_000, 10_000];
|
|
124
|
-
|
|
125
|
-
for (const count of SIZES) {
|
|
126
|
-
test(`scan ${count.toLocaleString()} cards against viewport`, () => {
|
|
127
|
-
const files = generateFileGrid(count);
|
|
128
|
-
|
|
129
|
-
// Simulate viewport at origin, ~2 screens wide
|
|
130
|
-
const vLeft = -500, vTop = -500, vRight = 3000, vBottom = 2000;
|
|
131
|
-
|
|
132
|
-
let visible = 0;
|
|
133
|
-
const t = bench(`AABB scan × ${count.toLocaleString()}`, () => {
|
|
134
|
-
visible = 0;
|
|
135
|
-
for (const f of files) {
|
|
136
|
-
if (intersectsViewport(f.x, f.y, f.width, f.height, vLeft, vTop, vRight, vBottom)) {
|
|
137
|
-
visible++;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
console.log(` → ${visible} visible / ${count} total`);
|
|
143
|
-
expect(visible).toBeGreaterThan(0);
|
|
144
|
-
expect(visible).toBeLessThan(count);
|
|
145
|
-
expect(t).toBeLessThan(count * 0.002); // < 2µs per card
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
test('scan 10K with shifted viewport (deep scroll)', () => {
|
|
150
|
-
const files = generateFileGrid(10_000);
|
|
151
|
-
|
|
152
|
-
// Simulate having scrolled to the middle of a 10K grid
|
|
153
|
-
const cols = Math.ceil(Math.sqrt(10_000)); // ~100
|
|
154
|
-
const midCol = Math.floor(cols / 2);
|
|
155
|
-
const midRow = Math.floor(cols / 2);
|
|
156
|
-
const vLeft = midCol * 620 - 1000;
|
|
157
|
-
const vTop = midRow * 740 - 500;
|
|
158
|
-
const vRight = vLeft + 3000;
|
|
159
|
-
const vBottom = vTop + 2000;
|
|
160
|
-
|
|
161
|
-
let visible = 0;
|
|
162
|
-
const t = bench('AABB scan 10K (mid-scroll)', () => {
|
|
163
|
-
visible = 0;
|
|
164
|
-
for (const f of files) {
|
|
165
|
-
if (intersectsViewport(f.x, f.y, f.width, f.height, vLeft, vTop, vRight, vBottom)) {
|
|
166
|
-
visible++;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
console.log(` → ${visible} visible / 10000 total (mid-scroll)`);
|
|
172
|
-
expect(t).toBeLessThan(20);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// ──────────────────────────────────────────────────────────
|
|
177
|
-
describe('Performance: Map operations (defer simulation)', () => {
|
|
178
|
-
const SIZES = [1_000, 5_000, 10_000];
|
|
179
|
-
|
|
180
|
-
for (const count of SIZES) {
|
|
181
|
-
test(`Map.set + Map.delete cycle × ${count.toLocaleString()}`, () => {
|
|
182
|
-
const files = generateFileGrid(count);
|
|
183
|
-
const deferred = new Map<string, typeof files[0]>();
|
|
184
|
-
|
|
185
|
-
// Defer
|
|
186
|
-
const tDefer = bench(`defer ${count.toLocaleString()} cards`, () => {
|
|
187
|
-
deferred.clear();
|
|
188
|
-
for (const f of files) {
|
|
189
|
-
deferred.set(f.id, f);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
expect(deferred.size).toBe(count);
|
|
194
|
-
expect(tDefer).toBeLessThan(count * 0.01);
|
|
195
|
-
|
|
196
|
-
// Materialize subset (simulates viewport intersection)
|
|
197
|
-
const vLeft = -500, vTop = -500, vRight = 3000, vBottom = 2000;
|
|
198
|
-
const toRemove: string[] = [];
|
|
199
|
-
|
|
200
|
-
const tMaterialize = bench(`scan+remove from ${count.toLocaleString()}`, () => {
|
|
201
|
-
toRemove.length = 0;
|
|
202
|
-
for (const [id, entry] of deferred) {
|
|
203
|
-
if (intersectsViewport(entry.x, entry.y, entry.width, entry.height, vLeft, vTop, vRight, vBottom)) {
|
|
204
|
-
toRemove.push(id);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
for (const id of toRemove) deferred.delete(id);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
console.log(` → removed ${toRemove.length} from deferred (remaining: ${deferred.size})`);
|
|
211
|
-
expect(tMaterialize).toBeLessThan(count * 0.005);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// ──────────────────────────────────────────────────────────
|
|
217
|
-
describe('Performance: End-to-end pipeline summary', () => {
|
|
218
|
-
test('10K file repository — full results', () => {
|
|
219
|
-
const COUNT = 10_000;
|
|
220
|
-
const files = generateFileGrid(COUNT);
|
|
221
|
-
const state = new CanvasState();
|
|
222
|
-
state.set(0.15, -2000, -1500); // Zoomed out, scrolled
|
|
223
|
-
|
|
224
|
-
const timings: Record<string, number> = {};
|
|
225
|
-
const deferred = new Map<string, typeof files[0]>();
|
|
226
|
-
|
|
227
|
-
// Phase 1: Grid generation
|
|
228
|
-
const t0 = performance.now();
|
|
229
|
-
const grid = generateFileGrid(COUNT);
|
|
230
|
-
timings['Grid generation'] = performance.now() - t0;
|
|
231
|
-
|
|
232
|
-
// Phase 2: Defer all
|
|
233
|
-
const t1 = performance.now();
|
|
234
|
-
for (const f of grid) deferred.set(f.id, f);
|
|
235
|
-
timings['Defer to Map'] = performance.now() - t1;
|
|
236
|
-
|
|
237
|
-
// Phase 3: Viewport scan
|
|
238
|
-
const vLeft = -500, vTop = -500, vRight = 4000, vBottom = 3000;
|
|
239
|
-
const visible: typeof files = [];
|
|
240
|
-
const t2 = performance.now();
|
|
241
|
-
for (const [, f] of deferred) {
|
|
242
|
-
if (intersectsViewport(f.x, f.y, f.width, f.height, vLeft, vTop, vRight, vBottom)) {
|
|
243
|
-
visible.push(f);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
timings['Viewport scan'] = performance.now() - t2;
|
|
247
|
-
|
|
248
|
-
// Phase 4: Remove materialized from deferred
|
|
249
|
-
const t3 = performance.now();
|
|
250
|
-
for (const f of visible) deferred.delete(f.id);
|
|
251
|
-
timings['Prune deferred'] = performance.now() - t3;
|
|
252
|
-
|
|
253
|
-
// Phase 5: Simulate 50 scroll-triggered scans
|
|
254
|
-
const t4 = performance.now();
|
|
255
|
-
for (let i = 0; i < 50; i++) {
|
|
256
|
-
const shifted = vLeft + i * 620;
|
|
257
|
-
let cnt = 0;
|
|
258
|
-
for (const [, f] of deferred) {
|
|
259
|
-
if (intersectsViewport(f.x, f.y, f.width, f.height, shifted, vTop, shifted + 3500, vBottom)) {
|
|
260
|
-
cnt++;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
timings['50 scroll scans'] = performance.now() - t4;
|
|
265
|
-
|
|
266
|
-
// Report
|
|
267
|
-
console.log(`\n ┌──────────────────────────────────────────────────┐`);
|
|
268
|
-
console.log(` │ GalaxyDraw 10K Repository Benchmark │`);
|
|
269
|
-
console.log(` ├──────────────────────────────────────────────────┤`);
|
|
270
|
-
for (const [label, ms] of Object.entries(timings)) {
|
|
271
|
-
console.log(` │ ${label.padEnd(20)} ${ms.toFixed(3).padStart(10)}ms │`);
|
|
272
|
-
}
|
|
273
|
-
console.log(` ├──────────────────────────────────────────────────┤`);
|
|
274
|
-
console.log(` │ Visible on load: ${visible.length.toString().padStart(6)} / ${COUNT} │`);
|
|
275
|
-
console.log(` │ Deferred remaining: ${deferred.size.toString().padStart(6)} / ${COUNT} │`);
|
|
276
|
-
console.log(` │ Total pipeline: ${Object.values(timings).reduce((a, b) => a + b).toFixed(3).padStart(10)}ms │`);
|
|
277
|
-
console.log(` └──────────────────────────────────────────────────┘\n`);
|
|
278
|
-
|
|
279
|
-
const total = Object.values(timings).reduce((a, b) => a + b);
|
|
280
|
-
expect(total).toBeLessThan(500); // Full pipeline under 500ms
|
|
281
|
-
expect(visible.length).toBeGreaterThan(0);
|
|
282
|
-
expect(deferred.size).toBeLessThan(COUNT);
|
|
283
|
-
});
|
|
284
|
-
});
|