vg-coder-cli 2.0.44 → 2.0.46

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.
@@ -0,0 +1,494 @@
1
+ /**
2
+ * Mermaid Viewer - Fullscreen modal with zoom, pan, and scale controls
3
+ * Inspired by mermaid.live interface
4
+ */
5
+
6
+ // State for pan and zoom
7
+ let viewerState = {
8
+ scale: 1,
9
+ translateX: 0,
10
+ translateY: 0,
11
+ isDragging: false,
12
+ startX: 0,
13
+ startY: 0
14
+ };
15
+
16
+ // Constants
17
+ const MIN_SCALE = 0.1;
18
+ const MAX_SCALE = 15;
19
+ const SCALE_STEP = 0.25;
20
+ const Z_INDEX = '9999999999';
21
+
22
+ /**
23
+ * Create mermaid toolbar buttons for diagram wrapper
24
+ */
25
+ export function createMermaidToolbar(code, svg) {
26
+ const toolbar = document.createElement('div');
27
+ toolbar.className = 'mermaid-toolbar';
28
+ Object.assign(toolbar.style, {
29
+ display: 'flex',
30
+ justifyContent: 'flex-end',
31
+ gap: '4px',
32
+ padding: '8px 12px',
33
+ background: 'rgba(0, 0, 0, 0.3)',
34
+ borderBottom: '1px solid rgba(255, 255, 255, 0.1)'
35
+ });
36
+
37
+ toolbar.innerHTML = `
38
+ <button class="mermaid-toolbar-btn" data-action="copy" title="Copy Mermaid Code" style="${getButtonStyle()}">
39
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
40
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
41
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
42
+ </svg>
43
+ </button>
44
+ <button class="mermaid-toolbar-btn" data-action="fullscreen" title="Open Fullscreen Viewer" style="${getButtonStyle()}">
45
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
46
+ <polyline points="15 3 21 3 21 9"></polyline>
47
+ <polyline points="9 21 3 21 3 15"></polyline>
48
+ <line x1="21" y1="3" x2="14" y2="10"></line>
49
+ <line x1="3" y1="21" x2="10" y2="14"></line>
50
+ </svg>
51
+ </button>
52
+ `;
53
+
54
+ // Copy button handler
55
+ toolbar.querySelector('[data-action="copy"]').addEventListener('click', async (e) => {
56
+ e.stopPropagation();
57
+ try {
58
+ await navigator.clipboard.writeText(code);
59
+ showToast(toolbar.parentElement, 'Copied!');
60
+ } catch (err) {
61
+ console.error('[MermaidViewer] Copy failed:', err);
62
+ showToast(toolbar.parentElement, 'Copy failed', true);
63
+ }
64
+ });
65
+
66
+ // Fullscreen button handler
67
+ toolbar.querySelector('[data-action="fullscreen"]').addEventListener('click', (e) => {
68
+ e.stopPropagation();
69
+ openMermaidViewer(svg, code);
70
+ });
71
+
72
+ return toolbar;
73
+ }
74
+
75
+ /**
76
+ * Open fullscreen mermaid viewer with zoom/pan controls
77
+ */
78
+ export function openMermaidViewer(svg, code) {
79
+ // Remove any existing modal
80
+ const existingModal = document.querySelector('.mermaid-viewer-modal');
81
+ if (existingModal) {
82
+ existingModal.remove();
83
+ }
84
+
85
+ // Reset state
86
+ viewerState = {
87
+ scale: 1,
88
+ translateX: 0,
89
+ translateY: 0,
90
+ isDragging: false,
91
+ startX: 0,
92
+ startY: 0
93
+ };
94
+
95
+ // Create modal
96
+ const modal = document.createElement('div');
97
+ modal.className = 'mermaid-viewer-modal';
98
+ Object.assign(modal.style, {
99
+ position: 'fixed',
100
+ top: '0',
101
+ left: '0',
102
+ right: '0',
103
+ bottom: '0',
104
+ width: '100vw',
105
+ height: '100vh',
106
+ background: 'rgba(10, 10, 15, 0.95)',
107
+ zIndex: Z_INDEX,
108
+ display: 'flex',
109
+ flexDirection: 'column',
110
+ opacity: '0',
111
+ transition: 'opacity 0.2s ease'
112
+ });
113
+
114
+ modal.innerHTML = `
115
+ <div class="mermaid-viewer-header" style="${getHeaderStyle()}">
116
+ <div class="mermaid-viewer-title" style="display: flex; align-items: center; gap: 12px;">
117
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#a855f7" stroke-width="2">
118
+ <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
119
+ <polyline points="2 17 12 22 22 17"></polyline>
120
+ <polyline points="2 12 12 17 22 12"></polyline>
121
+ </svg>
122
+ <span style="font-size: 14px; font-weight: 600; color: #ededed;">Mermaid Diagram Viewer</span>
123
+ </div>
124
+ <div class="mermaid-viewer-controls" style="display: flex; align-items: center; gap: 8px;">
125
+ <div class="mermaid-viewer-zoom-controls" style="display: flex; align-items: center; gap: 4px; background: rgba(255,255,255,0.05); border-radius: 6px; padding: 4px;">
126
+ <button data-action="zoom-out" title="Zoom Out" style="${getControlButtonStyle()}">
127
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
128
+ <circle cx="11" cy="11" r="8"></circle>
129
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
130
+ <line x1="8" y1="11" x2="14" y2="11"></line>
131
+ </svg>
132
+ </button>
133
+ <span class="zoom-level" style="min-width: 50px; text-align: center; font-size: 12px; color: #a1a1aa;">100%</span>
134
+ <button data-action="zoom-in" title="Zoom In" style="${getControlButtonStyle()}">
135
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
136
+ <circle cx="11" cy="11" r="8"></circle>
137
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
138
+ <line x1="11" y1="8" x2="11" y2="14"></line>
139
+ <line x1="8" y1="11" x2="14" y2="11"></line>
140
+ </svg>
141
+ </button>
142
+ </div>
143
+ <button data-action="fit" title="Fit to Screen" style="${getControlButtonStyle()}">
144
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
145
+ <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path>
146
+ </svg>
147
+ </button>
148
+ <button data-action="reset" title="Reset View" style="${getControlButtonStyle()}">
149
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
150
+ <polyline points="1 4 1 10 7 10"></polyline>
151
+ <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
152
+ </svg>
153
+ </button>
154
+ <div style="width: 1px; height: 24px; background: rgba(255,255,255,0.1); margin: 0 8px;"></div>
155
+ <button data-action="copy" title="Copy Code" style="${getControlButtonStyle()}">
156
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
157
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
158
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
159
+ </svg>
160
+ </button>
161
+ <button data-action="close" title="Close (Esc)" style="${getCloseButtonStyle()}">
162
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
163
+ <line x1="18" y1="6" x2="6" y2="18"></line>
164
+ <line x1="6" y1="6" x2="18" y2="18"></line>
165
+ </svg>
166
+ </button>
167
+ </div>
168
+ </div>
169
+ <div class="mermaid-viewer-canvas" style="${getCanvasStyle()}">
170
+ <div class="mermaid-viewer-content" style="${getContentStyle()}">
171
+ ${svg}
172
+ </div>
173
+ </div>
174
+ <div class="mermaid-viewer-footer" style="${getFooterStyle()}">
175
+ <span style="color: #71717a; font-size: 11px;">
176
+ 🖱️ Scroll to zoom • Drag to pan • Double-click to reset
177
+ </span>
178
+ </div>
179
+ `;
180
+
181
+ document.body.appendChild(modal);
182
+
183
+ // Get elements
184
+ const canvas = modal.querySelector('.mermaid-viewer-canvas');
185
+ const content = modal.querySelector('.mermaid-viewer-content');
186
+ const zoomLevel = modal.querySelector('.zoom-level');
187
+
188
+ // Update transform
189
+ const updateTransform = () => {
190
+ content.style.transform = `translate(${viewerState.translateX}px, ${viewerState.translateY}px) scale(${viewerState.scale})`;
191
+ zoomLevel.textContent = `${Math.round(viewerState.scale * 100)}%`;
192
+ };
193
+
194
+ // Zoom function
195
+ const zoom = (delta, centerX = null, centerY = null) => {
196
+ const oldScale = viewerState.scale;
197
+ const newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, viewerState.scale + delta));
198
+
199
+ if (centerX !== null && centerY !== null) {
200
+ // Zoom towards cursor position
201
+ const rect = canvas.getBoundingClientRect();
202
+ const x = centerX - rect.left - rect.width / 2;
203
+ const y = centerY - rect.top - rect.height / 2;
204
+
205
+ const scaleRatio = newScale / oldScale;
206
+ viewerState.translateX = x - (x - viewerState.translateX) * scaleRatio;
207
+ viewerState.translateY = y - (y - viewerState.translateY) * scaleRatio;
208
+ }
209
+
210
+ viewerState.scale = newScale;
211
+ updateTransform();
212
+ };
213
+
214
+ // Fit to screen
215
+ const fitToScreen = () => {
216
+ const svgEl = content.querySelector('svg');
217
+ if (!svgEl) return;
218
+
219
+ const canvasRect = canvas.getBoundingClientRect();
220
+ const svgRect = svgEl.getBoundingClientRect();
221
+
222
+ // Calculate scale to fit with padding
223
+ const padding = 80;
224
+ const scaleX = (canvasRect.width - padding) / (svgRect.width / viewerState.scale);
225
+ const scaleY = (canvasRect.height - padding) / (svgRect.height / viewerState.scale);
226
+
227
+ viewerState.scale = Math.min(scaleX, scaleY, 2); // Max 200% on fit
228
+ viewerState.translateX = 0;
229
+ viewerState.translateY = 0;
230
+ updateTransform();
231
+ };
232
+
233
+ // Reset view
234
+ const resetView = () => {
235
+ viewerState.scale = 1;
236
+ viewerState.translateX = 0;
237
+ viewerState.translateY = 0;
238
+ updateTransform();
239
+ };
240
+
241
+ // Close modal
242
+ const closeModal = () => {
243
+ modal.style.opacity = '0';
244
+ setTimeout(() => modal.remove(), 200);
245
+ document.removeEventListener('keydown', keyHandler);
246
+ };
247
+
248
+ // Key handler
249
+ const keyHandler = (e) => {
250
+ if (e.key === 'Escape') closeModal();
251
+ if (e.key === '+' || e.key === '=') zoom(SCALE_STEP);
252
+ if (e.key === '-') zoom(-SCALE_STEP);
253
+ if (e.key === '0') resetView();
254
+ if (e.key === 'f') fitToScreen();
255
+ };
256
+ document.addEventListener('keydown', keyHandler);
257
+
258
+ // Wheel zoom
259
+ canvas.addEventListener('wheel', (e) => {
260
+ e.preventDefault();
261
+ const delta = e.deltaY > 0 ? -SCALE_STEP : SCALE_STEP;
262
+ zoom(delta, e.clientX, e.clientY);
263
+ }, { passive: false });
264
+
265
+ // Pan with mouse drag
266
+ canvas.addEventListener('mousedown', (e) => {
267
+ if (e.target.closest('button')) return;
268
+ viewerState.isDragging = true;
269
+ viewerState.startX = e.clientX - viewerState.translateX;
270
+ viewerState.startY = e.clientY - viewerState.translateY;
271
+ canvas.style.cursor = 'grabbing';
272
+ });
273
+
274
+ canvas.addEventListener('mousemove', (e) => {
275
+ if (!viewerState.isDragging) return;
276
+ viewerState.translateX = e.clientX - viewerState.startX;
277
+ viewerState.translateY = e.clientY - viewerState.startY;
278
+ updateTransform();
279
+ });
280
+
281
+ canvas.addEventListener('mouseup', () => {
282
+ viewerState.isDragging = false;
283
+ canvas.style.cursor = 'grab';
284
+ });
285
+
286
+ canvas.addEventListener('mouseleave', () => {
287
+ viewerState.isDragging = false;
288
+ canvas.style.cursor = 'grab';
289
+ });
290
+
291
+ // Double click to reset
292
+ canvas.addEventListener('dblclick', resetView);
293
+
294
+ // Touch support for mobile
295
+ let lastTouchDistance = 0;
296
+ canvas.addEventListener('touchstart', (e) => {
297
+ if (e.touches.length === 2) {
298
+ lastTouchDistance = Math.hypot(
299
+ e.touches[0].clientX - e.touches[1].clientX,
300
+ e.touches[0].clientY - e.touches[1].clientY
301
+ );
302
+ } else if (e.touches.length === 1) {
303
+ viewerState.isDragging = true;
304
+ viewerState.startX = e.touches[0].clientX - viewerState.translateX;
305
+ viewerState.startY = e.touches[0].clientY - viewerState.translateY;
306
+ }
307
+ }, { passive: true });
308
+
309
+ canvas.addEventListener('touchmove', (e) => {
310
+ if (e.touches.length === 2) {
311
+ e.preventDefault();
312
+ const distance = Math.hypot(
313
+ e.touches[0].clientX - e.touches[1].clientX,
314
+ e.touches[0].clientY - e.touches[1].clientY
315
+ );
316
+ const delta = (distance - lastTouchDistance) * 0.01;
317
+ zoom(delta);
318
+ lastTouchDistance = distance;
319
+ } else if (e.touches.length === 1 && viewerState.isDragging) {
320
+ viewerState.translateX = e.touches[0].clientX - viewerState.startX;
321
+ viewerState.translateY = e.touches[0].clientY - viewerState.startY;
322
+ updateTransform();
323
+ }
324
+ }, { passive: false });
325
+
326
+ canvas.addEventListener('touchend', () => {
327
+ viewerState.isDragging = false;
328
+ });
329
+
330
+ // Button handlers
331
+ modal.querySelector('[data-action="zoom-in"]').addEventListener('click', () => zoom(SCALE_STEP));
332
+ modal.querySelector('[data-action="zoom-out"]').addEventListener('click', () => zoom(-SCALE_STEP));
333
+ modal.querySelector('[data-action="fit"]').addEventListener('click', fitToScreen);
334
+ modal.querySelector('[data-action="reset"]').addEventListener('click', resetView);
335
+ modal.querySelector('[data-action="close"]').addEventListener('click', closeModal);
336
+
337
+ // Copy button
338
+ modal.querySelector('[data-action="copy"]').addEventListener('click', async () => {
339
+ try {
340
+ await navigator.clipboard.writeText(code);
341
+ const btn = modal.querySelector('[data-action="copy"]');
342
+ btn.innerHTML = `
343
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#4ade80" stroke-width="2">
344
+ <polyline points="20 6 9 17 4 12"></polyline>
345
+ </svg>
346
+ `;
347
+ setTimeout(() => {
348
+ btn.innerHTML = `
349
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
350
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
351
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
352
+ </svg>
353
+ `;
354
+ }, 1500);
355
+ } catch (err) {
356
+ console.error('[MermaidViewer] Copy failed:', err);
357
+ }
358
+ });
359
+
360
+ // Click outside to close
361
+ modal.addEventListener('click', (e) => {
362
+ if (e.target === modal) closeModal();
363
+ });
364
+
365
+ // Animate in
366
+ requestAnimationFrame(() => {
367
+ modal.style.opacity = '1';
368
+ // Auto fit after render
369
+ setTimeout(fitToScreen, 100);
370
+ });
371
+ }
372
+
373
+ /**
374
+ * Show toast notification
375
+ */
376
+ export function showToast(container, message, isError = false) {
377
+ const existingToast = container.querySelector('.mermaid-toast');
378
+ if (existingToast) existingToast.remove();
379
+
380
+ const toast = document.createElement('div');
381
+ toast.className = 'mermaid-toast';
382
+ Object.assign(toast.style, {
383
+ position: 'absolute',
384
+ top: '50%',
385
+ left: '50%',
386
+ transform: 'translate(-50%, -50%)',
387
+ padding: '8px 16px',
388
+ borderRadius: '6px',
389
+ fontSize: '12px',
390
+ fontWeight: '500',
391
+ zIndex: '100',
392
+ background: isError ? 'rgba(239, 68, 68, 0.9)' : 'rgba(74, 222, 128, 0.9)',
393
+ color: isError ? '#fff' : '#000',
394
+ animation: 'mermaid-toast-fade 2s ease-out forwards'
395
+ });
396
+ toast.textContent = message;
397
+ container.appendChild(toast);
398
+
399
+ setTimeout(() => toast.remove(), 2000);
400
+ }
401
+
402
+ // Style helper functions
403
+ function getButtonStyle() {
404
+ return `
405
+ display: inline-flex;
406
+ align-items: center;
407
+ gap: 4px;
408
+ padding: 4px 8px;
409
+ background: rgba(255, 255, 255, 0.1);
410
+ border: none;
411
+ border-radius: 4px;
412
+ color: #a1a1aa;
413
+ cursor: pointer;
414
+ font-size: 11px;
415
+ transition: all 0.2s ease;
416
+ `.replace(/\s+/g, ' ').trim();
417
+ }
418
+
419
+ function getHeaderStyle() {
420
+ return `
421
+ display: flex;
422
+ justify-content: space-between;
423
+ align-items: center;
424
+ padding: 12px 20px;
425
+ background: rgba(20, 20, 25, 0.95);
426
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
427
+ flex-shrink: 0;
428
+ `.replace(/\s+/g, ' ').trim();
429
+ }
430
+
431
+ function getControlButtonStyle() {
432
+ return `
433
+ display: flex;
434
+ align-items: center;
435
+ justify-content: center;
436
+ width: 32px;
437
+ height: 32px;
438
+ background: transparent;
439
+ border: none;
440
+ border-radius: 6px;
441
+ color: #a1a1aa;
442
+ cursor: pointer;
443
+ transition: all 0.15s ease;
444
+ `.replace(/\s+/g, ' ').trim();
445
+ }
446
+
447
+ function getCloseButtonStyle() {
448
+ return `
449
+ display: flex;
450
+ align-items: center;
451
+ justify-content: center;
452
+ width: 32px;
453
+ height: 32px;
454
+ background: rgba(239, 68, 68, 0.15);
455
+ border: none;
456
+ border-radius: 6px;
457
+ color: #ef4444;
458
+ cursor: pointer;
459
+ transition: all 0.15s ease;
460
+ `.replace(/\s+/g, ' ').trim();
461
+ }
462
+
463
+ function getCanvasStyle() {
464
+ return `
465
+ flex: 1;
466
+ overflow: hidden;
467
+ background: linear-gradient(135deg, #0f0f14 0%, #1a1a24 100%);
468
+ position: relative;
469
+ cursor: grab;
470
+ `.replace(/\s+/g, ' ').trim();
471
+ }
472
+
473
+ function getContentStyle() {
474
+ return `
475
+ position: absolute;
476
+ top: 50%;
477
+ left: 50%;
478
+ transform-origin: center center;
479
+ transform: translate(-50%, -50%);
480
+ transition: transform 0.1s ease-out;
481
+ `.replace(/\s+/g, ' ').trim();
482
+ }
483
+
484
+ function getFooterStyle() {
485
+ return `
486
+ display: flex;
487
+ justify-content: center;
488
+ align-items: center;
489
+ padding: 8px 20px;
490
+ background: rgba(20, 20, 25, 0.95);
491
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
492
+ flex-shrink: 0;
493
+ `.replace(/\s+/g, ' ').trim();
494
+ }
@@ -123,7 +123,7 @@ function initRightResizeHandler() {
123
123
  const currentX = e.clientX;
124
124
  // For right panel, dragging left (negative diff) should increase width
125
125
  const diffX = startX - currentX; // Inverted direction
126
- const newWidth = Math.max(250, Math.min(600, startWidth + diffX)); // Min 250px, Max 600px
126
+ const newWidth = Math.max(250, Math.min(1200, startWidth + diffX)); // Min 250px, Max 1200px
127
127
 
128
128
  // Update width of tool-panel-container-right
129
129
  toolPanelContainerRight.style.width = `${newWidth}px`;