@kennofizet/apphub-frontend 0.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 (90) hide show
  1. package/README.md +84 -0
  2. package/package.json +31 -0
  3. package/src/api/coreApi.js +25 -0
  4. package/src/api/index.js +80 -0
  5. package/src/composables/createZoneContext.js +156 -0
  6. package/src/composables/useAppHubHostApi.js +24 -0
  7. package/src/composables/useAppHubZoneContext.js +11 -0
  8. package/src/composables/useDevOriginToggle.js +40 -0
  9. package/src/i18n/index.js +16 -0
  10. package/src/i18n/resolveLang.js +6 -0
  11. package/src/i18n/resolveTheme.js +30 -0
  12. package/src/i18n/translations/en.js +303 -0
  13. package/src/i18n/translations/vi.js +302 -0
  14. package/src/index.js +427 -0
  15. package/src/moduleStore.js +10 -0
  16. package/src/modules/app-store/components/AppHubAppStoreApp.vue +210 -0
  17. package/src/modules/app-store/components/AppHubAppStoreCard.vue +88 -0
  18. package/src/modules/app-store/components/AppHubAppStoreSettingsPanel.vue +266 -0
  19. package/src/modules/app-store/components/AppHubAppVersionHistory.vue +77 -0
  20. package/src/modules/app-store/components/AppHubDevReviewPanel.vue +206 -0
  21. package/src/modules/app-store/components/AppHubDraftStoreApp.vue +184 -0
  22. package/src/modules/app-store/components/AppHubDraftStoreCard.vue +116 -0
  23. package/src/modules/app-store/composables/useAppStore.js +206 -0
  24. package/src/modules/app-store/composables/useCatalogInfiniteScroll.js +47 -0
  25. package/src/modules/app-store/constants/catalogModes.js +2 -0
  26. package/src/modules/app-store/data/defaultCatalog.js +19 -0
  27. package/src/modules/app-store/index.js +9 -0
  28. package/src/modules/app-store/utils/normalizeCatalogApp.js +37 -0
  29. package/src/modules/desktop/components/AppHubDesktop.vue +1510 -0
  30. package/src/modules/desktop/components/AppHubDesktopDevOriginBar.vue +57 -0
  31. package/src/modules/desktop/components/AppHubDesktopDropLayer.vue +15 -0
  32. package/src/modules/desktop/components/AppHubDesktopDropTarget.vue +32 -0
  33. package/src/modules/desktop/components/AppHubDesktopIconContextMenu.vue +74 -0
  34. package/src/modules/desktop/components/AppHubDesktopIconFolder.vue +60 -0
  35. package/src/modules/desktop/components/AppHubDesktopIconGroup.vue +58 -0
  36. package/src/modules/desktop/components/AppHubDesktopIconInfoDialog.vue +33 -0
  37. package/src/modules/desktop/components/AppHubDesktopIconRenameDialog.vue +62 -0
  38. package/src/modules/desktop/components/AppHubDesktopSettings.vue +28 -0
  39. package/src/modules/desktop/components/AppHubDropInstallBadge.vue +65 -0
  40. package/src/modules/desktop/components/AppHubDuplicateAppDialog.vue +38 -0
  41. package/src/modules/desktop/components/AppHubGuideApp.vue +278 -0
  42. package/src/modules/desktop/components/AppHubOriginBlockScreen.vue +105 -0
  43. package/src/modules/desktop/components/AppHubOriginLoadingScreen.vue +23 -0
  44. package/src/modules/desktop/components/AppHubPlaceholderApp.vue +14 -0
  45. package/src/modules/desktop/components/AppHubSettingsApp.vue +319 -0
  46. package/src/modules/desktop/components/AppHubStartButton.vue +24 -0
  47. package/src/modules/desktop/components/AppHubStartMenu.vue +182 -0
  48. package/src/modules/desktop/components/AppHubTaskbarPins.vue +23 -0
  49. package/src/modules/desktop/components/settings/AppHubSettingsKeyboardPanel.vue +82 -0
  50. package/src/modules/desktop/components/settings/AppHubSettingsScreenPanel.vue +41 -0
  51. package/src/modules/desktop/components/settings/AppHubSettingsStartMenuPanel.vue +95 -0
  52. package/src/modules/desktop/composables/simulateInstallProgress.js +15 -0
  53. package/src/modules/desktop/composables/useDesktopDropInstall.js +272 -0
  54. package/src/modules/desktop/composables/useDesktopHubSettings.js +51 -0
  55. package/src/modules/desktop/composables/useDesktopIconDrag.js +207 -0
  56. package/src/modules/desktop/composables/useDesktopShell.js +335 -0
  57. package/src/modules/desktop/data/builtinApps.js +77 -0
  58. package/src/modules/desktop/index.js +12 -0
  59. package/src/modules/desktop/styles/desktop.css +3104 -0
  60. package/src/modules/desktop/styles/theme.css +616 -0
  61. package/src/modules/desktop/utils/desktopGrid.js +43 -0
  62. package/src/modules/desktop/utils/desktopIconGroups.js +103 -0
  63. package/src/modules/desktop/utils/desktopSession.js +40 -0
  64. package/src/modules/desktop/utils/desktopSettings.js +37 -0
  65. package/src/modules/desktop/utils/dropPackageParser.js +140 -0
  66. package/src/modules/desktop/utils/duplicateAppUtils.js +28 -0
  67. package/src/modules/desktop/utils/hubKeyboardSettings.js +63 -0
  68. package/src/modules/desktop/utils/recentApps.js +148 -0
  69. package/src/modules/desktop/utils/startMenuFavorites.js +100 -0
  70. package/src/modules/desktop/utils/startMenuPins.js +90 -0
  71. package/src/modules/notifications/components/AppHubDesktopNotifications.vue +54 -0
  72. package/src/modules/notifications/composables/createDesktopNotifications.js +86 -0
  73. package/src/modules/notifications/index.js +9 -0
  74. package/src/modules/notifications/styles/notifications.css +118 -0
  75. package/src/modules/notifications/utils/parseApiError.js +29 -0
  76. package/src/modules/runner/components/AppHubRunner.vue +292 -0
  77. package/src/modules/runner/index.js +1 -0
  78. package/src/modules/window-manager/components/AppHubWindowFrame.vue +224 -0
  79. package/src/modules/window-manager/composables/useWindowManager.js +652 -0
  80. package/src/modules/window-manager/index.js +7 -0
  81. package/src/modules/window-manager/utils/sessionLayout.js +28 -0
  82. package/src/modules/window-manager/utils/windowLayout.js +236 -0
  83. package/src/modules/window-manager/utils/windowSnap.js +146 -0
  84. package/src/utils/bootstrapCache.js +47 -0
  85. package/src/utils/devOriginSettings.js +22 -0
  86. package/src/utils/launchUrl.js +111 -0
  87. package/src/utils/originSafety.js +267 -0
  88. package/src/utils/safeStorage.js +191 -0
  89. package/src/utils/semver.js +30 -0
  90. package/src/utils/zoneContext.js +38 -0
@@ -0,0 +1,652 @@
1
+ import { computed, inject, markRaw, reactive } from 'vue'
2
+
3
+ import {
4
+
5
+ clampWindowDimensions,
6
+
7
+ clampWindowToWorkArea,
8
+
9
+ fullscreenBounds,
10
+
11
+ layoutSnapshot,
12
+
13
+ miniBoundsFromStorage,
14
+
15
+ resolveOpenLayout,
16
+
17
+ saveWindowLayout,
18
+
19
+ setDesktopWorkArea,
20
+
21
+ } from '../utils/windowLayout.js'
22
+
23
+ import {
24
+
25
+ applyFullscreen,
26
+
27
+ captureFloatingBounds,
28
+
29
+ computeSnapAction,
30
+
31
+ defaultFloatingBounds,
32
+
33
+ getSnapBounds,
34
+
35
+ resolveRestoreBounds,
36
+
37
+ } from '../utils/windowSnap.js'
38
+
39
+ import { layoutFromSession } from '../utils/sessionLayout.js'
40
+
41
+
42
+
43
+ export const WINDOW_MANAGER_KEY = 'apphubWindowManager'
44
+
45
+
46
+
47
+ /**
48
+
49
+ * Independent window manager — open, close, focus, minimize app windows on the desktop.
50
+
51
+ */
52
+
53
+ export function createWindowManagerState() {
54
+
55
+ const state = reactive({
56
+
57
+ windows: [],
58
+
59
+ activeId: null,
60
+
61
+ nextZ: 10,
62
+
63
+ })
64
+
65
+
66
+
67
+ function bringToFront(id) {
68
+
69
+ state.nextZ += 1
70
+
71
+ const win = state.windows.find((w) => w.id === id)
72
+
73
+ if (win) {
74
+
75
+ win.zIndex = state.nextZ
76
+
77
+ win.minimized = false
78
+
79
+ }
80
+
81
+ state.activeId = id
82
+
83
+ }
84
+
85
+
86
+
87
+ function persistLayout(win) {
88
+
89
+ if (!win?.layoutKey) return
90
+
91
+ saveWindowLayout(win.layoutKey, layoutSnapshot(win))
92
+
93
+ }
94
+
95
+
96
+
97
+ function restoreWindowBounds(win) {
98
+
99
+ const stored = win.layoutKey ? miniBoundsFromStorage(win.layoutKey, win) : null
100
+
101
+ const bounds = resolveRestoreBounds(win) ?? stored ?? defaultFloatingBounds(win)
102
+
103
+ win.display = 'mini'
104
+
105
+ win.snap = null
106
+
107
+ win.x = bounds.x
108
+
109
+ win.y = bounds.y
110
+
111
+ win.width = bounds.width
112
+
113
+ win.height = bounds.height
114
+
115
+ clampWindowToWorkArea(win)
116
+
117
+ }
118
+
119
+
120
+
121
+ function openWindow(definition, sessionState = null) {
122
+
123
+ const existing = state.windows.find((w) => w.id === definition.id)
124
+
125
+ if (existing) {
126
+
127
+ bringToFront(definition.id)
128
+
129
+ return existing
130
+
131
+ }
132
+
133
+
134
+
135
+ const layout = sessionState
136
+
137
+ ? layoutFromSession(sessionState, definition)
138
+
139
+ : resolveOpenLayout({
140
+
141
+ ...definition,
142
+
143
+ offsetIndex: state.windows.length,
144
+
145
+ })
146
+
147
+
148
+
149
+ const zIndex = sessionState?.zIndex ?? state.nextZ + 1
150
+
151
+ state.nextZ = Math.max(state.nextZ, zIndex)
152
+
153
+
154
+
155
+ const entry = {
156
+
157
+ id: definition.id,
158
+
159
+ title: definition.title,
160
+
161
+ icon: definition.icon ?? '📦',
162
+
163
+ component: definition.component ? markRaw(definition.component) : null,
164
+
165
+ props: markRaw({ ...(definition.props ?? {}) }),
166
+
167
+ width: layout.width,
168
+
169
+ height: layout.height,
170
+
171
+ x: layout.x,
172
+
173
+ y: layout.y,
174
+
175
+ display: layout.display,
176
+
177
+ layoutKey: layout.layoutKey,
178
+
179
+ miniWidth: layout.miniWidth,
180
+
181
+ miniHeight: layout.miniHeight,
182
+
183
+ snap: layout.display === 'fullscreen' ? 'fullscreen' : null,
184
+
185
+ floatingBounds: null,
186
+
187
+ zIndex,
188
+
189
+ minimized: !!sessionState?.minimized,
190
+
191
+ }
192
+
193
+ state.windows.push(entry)
194
+
195
+ if (!sessionState) {
196
+
197
+ state.activeId = entry.id
198
+
199
+ }
200
+
201
+ return entry
202
+
203
+ }
204
+
205
+
206
+
207
+ function finishSessionRestore(activeId) {
208
+
209
+ if (activeId && state.windows.some((w) => w.id === activeId)) {
210
+
211
+ state.activeId = activeId
212
+
213
+ const win = state.windows.find((w) => w.id === activeId)
214
+
215
+ if (win && !win.minimized) {
216
+
217
+ state.nextZ += 1
218
+
219
+ win.zIndex = state.nextZ
220
+
221
+ }
222
+
223
+ } else {
224
+
225
+ const top = [...state.windows]
226
+
227
+ .filter((w) => !w.minimized)
228
+
229
+ .sort((a, b) => b.zIndex - a.zIndex)[0]
230
+
231
+ state.activeId = top?.id ?? null
232
+
233
+ }
234
+
235
+ syncFullscreenWindows()
236
+
237
+ relayoutWindows()
238
+
239
+ }
240
+
241
+
242
+
243
+ function closeWindow(id) {
244
+
245
+ const idx = state.windows.findIndex((w) => w.id === id)
246
+
247
+ if (idx === -1) return
248
+
249
+ const win = state.windows[idx]
250
+
251
+ persistLayout(win)
252
+
253
+ state.windows.splice(idx, 1)
254
+
255
+ if (state.activeId === id) {
256
+
257
+ const top = state.windows[state.windows.length - 1]
258
+
259
+ state.activeId = top?.id ?? null
260
+
261
+ }
262
+
263
+ }
264
+
265
+
266
+
267
+ function minimizeWindow(id) {
268
+
269
+ const win = state.windows.find((w) => w.id === id)
270
+
271
+ if (!win) return
272
+
273
+ win.minimized = true
274
+
275
+ if (state.activeId === id) {
276
+
277
+ const visible = [...state.windows].filter((w) => !w.minimized).sort((a, b) => b.zIndex - a.zIndex)
278
+
279
+ state.activeId = visible[0]?.id ?? null
280
+
281
+ }
282
+
283
+ }
284
+
285
+
286
+
287
+ function focusWindow(id) {
288
+
289
+ bringToFront(id)
290
+
291
+ }
292
+
293
+
294
+
295
+ function clearSnap(id) {
296
+
297
+ const win = state.windows.find((w) => w.id === id)
298
+
299
+ if (win) win.snap = null
300
+
301
+ }
302
+
303
+
304
+
305
+ /**
306
+
307
+ * Leave fullscreen and place the window so the title-bar grab point stays under the pointer.
308
+
309
+ * pointerX/pointerY and offsetX/offsetY are in work-area coordinates.
310
+
311
+ */
312
+
313
+ function restoreWindowFromFullscreen(id, { pointerX, pointerY, offsetX, offsetY }) {
314
+
315
+ const win = state.windows.find((w) => w.id === id)
316
+
317
+ if (!win || win.display !== 'fullscreen') return false
318
+
319
+
320
+
321
+ win.display = 'mini'
322
+
323
+ win.snap = null
324
+
325
+ const bounds = resolveRestoreBounds(win) ?? defaultFloatingBounds(win)
326
+
327
+ win.width = bounds.width
328
+
329
+ win.height = bounds.height
330
+
331
+ win.x = pointerX - offsetX
332
+
333
+ win.y = pointerY - offsetY
334
+
335
+ clampWindowToWorkArea(win)
336
+
337
+ persistLayout(win)
338
+
339
+ bringToFront(id)
340
+
341
+ return true
342
+
343
+ }
344
+
345
+
346
+
347
+ function toggleWindowDisplay(id) {
348
+
349
+ const win = state.windows.find((w) => w.id === id)
350
+
351
+ if (!win) return
352
+
353
+
354
+
355
+ if (win.display === 'fullscreen') {
356
+
357
+ restoreWindowBounds(win)
358
+
359
+ } else {
360
+
361
+ if (win.layoutKey) {
362
+
363
+ saveWindowLayout(win.layoutKey, {
364
+
365
+ display: 'fullscreen',
366
+
367
+ mini: win.floatingBounds ?? {
368
+
369
+ x: win.x,
370
+
371
+ y: win.y,
372
+
373
+ width: win.width,
374
+
375
+ height: win.height,
376
+
377
+ },
378
+
379
+ })
380
+
381
+ }
382
+
383
+ applyFullscreen(win)
384
+
385
+ }
386
+
387
+
388
+
389
+ persistLayout(win)
390
+
391
+ bringToFront(id)
392
+
393
+ }
394
+
395
+
396
+
397
+ /** @param {'left'|'right'|'up'|'down'} direction */
398
+
399
+ function snapActiveWindow(id, direction) {
400
+
401
+ const win = state.windows.find((w) => w.id === id)
402
+
403
+ if (!win || win.minimized) return false
404
+
405
+
406
+
407
+ const result = computeSnapAction(win, direction)
408
+
409
+ if (result.action === 'none') return false
410
+
411
+
412
+
413
+ bringToFront(id)
414
+
415
+
416
+
417
+ if (result.action === 'minimize') {
418
+
419
+ minimizeWindow(id)
420
+
421
+ return true
422
+
423
+ }
424
+
425
+
426
+
427
+ if (result.action === 'restore') {
428
+
429
+ restoreWindowBounds(win)
430
+
431
+ persistLayout(win)
432
+
433
+ return true
434
+
435
+ }
436
+
437
+
438
+
439
+ if (result.action === 'fullscreen') {
440
+
441
+ if (win.layoutKey) {
442
+
443
+ saveWindowLayout(win.layoutKey, {
444
+
445
+ display: 'fullscreen',
446
+
447
+ mini: {
448
+
449
+ x: win.x,
450
+
451
+ y: win.y,
452
+
453
+ width: win.width,
454
+
455
+ height: win.height,
456
+
457
+ },
458
+
459
+ })
460
+
461
+ }
462
+
463
+ applyFullscreen(win)
464
+
465
+ persistLayout(win)
466
+
467
+ return true
468
+
469
+ }
470
+
471
+
472
+
473
+ if (result.action === 'snap' && result.bounds) {
474
+
475
+ if (!win.snap && win.display === 'mini') {
476
+
477
+ captureFloatingBounds(win)
478
+
479
+ }
480
+
481
+ Object.assign(win, result.bounds)
482
+
483
+ win.display = result.display ?? 'mini'
484
+
485
+ win.snap = result.snap ?? null
486
+
487
+ clampWindowToWorkArea(win)
488
+
489
+ persistLayout(win)
490
+
491
+ return true
492
+
493
+ }
494
+
495
+
496
+
497
+ return false
498
+
499
+ }
500
+
501
+
502
+
503
+ function saveWindowLayoutState(id) {
504
+
505
+ const win = state.windows.find((w) => w.id === id)
506
+
507
+ if (!win || win.display !== 'mini') return
508
+
509
+ captureFloatingBounds(win)
510
+
511
+ persistLayout(win)
512
+
513
+ }
514
+
515
+
516
+
517
+ function setWorkArea(size) {
518
+
519
+ setDesktopWorkArea(size)
520
+
521
+ syncFullscreenWindows()
522
+
523
+ relayoutSnappedWindows()
524
+
525
+ }
526
+
527
+
528
+
529
+ function syncFullscreenWindows() {
530
+
531
+ const bounds = fullscreenBounds()
532
+
533
+ state.windows.forEach((win) => {
534
+
535
+ if (win.display === 'fullscreen') {
536
+
537
+ Object.assign(win, bounds)
538
+
539
+ }
540
+
541
+ })
542
+
543
+ }
544
+
545
+
546
+
547
+ function relayoutSnappedWindows() {
548
+
549
+ state.windows.forEach((win) => {
550
+
551
+ if (win.display === 'fullscreen' || !win.snap || win.snap === 'fullscreen') return
552
+
553
+ const bounds = getSnapBounds(win.snap)
554
+
555
+ if (bounds) Object.assign(win, bounds)
556
+
557
+ })
558
+
559
+ }
560
+
561
+
562
+
563
+ function relayoutWindows() {
564
+
565
+ syncFullscreenWindows()
566
+
567
+ relayoutSnappedWindows()
568
+
569
+ state.windows.forEach((win) => {
570
+
571
+ if (win.display === 'fullscreen') return
572
+
573
+ if (win.snap) return
574
+
575
+ clampWindowDimensions(win)
576
+
577
+ })
578
+
579
+ }
580
+
581
+
582
+
583
+ const visibleWindows = computed(() => state.windows.filter((w) => !w.minimized))
584
+
585
+
586
+
587
+ const taskbarWindows = computed(() => [...state.windows].sort((a, b) => a.title.localeCompare(b.title)))
588
+
589
+
590
+
591
+ return {
592
+
593
+ state,
594
+
595
+ visibleWindows,
596
+
597
+ taskbarWindows,
598
+
599
+ openWindow,
600
+
601
+ finishSessionRestore,
602
+
603
+ closeWindow,
604
+
605
+ minimizeWindow,
606
+
607
+ focusWindow,
608
+
609
+ toggleWindowDisplay,
610
+
611
+ snapActiveWindow,
612
+
613
+ restoreWindowFromFullscreen,
614
+
615
+ saveWindowLayoutState,
616
+
617
+ clearSnap,
618
+
619
+ setWorkArea,
620
+
621
+ syncFullscreenWindows,
622
+
623
+ relayoutWindows,
624
+
625
+ }
626
+
627
+ }
628
+
629
+
630
+
631
+ export function provideWindowManager(app, manager) {
632
+
633
+ app.provide(WINDOW_MANAGER_KEY, manager)
634
+
635
+ }
636
+
637
+
638
+
639
+ export function useWindowManager() {
640
+
641
+ const manager = inject(WINDOW_MANAGER_KEY, null)
642
+
643
+ if (!manager) {
644
+
645
+ throw new Error('useWindowManager() requires installAppHubModule() or provideWindowManager()')
646
+
647
+ }
648
+
649
+ return manager
650
+
651
+ }
652
+
@@ -0,0 +1,7 @@
1
+ export {
2
+ createWindowManagerState,
3
+ provideWindowManager,
4
+ useWindowManager,
5
+ WINDOW_MANAGER_KEY,
6
+ } from './composables/useWindowManager.js'
7
+ export { default as AppHubWindowFrame } from './components/AppHubWindowFrame.vue'
@@ -0,0 +1,28 @@
1
+ import { fullscreenBounds } from './windowLayout.js'
2
+
3
+ export function layoutFromSession(saved, definition) {
4
+ const miniWidth = definition.miniWidth ?? definition.width ?? 720
5
+ const miniHeight = definition.miniHeight ?? definition.height ?? 480
6
+ const layoutKey = definition.layoutKey ?? null
7
+
8
+ if (saved.display === 'fullscreen') {
9
+ return {
10
+ display: 'fullscreen',
11
+ layoutKey,
12
+ miniWidth,
13
+ miniHeight,
14
+ ...fullscreenBounds(),
15
+ }
16
+ }
17
+
18
+ return {
19
+ display: 'mini',
20
+ layoutKey,
21
+ miniWidth,
22
+ miniHeight,
23
+ width: saved.width ?? miniWidth,
24
+ height: saved.height ?? miniHeight,
25
+ x: saved.x ?? 0,
26
+ y: saved.y ?? 0,
27
+ }
28
+ }