Reference implementation for the Phoenix Architecture. Work in progress. aicoding.leaflet.pub/
ai coding crazy
1
fork

Configure Feed

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

at main 191 lines 5.1 kB view raw
1export interface ViewportInfo { 2 width: number; 3 height: number; 4 devicePixelRatio: number; 5 isMobile: boolean; 6} 7 8export interface TouchPoint { 9 x: number; 10 y: number; 11 identifier: number; 12} 13 14export interface PointerEvent { 15 x: number; 16 y: number; 17 type: 'start' | 'move' | 'end'; 18 pressure?: number; 19} 20 21export type PointerEventHandler = (event: PointerEvent) => void; 22 23export class ResponsiveCanvas { 24 private canvasId: string; 25 private containerId: string; 26 private scale: number = 1; 27 private offsetX: number = 0; 28 private offsetY: number = 0; 29 private pointerHandlers: Set<PointerEventHandler> = new Set(); 30 private activeTouches: Map<number, TouchPoint> = new Map(); 31 private canvasWidth: number = 800; 32 private canvasHeight: number = 600; 33 private containerWidth: number = 800; 34 private containerHeight: number = 600; 35 36 constructor(canvasId: string, containerId: string) { 37 this.canvasId = canvasId; 38 this.containerId = containerId; 39 this.updateScale(); 40 } 41 42 private updateScale(): void { 43 const scaleX = this.containerWidth / this.canvasWidth; 44 const scaleY = this.containerHeight / this.canvasHeight; 45 this.scale = Math.min(scaleX, scaleY, 1); 46 47 const scaledWidth = this.canvasWidth * this.scale; 48 const scaledHeight = this.canvasHeight * this.scale; 49 this.offsetX = (this.containerWidth - scaledWidth) / 2; 50 this.offsetY = (this.containerHeight - scaledHeight) / 2; 51 } 52 53 private getCanvasCoordinates(clientX: number, clientY: number): { x: number; y: number } { 54 const x = (clientX - this.offsetX) / this.scale; 55 const y = (clientY - this.offsetY) / this.scale; 56 return { x, y }; 57 } 58 59 public handlePointerStart(clientX: number, clientY: number, pressure: number = 1, identifier?: number): void { 60 const coords = this.getCanvasCoordinates(clientX, clientY); 61 62 if (identifier !== undefined) { 63 this.activeTouches.set(identifier, { 64 x: coords.x, 65 y: coords.y, 66 identifier 67 }); 68 } 69 70 this.emitPointerEvent({ 71 x: coords.x, 72 y: coords.y, 73 type: 'start', 74 pressure 75 }); 76 } 77 78 public handlePointerMove(clientX: number, clientY: number, pressure: number = 1, identifier?: number): void { 79 const coords = this.getCanvasCoordinates(clientX, clientY); 80 81 if (identifier !== undefined) { 82 if (this.activeTouches.has(identifier)) { 83 this.activeTouches.set(identifier, { 84 x: coords.x, 85 y: coords.y, 86 identifier 87 }); 88 } else { 89 return; 90 } 91 } 92 93 this.emitPointerEvent({ 94 x: coords.x, 95 y: coords.y, 96 type: 'move', 97 pressure 98 }); 99 } 100 101 public handlePointerEnd(clientX: number, clientY: number, identifier?: number): void { 102 const coords = this.getCanvasCoordinates(clientX, clientY); 103 104 if (identifier !== undefined) { 105 if (this.activeTouches.has(identifier)) { 106 this.activeTouches.delete(identifier); 107 } else { 108 return; 109 } 110 } 111 112 this.emitPointerEvent({ 113 x: coords.x, 114 y: coords.y, 115 type: 'end', 116 pressure: 0 117 }); 118 } 119 120 private emitPointerEvent(event: PointerEvent): void { 121 this.pointerHandlers.forEach(handler => { 122 try { 123 handler(event); 124 } catch (error) { 125 console.error('Error in pointer event handler:', error); 126 } 127 }); 128 } 129 130 public addPointerEventListener(handler: PointerEventHandler): void { 131 this.pointerHandlers.add(handler); 132 } 133 134 public removePointerEventListener(handler: PointerEventHandler): void { 135 this.pointerHandlers.delete(handler); 136 } 137 138 public setCanvasSize(width: number, height: number): void { 139 this.canvasWidth = width; 140 this.canvasHeight = height; 141 this.updateScale(); 142 } 143 144 public setContainerSize(width: number, height: number): void { 145 this.containerWidth = width; 146 this.containerHeight = height; 147 this.updateScale(); 148 } 149 150 public getViewportInfo(): ViewportInfo { 151 return { 152 width: this.containerWidth, 153 height: this.containerHeight, 154 devicePixelRatio: 1, 155 isMobile: this.detectMobile() 156 }; 157 } 158 159 private detectMobile(): boolean { 160 return false; 161 } 162 163 public getScale(): number { 164 return this.scale; 165 } 166 167 public getCanvasHTML(): string { 168 return `<canvas id="${this.canvasId}" width="${this.canvasWidth}" height="${this.canvasHeight}" style="transform: scale(${this.scale}); transform-origin: top left; position: absolute; left: ${this.offsetX}px; top: ${this.offsetY}px;"></canvas>`; 169 } 170 171 public destroy(): void { 172 this.pointerHandlers.clear(); 173 this.activeTouches.clear(); 174 } 175} 176 177export function createResponsiveCanvas(canvasId: string, containerId: string): ResponsiveCanvas { 178 return new ResponsiveCanvas(canvasId, containerId); 179} 180 181export function isMobileDevice(): boolean { 182 return false; 183} 184 185/** @internal Phoenix VCS traceability — do not remove. */ 186export const _phoenix = { 187 iu_id: '34e0a94555fd05dabb716eb8d9f35d4ad180e267ff13ca20cc74e1b3326659db', 188 name: 'Responsiveness', 189 risk_tier: 'low', 190 canon_ids: [2 as const], 191} as const;