web based infinite canvas
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add click to add fallback for stencil palette

+70 -31
+6 -6
TODO.txt
··· 135 135 -------------------------------------------------------------------------------- 136 136 137 137 /packages/core/src/stencils: 138 - [ ] Define Stencil: 138 + [x] Define Stencil: 139 139 - id, name, category, tags[] 140 140 - preview: { kind: 'svg'|'canvas', data } 141 141 - spawn: function (atPoint, scale) -> ShapeRecords[] (group) 142 142 (A stencil can insert 1 shape or a grouped set.) 143 143 144 - [ ] Create initial categories: 144 + [x] Create initial categories: 145 145 - Flowchart: process, decision, terminator, data, document 146 146 - Diagrams: server, db, queue, user, browser, mobile 147 147 - UI: button, input, card, modal ··· 154 154 -------------------------------------------------------------------------------- 155 155 156 156 /apps/web: 157 - [ ] Stencils drawer/palette: 157 + [x] Stencils drawer/palette: 158 158 - search (name + tags) 159 159 - category filter 160 160 - click inserts at viewport center OR ··· 162 162 [ ] Placement rules: 163 163 - insert into active layer (if layers exist) 164 164 - snap to grid if enabled 165 - [ ] Toolbar 165 + [x] Toolbar 166 166 - Open menu to insert stencils with button to open drawer 167 - [ ] Drawer 167 + [x] Drawer 168 168 - Stencil selection with search, category filter, and thumbnail previews 169 169 170 170 (DoD): Inserting stencils is faster than drawing shapes manually. ··· 185 185 S4. Preview rendering 186 186 -------------------------------------------------------------------------------- 187 187 188 - [ ] Render stencil previews in the panel: 188 + [x] Render stencil previews in the panel: 189 189 - small SVG thumbnails (best) & draw to offscreen canvas 190 190 191 191 (DoD): Users can recognize stencils instantly.
+24 -3
apps/web/src/lib/canvas/Canvas.svelte
··· 6 6 import StencilPalette from '$lib/components/StencilPalette.svelte'; 7 7 import { createCanvasController } from './canvas-store.svelte.ts'; 8 8 import { draggingStencil, endDrag } from '$lib/dnd.svelte'; 9 - import { Camera } from 'inkfinite-core'; 9 + import { Camera, stencils } from 'inkfinite-core'; 10 10 11 11 let canvasEl = $state<HTMLCanvasElement | null>(null); 12 12 let textEditorEl = $state<HTMLTextAreaElement | null>(null); ··· 48 48 }); 49 49 50 50 function handleDrop(e: DragEvent) { 51 + console.log('[Canvas] Drop event detected', { 52 + clientX: e.clientX, 53 + clientY: e.clientY, 54 + dataTransferTypes: e.dataTransfer?.types 55 + }); 51 56 e.preventDefault(); 52 57 const stencil = draggingStencil.current; 53 - if (!stencil || !canvasEl) return; 58 + console.log('[Canvas] Dragging stencil state:', stencil); 59 + 60 + if (!stencil || !canvasEl) { 61 + console.warn('[Canvas] Drop ignored - missing stencil or canvas ref'); 62 + return; 63 + } 54 64 55 65 const rect = canvasEl.getBoundingClientRect(); 56 66 const screen = { x: e.clientX - rect.left, y: e.clientY - rect.top }; 57 67 const viewport = c.getViewport(); 58 68 const world = Camera.screenToWorld(c.store.getState().camera, screen, viewport); 59 69 70 + console.log('[Canvas] Inserting stencil at:', world); 60 71 c.insertStencil(stencil, world); 61 72 endDrag(); 62 73 } 63 74 64 75 function handleStencilsClick() { 65 76 c.stencilPaletteOpen = !c.stencilPaletteOpen; 77 + } 78 + 79 + // TODO: close palette on click? Users might want to add multiple. 80 + function handleInsertStencilAtCenter(stencil: stencils.Stencil) { 81 + console.log('[Canvas] Click insert stencil:', stencil.id); 82 + const viewport = c.getViewport(); 83 + const screen = { x: viewport.width / 2, y: viewport.height / 2 }; 84 + const world = Camera.screenToWorld(c.store.getState().camera, screen, viewport); 85 + c.insertStencil(stencil, world); 66 86 } 67 87 </script> 68 88 ··· 191 211 {/if} 192 212 <StencilPalette 193 213 bind:open={c.stencilPaletteOpen} 194 - onClose={() => (c.stencilPaletteOpen = false)} /> 214 + onClose={() => (c.stencilPaletteOpen = false)} 215 + onStencilClick={handleInsertStencilAtCenter} /> 195 216 </div> 196 217 197 218 <style>
+27 -10
apps/web/src/lib/components/StencilPalette.svelte
··· 8 8 type Stencil = stencils.Stencil; 9 9 const { registry, registerBuiltinStencils } = stencils; 10 10 11 - let { open = $bindable(false), onClose }: { open: boolean; onClose: () => void } = $props(); 11 + let { 12 + open = $bindable(false), 13 + onClose, 14 + onStencilClick 15 + }: { 16 + open: boolean; 17 + onClose: () => void; 18 + onStencilClick?: (stencil: Stencil) => void; 19 + } = $props(); 12 20 13 21 let categories = $state([] as string[]); 14 22 let stencilsByCategory = $state({} as Record<string, Stencil[]>); ··· 42 50 } 43 51 44 52 function onDragStart(e: DragEvent, stencil: Stencil) { 53 + console.log('[StencilPalette] Drag started for stencil:', stencil.id); 45 54 if (e.dataTransfer) { 46 55 e.dataTransfer.effectAllowed = 'copy'; 56 + e.dataTransfer.setData('application/x-inkfinite-stencil', stencil.id); 47 57 } 48 58 startDrag(stencil); 49 59 } ··· 108 118 draggable="true" 109 119 ondragstart={(e) => onDragStart(e, stencil)} 110 120 ondragend={endDrag} 121 + onclick={() => onStencilClick?.(stencil)} 122 + onkeydown={(e) => { 123 + if (e.key === 'Enter' || e.key === ' ') { 124 + e.preventDefault(); 125 + onStencilClick?.(stencil); 126 + } 127 + }} 111 128 class="palette__item" 112 129 title={stencil.name}> 113 130 <div class="palette__item-preview"> ··· 318 335 transform-origin: center; 319 336 pointer-events: none; 320 337 color: var(--text, #333); 321 - width: 100%; 322 - height: 100%; 323 - display: flex; 324 - align-items: center; 325 - justify-content: center; 338 + width: 100%; 339 + height: 100%; 340 + display: flex; 341 + align-items: center; 342 + justify-content: center; 326 343 } 327 344 328 - .palette__item-preview-content :global(svg) { 329 - width: 100%; 330 - height: 100%; 331 - } 345 + .palette__item-preview-content :global(svg) { 346 + width: 100%; 347 + height: 100%; 348 + } 332 349 333 350 .palette__item-name { 334 351 font-size: 0.75rem;
+13 -12
apps/web/src/lib/components/Toolbar.svelte
··· 558 558 <span class="toolbar__tool-icon">{tool.icon}</span> 559 559 <span class="toolbar__tool-label">{tool.label}</span> 560 560 </button> 561 + {#if tool.id === 'select' && onStencilsClick} 562 + <button 563 + class="toolbar__tool-button tool-button" 564 + onclick={onStencilsClick} 565 + aria-label="Stencils" 566 + title="Stencils"> 567 + <span class="toolbar__tool-icon"> 568 + <Icon name="grid-dots" size={18} /> 569 + </span> 570 + <span class="toolbar__tool-label">Stencils</span> 571 + </button> 572 + <div class="toolbar__divider"></div> 573 + {/if} 561 574 {/each} 562 575 563 576 {#if showColorControls} ··· 705 718 aria-pressed="false"> 706 719 <span class="toolbar__tool-icon">⏱</span> 707 720 <span class="toolbar__tool-label">History</span> 708 - </button> 709 - {/if} 710 - {#if onStencilsClick} 711 - <button 712 - class="toolbar__tool-button tool-button" 713 - onclick={onStencilsClick} 714 - aria-label="Stencils" 715 - title="Stencils"> 716 - <span class="toolbar__tool-icon"> 717 - <Icon name="grid-dots" size={18} /> 718 - </span> 719 - <span class="toolbar__tool-label">Stencils</span> 720 721 </button> 721 722 {/if} 722 723 </div>