cyclecad 3.0.0 → 3.1.0

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.
Files changed (66) hide show
  1. package/BILLING-IMPLEMENTATION-SUMMARY.md +425 -0
  2. package/BILLING-INDEX.md +293 -0
  3. package/BILLING-INTEGRATION-GUIDE.md +414 -0
  4. package/COLLABORATION-INDEX.md +440 -0
  5. package/COLLABORATION-SYSTEM-SUMMARY.md +548 -0
  6. package/DOCKER-BUILD-MANIFEST.txt +483 -0
  7. package/DOCKER-FILES-REFERENCE.md +440 -0
  8. package/DOCKER-INFRASTRUCTURE.md +475 -0
  9. package/DOCKER-README.md +435 -0
  10. package/Dockerfile +33 -55
  11. package/PWA-FILES-CREATED.txt +350 -0
  12. package/QUICK-START-TESTING.md +126 -0
  13. package/STEP-IMPORT-QUICKSTART.md +347 -0
  14. package/STEP-IMPORT-SYSTEM-SUMMARY.md +502 -0
  15. package/app/css/mobile.css +1074 -0
  16. package/app/icons/generate-icons.js +203 -0
  17. package/app/js/billing-ui.js +990 -0
  18. package/app/js/brep-kernel.js +933 -981
  19. package/app/js/collab-client.js +750 -0
  20. package/app/js/mobile-nav.js +623 -0
  21. package/app/js/mobile-toolbar.js +476 -0
  22. package/app/js/modules/billing-module.js +724 -0
  23. package/app/js/modules/step-module-enhanced.js +938 -0
  24. package/app/js/offline-manager.js +705 -0
  25. package/app/js/responsive-init.js +360 -0
  26. package/app/js/touch-handler.js +429 -0
  27. package/app/manifest.json +211 -0
  28. package/app/offline.html +508 -0
  29. package/app/sw.js +571 -0
  30. package/app/tests/billing-tests.html +779 -0
  31. package/app/tests/brep-tests.html +980 -0
  32. package/app/tests/collab-tests.html +743 -0
  33. package/app/tests/mobile-tests.html +1299 -0
  34. package/app/tests/pwa-tests.html +1134 -0
  35. package/app/tests/step-tests.html +1042 -0
  36. package/app/tests/test-agent-v3.html +719 -0
  37. package/docker-compose.yml +225 -0
  38. package/docs/BILLING-HELP.json +260 -0
  39. package/docs/BILLING-README.md +639 -0
  40. package/docs/BILLING-TUTORIAL.md +736 -0
  41. package/docs/BREP-HELP.json +326 -0
  42. package/docs/BREP-TUTORIAL.md +802 -0
  43. package/docs/COLLABORATION-HELP.json +228 -0
  44. package/docs/COLLABORATION-TUTORIAL.md +818 -0
  45. package/docs/DOCKER-HELP.json +224 -0
  46. package/docs/DOCKER-TUTORIAL.md +974 -0
  47. package/docs/MOBILE-HELP.json +243 -0
  48. package/docs/MOBILE-RESPONSIVE-README.md +378 -0
  49. package/docs/MOBILE-TUTORIAL.md +747 -0
  50. package/docs/PWA-HELP.json +228 -0
  51. package/docs/PWA-README.md +662 -0
  52. package/docs/PWA-TUTORIAL.md +757 -0
  53. package/docs/STEP-HELP.json +481 -0
  54. package/docs/STEP-IMPORT-TUTORIAL.md +824 -0
  55. package/docs/TESTING-GUIDE.md +528 -0
  56. package/docs/TESTING-HELP.json +182 -0
  57. package/fusion-vs-cyclecad.html +1771 -0
  58. package/nginx.conf +237 -0
  59. package/package.json +1 -1
  60. package/server/Dockerfile.converter +51 -0
  61. package/server/Dockerfile.signaling +28 -0
  62. package/server/billing-server.js +487 -0
  63. package/server/converter-enhanced.py +528 -0
  64. package/server/requirements-converter.txt +29 -0
  65. package/server/signaling-server.js +801 -0
  66. package/tests/docker-tests.sh +389 -0
@@ -0,0 +1,623 @@
1
+ /**
2
+ * Mobile Navigation Component for cycleCAD
3
+ * Hamburger menu, bottom tabs, breadcrumb, and floating action button
4
+ */
5
+
6
+ class MobileNav {
7
+ constructor(options = {}) {
8
+ this.options = {
9
+ workspaces: [],
10
+ onWorkspaceChange: () => {},
11
+ onMenuOpen: () => {},
12
+ onMenuClose: () => {},
13
+ onFabClick: () => {},
14
+ ...options
15
+ };
16
+
17
+ this.menuOpen = false;
18
+ this.breadcrumb = [];
19
+ this.activeWorkspace = 0;
20
+ this.init();
21
+ }
22
+
23
+ init() {
24
+ this.createHamburgerMenu();
25
+ this.createWorkspaceTabs();
26
+ this.createBreadcrumb();
27
+ this.createFAB();
28
+ this.attachEventListeners();
29
+ }
30
+
31
+ createHamburgerMenu() {
32
+ const hamburger = document.createElement('button');
33
+ hamburger.className = 'hamburger-menu';
34
+ hamburger.id = 'hamburger-menu';
35
+ hamburger.innerHTML = `
36
+ <span>
37
+ <div></div>
38
+ <div></div>
39
+ <div></div>
40
+ </span>
41
+ `;
42
+ hamburger.addEventListener('click', () => this.toggleMenu());
43
+
44
+ const header = document.querySelector('.menu-bar') || document.body;
45
+ if (header === document.body) {
46
+ // Prepend to body if no menu bar exists
47
+ document.body.insertBefore(hamburger, document.body.firstChild);
48
+ } else {
49
+ header.insertBefore(hamburger, header.firstChild);
50
+ }
51
+
52
+ // Create menu overlay
53
+ const menu = document.createElement('div');
54
+ menu.className = 'mobile-menu';
55
+ menu.id = 'mobile-menu';
56
+ menu.innerHTML = `
57
+ <div class="menu-backdrop"></div>
58
+ <nav class="menu-content">
59
+ <div class="menu-header">
60
+ <h2>Menu</h2>
61
+ <button class="menu-close">✕</button>
62
+ </div>
63
+ <ul class="menu-items">
64
+ <li><a href="#sketch">📝 Sketch</a></li>
65
+ <li><a href="#model">🧊 Model</a></li>
66
+ <li><a href="#assembly">🔗 Assembly</a></li>
67
+ <li><a href="#analyze">📊 Analyze</a></li>
68
+ <li><a href="#export">💾 Export</a></li>
69
+ <li><a href="#settings">⚙️ Settings</a></li>
70
+ </ul>
71
+ </nav>
72
+ `;
73
+
74
+ document.body.appendChild(menu);
75
+
76
+ // Close menu handlers
77
+ menu.querySelector('.menu-close').addEventListener('click', () => this.closeMenu());
78
+ menu.querySelector('.menu-backdrop').addEventListener('click', () => this.closeMenu());
79
+
80
+ this.hamburger = hamburger;
81
+ this.menu = menu;
82
+ }
83
+
84
+ createWorkspaceTabs() {
85
+ const tabs = document.createElement('div');
86
+ tabs.className = 'workspace-tabs';
87
+ tabs.id = 'workspace-tabs';
88
+
89
+ this.options.workspaces.forEach((ws, index) => {
90
+ const tab = document.createElement('button');
91
+ tab.className = `workspace-tab ${index === 0 ? 'active' : ''}`;
92
+ tab.dataset.index = index;
93
+ tab.textContent = ws.label || `Workspace ${index + 1}`;
94
+ tab.addEventListener('click', () => this.selectWorkspace(index));
95
+ tabs.appendChild(tab);
96
+ });
97
+
98
+ document.body.insertBefore(tabs, document.getElementById('workspace') || document.body.lastChild);
99
+ this.tabs = tabs;
100
+ }
101
+
102
+ createBreadcrumb() {
103
+ const breadcrumb = document.createElement('div');
104
+ breadcrumb.className = 'breadcrumb';
105
+ breadcrumb.id = 'breadcrumb';
106
+ breadcrumb.innerHTML = `
107
+ <span class="breadcrumb-item"><a href="#home">Home</a></span>
108
+ `;
109
+
110
+ document.body.insertBefore(breadcrumb, document.getElementById('workspace') || document.body.lastChild);
111
+ this.breadcrumb = breadcrumb;
112
+ }
113
+
114
+ createFAB() {
115
+ const fab = document.createElement('button');
116
+ fab.className = 'floating-action-button';
117
+ fab.id = 'floating-action-button';
118
+ fab.innerHTML = '➕';
119
+
120
+ fab.addEventListener('click', () => {
121
+ this.toggleFABMenu();
122
+ this.options.onFabClick();
123
+ });
124
+
125
+ document.body.appendChild(fab);
126
+ this.fab = fab;
127
+
128
+ // Create FAB menu
129
+ const fabMenu = document.createElement('div');
130
+ fabMenu.className = 'fab-menu hidden';
131
+ fabMenu.id = 'fab-menu';
132
+
133
+ const fabItems = [
134
+ { icon: '➕', label: 'New Part', action: 'newPart' },
135
+ { icon: '📐', label: 'Sketch', action: 'sketch' },
136
+ { icon: '🧊', label: 'Solid', action: 'solid' },
137
+ { icon: '🔍', label: 'View', action: 'view' },
138
+ { icon: '⚙️', label: 'Settings', action: 'settings' },
139
+ { icon: '❓', label: 'Help', action: 'help' }
140
+ ];
141
+
142
+ fabItems.forEach(item => {
143
+ const itemEl = document.createElement('button');
144
+ itemEl.className = 'fab-item';
145
+ itemEl.innerHTML = item.icon;
146
+ itemEl.title = item.label;
147
+ itemEl.addEventListener('click', (e) => {
148
+ e.stopPropagation();
149
+ this.executeFABAction(item.action);
150
+ this.closeFABMenu();
151
+ });
152
+ fabMenu.appendChild(itemEl);
153
+ });
154
+
155
+ document.body.appendChild(fabMenu);
156
+ this.fabMenu = fabMenu;
157
+ }
158
+
159
+ toggleMenu() {
160
+ if (this.menuOpen) {
161
+ this.closeMenu();
162
+ } else {
163
+ this.openMenu();
164
+ }
165
+ }
166
+
167
+ openMenu() {
168
+ this.menuOpen = true;
169
+ this.menu.classList.add('open');
170
+ this.hamburger.classList.add('active');
171
+ document.body.style.overflow = 'hidden';
172
+ this.options.onMenuOpen();
173
+ }
174
+
175
+ closeMenu() {
176
+ this.menuOpen = false;
177
+ this.menu.classList.remove('open');
178
+ this.hamburger.classList.remove('active');
179
+ document.body.style.overflow = '';
180
+ this.options.onMenuClose();
181
+ }
182
+
183
+ selectWorkspace(index) {
184
+ // Remove active from all tabs
185
+ this.tabs.querySelectorAll('.workspace-tab').forEach(tab => {
186
+ tab.classList.remove('active');
187
+ });
188
+
189
+ // Add active to selected tab
190
+ this.tabs.querySelector(`[data-index="${index}"]`).classList.add('active');
191
+
192
+ this.activeWorkspace = index;
193
+ this.options.onWorkspaceChange(this.options.workspaces[index]);
194
+ }
195
+
196
+ toggleFABMenu() {
197
+ this.fabMenu.classList.toggle('hidden');
198
+ }
199
+
200
+ closeFABMenu() {
201
+ this.fabMenu.classList.add('hidden');
202
+ }
203
+
204
+ executeFABAction(action) {
205
+ switch (action) {
206
+ case 'newPart':
207
+ if (window.app && window.app.newPart) window.app.newPart();
208
+ break;
209
+ case 'sketch':
210
+ if (window.app && window.app.enterSketchMode) window.app.enterSketchMode();
211
+ break;
212
+ case 'solid':
213
+ if (window.app && window.app.enterSolidMode) window.app.enterSolidMode();
214
+ break;
215
+ case 'view':
216
+ if (window.app && window.app.toggleViewMode) window.app.toggleViewMode();
217
+ break;
218
+ case 'settings':
219
+ if (window.app && window.app.openSettings) window.app.openSettings();
220
+ break;
221
+ case 'help':
222
+ if (window.app && window.app.openHelp) window.app.openHelp();
223
+ break;
224
+ }
225
+ }
226
+
227
+ setBreadcrumb(items) {
228
+ const breadcrumbEl = document.getElementById('breadcrumb');
229
+ if (!breadcrumbEl) return;
230
+
231
+ let html = '<span class="breadcrumb-item"><a href="#home">Home</a></span>';
232
+
233
+ items.forEach((item, index) => {
234
+ const isLast = index === items.length - 1;
235
+ const href = item.href || '#';
236
+ const label = item.label || item;
237
+
238
+ if (!isLast) {
239
+ html += `<span class="breadcrumb-separator">/</span>`;
240
+ html += `<span class="breadcrumb-item"><a href="${href}">${label}</a></span>`;
241
+ } else {
242
+ html += `<span class="breadcrumb-separator">/</span>`;
243
+ html += `<span class="breadcrumb-item active">${label}</span>`;
244
+ }
245
+ });
246
+
247
+ breadcrumbEl.innerHTML = html;
248
+ }
249
+
250
+ updateWorkspaces(workspaces) {
251
+ this.options.workspaces = workspaces;
252
+ const tabs = document.getElementById('workspace-tabs');
253
+ if (tabs) {
254
+ tabs.innerHTML = '';
255
+ workspaces.forEach((ws, index) => {
256
+ const tab = document.createElement('button');
257
+ tab.className = `workspace-tab ${index === 0 ? 'active' : ''}`;
258
+ tab.dataset.index = index;
259
+ tab.textContent = ws.label || `Workspace ${index + 1}`;
260
+ tab.addEventListener('click', () => this.selectWorkspace(index));
261
+ tabs.appendChild(tab);
262
+ });
263
+ }
264
+ }
265
+
266
+ getActiveWorkspace() {
267
+ return this.options.workspaces[this.activeWorkspace];
268
+ }
269
+
270
+ showContextMenu(x, y, items) {
271
+ const contextMenu = document.createElement('div');
272
+ contextMenu.className = 'context-menu-mobile';
273
+ contextMenu.style.position = 'fixed';
274
+ contextMenu.style.bottom = '44px';
275
+ contextMenu.style.left = '0';
276
+ contextMenu.style.right = '0';
277
+
278
+ items.forEach(item => {
279
+ const menuItem = document.createElement('div');
280
+ menuItem.className = 'menu-item';
281
+ menuItem.innerHTML = `
282
+ <span class="menu-icon">${item.icon || ''}</span>
283
+ <span>${item.label}</span>
284
+ `;
285
+ menuItem.addEventListener('click', () => {
286
+ item.action?.();
287
+ contextMenu.remove();
288
+ });
289
+ contextMenu.appendChild(menuItem);
290
+ });
291
+
292
+ document.body.appendChild(contextMenu);
293
+
294
+ // Close on outside click
295
+ setTimeout(() => {
296
+ document.addEventListener('click', function closeContext() {
297
+ contextMenu.remove();
298
+ document.removeEventListener('click', closeContext);
299
+ });
300
+ }, 0);
301
+ }
302
+
303
+ attachEventListeners() {
304
+ // Close menu on workspace tab click
305
+ this.tabs?.addEventListener('click', (e) => {
306
+ if (e.target.closest('.workspace-tab')) {
307
+ this.closeMenu();
308
+ }
309
+ });
310
+
311
+ // Close FAB menu on viewport click
312
+ const viewport = document.getElementById('viewport');
313
+ if (viewport) {
314
+ viewport.addEventListener('click', () => {
315
+ this.closeFABMenu();
316
+ });
317
+ }
318
+
319
+ // Handle orientation changes
320
+ window.addEventListener('orientationchange', () => {
321
+ this.closeMenu();
322
+ this.closeFABMenu();
323
+ });
324
+ }
325
+
326
+ destroy() {
327
+ this.hamburger?.remove();
328
+ this.menu?.remove();
329
+ this.tabs?.remove();
330
+ this.breadcrumb?.remove();
331
+ this.fab?.remove();
332
+ this.fabMenu?.remove();
333
+ }
334
+ }
335
+
336
+ // Add CSS styles if not already present
337
+ if (!document.getElementById('mobile-nav-styles')) {
338
+ const style = document.createElement('style');
339
+ style.id = 'mobile-nav-styles';
340
+ style.textContent = `
341
+ .mobile-menu {
342
+ position: fixed;
343
+ left: 0;
344
+ top: 0;
345
+ width: 100%;
346
+ height: 100%;
347
+ z-index: 200;
348
+ pointer-events: none;
349
+ }
350
+
351
+ .mobile-menu.open {
352
+ pointer-events: auto;
353
+ }
354
+
355
+ .menu-backdrop {
356
+ position: absolute;
357
+ top: 0;
358
+ left: 0;
359
+ width: 100%;
360
+ height: 100%;
361
+ background: rgba(0, 0, 0, 0.5);
362
+ backdrop-filter: blur(4px);
363
+ opacity: 0;
364
+ transition: opacity 0.3s;
365
+ }
366
+
367
+ .mobile-menu.open .menu-backdrop {
368
+ opacity: 1;
369
+ }
370
+
371
+ .menu-content {
372
+ position: absolute;
373
+ top: 0;
374
+ left: 0;
375
+ width: 80vw;
376
+ max-width: 280px;
377
+ height: 100%;
378
+ background: var(--bg-primary);
379
+ display: flex;
380
+ flex-direction: column;
381
+ transform: translateX(-100%);
382
+ transition: transform 0.3s ease-out;
383
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
384
+ z-index: 210;
385
+ }
386
+
387
+ .mobile-menu.open .menu-content {
388
+ transform: translateX(0);
389
+ }
390
+
391
+ .menu-header {
392
+ display: flex;
393
+ justify-content: space-between;
394
+ align-items: center;
395
+ padding: 16px;
396
+ border-bottom: 1px solid var(--border-color);
397
+ }
398
+
399
+ .menu-header h2 {
400
+ margin: 0;
401
+ font-size: 18px;
402
+ }
403
+
404
+ .menu-close {
405
+ width: 44px;
406
+ height: 44px;
407
+ border: none;
408
+ background: transparent;
409
+ cursor: pointer;
410
+ font-size: 20px;
411
+ color: var(--text-primary);
412
+ }
413
+
414
+ .menu-items {
415
+ list-style: none;
416
+ margin: 0;
417
+ padding: 0;
418
+ overflow-y: auto;
419
+ flex: 1;
420
+ }
421
+
422
+ .menu-items li {
423
+ border-bottom: 1px solid var(--border-color);
424
+ }
425
+
426
+ .menu-items a {
427
+ display: block;
428
+ padding: 16px;
429
+ color: var(--text-primary);
430
+ text-decoration: none;
431
+ font-size: 16px;
432
+ transition: background 0.2s;
433
+ }
434
+
435
+ .menu-items a:active {
436
+ background: var(--bg-secondary);
437
+ }
438
+
439
+ .workspace-tabs {
440
+ display: flex;
441
+ gap: 0;
442
+ border-bottom: 1px solid var(--border-color);
443
+ overflow-x: auto;
444
+ -webkit-overflow-scrolling: touch;
445
+ background: var(--bg-secondary);
446
+ }
447
+
448
+ .workspace-tab {
449
+ flex: 0 0 auto;
450
+ padding: 12px 16px;
451
+ min-width: 100px;
452
+ text-align: center;
453
+ border-bottom: 3px solid transparent;
454
+ cursor: pointer;
455
+ font-size: 13px;
456
+ white-space: nowrap;
457
+ background: transparent;
458
+ border: none;
459
+ color: var(--text-primary);
460
+ transition: all 0.2s;
461
+ }
462
+
463
+ .workspace-tab.active {
464
+ border-bottom-color: var(--accent-color);
465
+ color: var(--accent-color);
466
+ }
467
+
468
+ .breadcrumb {
469
+ display: flex;
470
+ gap: 4px;
471
+ padding: 4px 8px;
472
+ font-size: 12px;
473
+ overflow-x: auto;
474
+ -webkit-overflow-scrolling: touch;
475
+ background: var(--bg-secondary);
476
+ border-bottom: 1px solid var(--border-color);
477
+ }
478
+
479
+ .breadcrumb-item {
480
+ white-space: nowrap;
481
+ padding: 4px 8px;
482
+ }
483
+
484
+ .breadcrumb-item a {
485
+ color: var(--accent-color);
486
+ text-decoration: none;
487
+ }
488
+
489
+ .breadcrumb-item.active {
490
+ color: var(--text-primary);
491
+ font-weight: 500;
492
+ }
493
+
494
+ .breadcrumb-separator {
495
+ opacity: 0.5;
496
+ }
497
+
498
+ .floating-action-button {
499
+ position: fixed;
500
+ bottom: 72px;
501
+ right: 16px;
502
+ width: 56px;
503
+ height: 56px;
504
+ border-radius: 50%;
505
+ background: var(--accent-color);
506
+ color: white;
507
+ border: none;
508
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
509
+ cursor: pointer;
510
+ display: flex;
511
+ align-items: center;
512
+ justify-content: center;
513
+ font-size: 24px;
514
+ z-index: 170;
515
+ transition: all 0.2s;
516
+ }
517
+
518
+ .floating-action-button:active {
519
+ transform: scale(0.9);
520
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
521
+ }
522
+
523
+ .floating-action-button.hidden {
524
+ display: none;
525
+ }
526
+
527
+ .fab-menu {
528
+ position: fixed;
529
+ bottom: 140px;
530
+ right: 16px;
531
+ display: flex;
532
+ flex-direction: column;
533
+ gap: 12px;
534
+ z-index: 165;
535
+ transition: opacity 0.3s;
536
+ }
537
+
538
+ .fab-menu.hidden {
539
+ opacity: 0;
540
+ pointer-events: none;
541
+ }
542
+
543
+ .fab-item {
544
+ width: 48px;
545
+ height: 48px;
546
+ border-radius: 50%;
547
+ background: var(--bg-secondary);
548
+ border: 1px solid var(--border-color);
549
+ display: flex;
550
+ align-items: center;
551
+ justify-content: center;
552
+ cursor: pointer;
553
+ font-size: 20px;
554
+ transition: all 0.2s;
555
+ }
556
+
557
+ .fab-item:active {
558
+ transform: scale(0.9);
559
+ background: var(--accent-color);
560
+ color: white;
561
+ }
562
+
563
+ .context-menu-mobile {
564
+ background: var(--bg-primary);
565
+ border-top: 1px solid var(--border-color);
566
+ z-index: 160;
567
+ padding: 8px;
568
+ max-height: 50vh;
569
+ overflow-y: auto;
570
+ border-radius: 12px 12px 0 0;
571
+ box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);
572
+ }
573
+
574
+ .context-menu-mobile .menu-item {
575
+ padding: 16px;
576
+ border-bottom: 1px solid var(--border-color);
577
+ display: flex;
578
+ align-items: center;
579
+ gap: 12px;
580
+ cursor: pointer;
581
+ min-height: 48px;
582
+ }
583
+
584
+ .context-menu-mobile .menu-item:last-child {
585
+ border-bottom: none;
586
+ }
587
+
588
+ .context-menu-mobile .menu-item:active {
589
+ background: var(--bg-secondary);
590
+ }
591
+
592
+ .context-menu-mobile .menu-icon {
593
+ font-size: 20px;
594
+ opacity: 0.7;
595
+ }
596
+
597
+ @media (max-width: 599px) {
598
+ .workspace-tabs {
599
+ display: flex;
600
+ }
601
+ }
602
+
603
+ @media (min-width: 600px) {
604
+ .workspace-tabs {
605
+ display: none;
606
+ }
607
+
608
+ .floating-action-button {
609
+ display: none;
610
+ }
611
+
612
+ .fab-menu {
613
+ display: none;
614
+ }
615
+ }
616
+ `;
617
+ document.head.appendChild(style);
618
+ }
619
+
620
+ // Export for use in modules
621
+ if (typeof module !== 'undefined' && module.exports) {
622
+ module.exports = MobileNav;
623
+ }