@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,57 @@
1
+ <template>
2
+ <div
3
+ v-if="visible"
4
+ class="apphub-dev-origin-bar"
5
+ :class="`apphub-dev-origin-bar--${placement}`"
6
+ role="region"
7
+ :aria-label="labels.title"
8
+ >
9
+ <span class="apphub-dev-origin-bar__badge">DEV</span>
10
+ <div v-if="placement === 'corner'" class="apphub-dev-origin-bar__text">
11
+ <strong class="apphub-dev-origin-bar__title">{{ labels.title }}</strong>
12
+ <span class="apphub-dev-origin-bar__status">{{ statusLabel }}</span>
13
+ </div>
14
+ <span v-else class="apphub-dev-origin-bar__status apphub-dev-origin-bar__status--inline">
15
+ {{ statusLabel }}
16
+ </span>
17
+ <button
18
+ type="button"
19
+ class="apphub-dev-origin-bar__btn"
20
+ :title="labels.title"
21
+ @click="toggle"
22
+ >
23
+ {{ actionLabel }}
24
+ </button>
25
+ </div>
26
+ </template>
27
+
28
+ <script setup>
29
+ import { computed, inject } from 'vue'
30
+ import { t } from '../../../i18n/index.js'
31
+ import { resolveLang } from '../../../i18n/resolveLang.js'
32
+ import { useDevOriginToggle } from '../../../composables/useDevOriginToggle.js'
33
+
34
+ defineProps({
35
+ placement: { type: String, default: 'corner' },
36
+ })
37
+
38
+ const { visible, devFriendlyOn, toggle } = useDevOriginToggle()
39
+ const moduleOptions = inject('apphubOptions', {})
40
+ const lang = computed(() => resolveLang(moduleOptions?.language, 'vi'))
41
+
42
+ const labels = computed(() => ({
43
+ title: t('dev_origin_bar_title', lang.value),
44
+ status_relaxed: t('dev_origin_status_relaxed', lang.value),
45
+ status_strict: t('dev_origin_status_strict', lang.value),
46
+ action_enable_strict: t('dev_origin_action_strict', lang.value),
47
+ action_enable_relaxed: t('dev_origin_action_relaxed', lang.value),
48
+ }))
49
+
50
+ const statusLabel = computed(() =>
51
+ devFriendlyOn.value ? labels.value.status_relaxed : labels.value.status_strict,
52
+ )
53
+
54
+ const actionLabel = computed(() =>
55
+ devFriendlyOn.value ? labels.value.action_enable_strict : labels.value.action_enable_relaxed,
56
+ )
57
+ </script>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <div class="apphub-drop-layer">
3
+ <div class="apphub-drop-layer__glow" />
4
+ <div class="apphub-drop-layer__hint">
5
+ <span class="apphub-drop-layer__hint-icon">⬇</span>
6
+ <p>{{ hint }}</p>
7
+ </div>
8
+ </div>
9
+ </template>
10
+
11
+ <script setup>
12
+ defineProps({
13
+ hint: { type: String, default: '' },
14
+ })
15
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <div
3
+ v-if="target"
4
+ class="apphub-desktop__drop-target"
5
+ :style="{ left: `${target.x}px`, top: `${target.y}px` }"
6
+ >
7
+ <div class="apphub-desktop__drop-target-panel">
8
+ <p class="apphub-desktop__drop-target-label">{{ label }}</p>
9
+ <div class="apphub-desktop__drop-target-grid">
10
+ <span
11
+ v-for="app in previewApps"
12
+ :key="app.id"
13
+ class="apphub-desktop__drop-target-icon"
14
+ :title="app.name"
15
+ >
16
+ {{ app.icon }}
17
+ </span>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup>
24
+ import { computed } from 'vue'
25
+
26
+ const props = defineProps({
27
+ target: { type: Object, default: null },
28
+ label: { type: String, default: '' },
29
+ })
30
+
31
+ const previewApps = computed(() => props.target?.apps?.slice(0, 9) ?? [])
32
+ </script>
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <div
3
+ v-if="open"
4
+ class="apphub-icon-menu"
5
+ :style="{ left: `${x}px`, top: `${y}px` }"
6
+ role="menu"
7
+ @click.stop
8
+ @contextmenu.prevent
9
+ >
10
+ <button type="button" class="apphub-icon-menu__item" role="menuitem" @click="emit('open')">
11
+ {{ openLabel }}
12
+ </button>
13
+ <button
14
+ v-if="showPin"
15
+ type="button"
16
+ class="apphub-icon-menu__item"
17
+ role="menuitem"
18
+ @click="emit('pin')"
19
+ >
20
+ {{ pinLabel }}
21
+ </button>
22
+ <button
23
+ v-if="showFavorite"
24
+ type="button"
25
+ class="apphub-icon-menu__item"
26
+ role="menuitem"
27
+ @click="emit('favorite')"
28
+ >
29
+ {{ favoriteLabel }}
30
+ </button>
31
+ <button
32
+ v-if="canRename"
33
+ type="button"
34
+ class="apphub-icon-menu__item"
35
+ role="menuitem"
36
+ @click="emit('rename')"
37
+ >
38
+ {{ renameLabel }}
39
+ </button>
40
+ <button
41
+ v-if="showUninstall"
42
+ type="button"
43
+ class="apphub-icon-menu__item apphub-icon-menu__item--danger"
44
+ role="menuitem"
45
+ @click="emit('uninstall')"
46
+ >
47
+ {{ uninstallLabel }}
48
+ </button>
49
+ <div class="apphub-icon-menu__sep" role="separator" />
50
+ <button type="button" class="apphub-icon-menu__item" role="menuitem" @click="emit('info')">
51
+ {{ propertiesLabel }}
52
+ </button>
53
+ </div>
54
+ </template>
55
+
56
+ <script setup>
57
+ defineProps({
58
+ open: { type: Boolean, default: false },
59
+ x: { type: Number, default: 0 },
60
+ y: { type: Number, default: 0 },
61
+ canRename: { type: Boolean, default: true },
62
+ showPin: { type: Boolean, default: false },
63
+ showFavorite: { type: Boolean, default: false },
64
+ showUninstall: { type: Boolean, default: false },
65
+ openLabel: { type: String, default: '' },
66
+ pinLabel: { type: String, default: '' },
67
+ favoriteLabel: { type: String, default: '' },
68
+ renameLabel: { type: String, default: '' },
69
+ uninstallLabel: { type: String, default: '' },
70
+ propertiesLabel: { type: String, default: '' },
71
+ })
72
+
73
+ const emit = defineEmits(['open', 'pin', 'favorite', 'uninstall', 'rename', 'info', 'close'])
74
+ </script>
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <div
3
+ v-if="open"
4
+ class="apphub-icon-folder"
5
+ :class="{ 'apphub-icon-folder--preview': preview }"
6
+ :style="{ left: `${x}px`, top: `${y}px` }"
7
+ @mousedown.stop
8
+ @click.stop
9
+ >
10
+ <div class="apphub-icon-folder__panel">
11
+ <header class="apphub-icon-folder__header">
12
+ <span class="apphub-icon-folder__title">{{ title }}</span>
13
+ <span class="apphub-icon-folder__count">{{ countLabel }}</span>
14
+ </header>
15
+ <div class="apphub-icon-folder__grid">
16
+ <button
17
+ v-for="app in apps"
18
+ :key="app.id"
19
+ type="button"
20
+ class="apphub-icon-folder__item"
21
+ :class="{
22
+ 'apphub-icon-folder__item--dragging': !preview && isDragging(app.id),
23
+ 'apphub-icon-folder__item--holding': !preview && isHolding(app.id),
24
+ 'apphub-icon-folder__item--preview-new':
25
+ preview && previewNewIds.includes(app.id),
26
+ }"
27
+ :tabindex="preview ? -1 : 0"
28
+ @mousedown.stop="!preview && emit('item-pointer-down', app, $event)"
29
+ @dblclick.stop="!preview && emit('open-app', app)"
30
+ @contextmenu.prevent.stop="!preview && emit('item-context-menu', app, $event)"
31
+ >
32
+ <span class="apphub-icon-folder__item-icon-wrap">
33
+ <span class="apphub-icon-folder__item-icon">{{ app.icon }}</span>
34
+ <span v-if="app.status === 'draft'" class="apphub-icon-folder__item-flag">D</span>
35
+ </span>
36
+ <span class="apphub-icon-folder__item-label">{{ app.name }}</span>
37
+ </button>
38
+ </div>
39
+ <p class="apphub-icon-folder__hint">{{ hint }}</p>
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <script setup>
45
+ defineProps({
46
+ open: { type: Boolean, default: false },
47
+ preview: { type: Boolean, default: false },
48
+ x: { type: Number, default: 0 },
49
+ y: { type: Number, default: 0 },
50
+ apps: { type: Array, default: () => [] },
51
+ title: { type: String, default: '' },
52
+ countLabel: { type: String, default: '' },
53
+ hint: { type: String, default: '' },
54
+ previewNewIds: { type: Array, default: () => [] },
55
+ isDragging: { type: Function, default: () => false },
56
+ isHolding: { type: Function, default: () => false },
57
+ })
58
+
59
+ const emit = defineEmits(['item-pointer-down', 'open-app', 'item-context-menu'])
60
+ </script>
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <button
3
+ type="button"
4
+ class="apphub-desktop__icon apphub-desktop__icon--placed apphub-desktop__icon--group"
5
+ :class="{
6
+ 'apphub-desktop__icon--dragging': dragging,
7
+ 'apphub-desktop__icon--holding': holding,
8
+ 'apphub-desktop__icon--drop-target': dropHighlight,
9
+ }"
10
+ :style="{ left: `${x}px`, top: `${y}px` }"
11
+ :title="title"
12
+ @mousedown.stop="emit('pointer-down', $event)"
13
+ @click.stop="emit('click')"
14
+ @contextmenu.prevent.stop="emit('context-menu', $event)"
15
+ >
16
+ <span class="apphub-desktop__group-preview">
17
+ <span
18
+ v-for="(app, i) in previewApps"
19
+ :key="app.id"
20
+ class="apphub-desktop__group-preview-item"
21
+ :style="previewStyle(i)"
22
+ >
23
+ {{ app.icon }}
24
+ </span>
25
+ </span>
26
+ <span class="apphub-desktop__icon-label" :title="label">{{ label }}</span>
27
+ </button>
28
+ </template>
29
+
30
+ <script setup>
31
+ import { computed } from 'vue'
32
+
33
+ const props = defineProps({
34
+ apps: { type: Array, default: () => [] },
35
+ x: { type: Number, required: true },
36
+ y: { type: Number, required: true },
37
+ label: { type: String, default: '' },
38
+ title: { type: String, default: '' },
39
+ dragging: { type: Boolean, default: false },
40
+ holding: { type: Boolean, default: false },
41
+ dropHighlight: { type: Boolean, default: false },
42
+ })
43
+
44
+ const emit = defineEmits(['pointer-down', 'click', 'context-menu'])
45
+
46
+ const previewApps = computed(() => props.apps.slice(0, 4))
47
+
48
+ const previewOffsets = [
49
+ { left: '4px', top: '4px' },
50
+ { left: '22px', top: '4px' },
51
+ { left: '4px', top: '22px' },
52
+ { left: '22px', top: '22px' },
53
+ ]
54
+
55
+ function previewStyle(index) {
56
+ return previewOffsets[index] ?? previewOffsets[0]
57
+ }
58
+ </script>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div v-if="open" class="apphub-icon-info" @click.self="emit('close')">
3
+ <div class="apphub-icon-info__panel" role="dialog" aria-modal="true">
4
+ <header class="apphub-icon-info__header">
5
+ <span class="apphub-icon-info__icon">{{ app?.icon }}</span>
6
+ <h3 class="apphub-icon-info__title">{{ title }}</h3>
7
+ </header>
8
+ <dl class="apphub-icon-info__list">
9
+ <div v-for="row in rows" :key="row.label" class="apphub-icon-info__row">
10
+ <dt>{{ row.label }}</dt>
11
+ <dd>{{ row.value }}</dd>
12
+ </div>
13
+ </dl>
14
+ <footer class="apphub-icon-info__actions">
15
+ <button type="button" class="apphub-icon-info__btn" @click="emit('close')">
16
+ {{ closeLabel }}
17
+ </button>
18
+ </footer>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup>
24
+ defineProps({
25
+ open: { type: Boolean, default: false },
26
+ title: { type: String, default: '' },
27
+ app: { type: Object, default: null },
28
+ rows: { type: Array, default: () => [] },
29
+ closeLabel: { type: String, default: '' },
30
+ })
31
+
32
+ const emit = defineEmits(['close'])
33
+ </script>
@@ -0,0 +1,62 @@
1
+ <template>
2
+ <div v-if="open" class="apphub-icon-rename" @click.self="emit('cancel')">
3
+ <div class="apphub-icon-rename__panel" role="dialog" aria-modal="true">
4
+ <h3 class="apphub-icon-rename__title">{{ title }}</h3>
5
+ <label class="apphub-icon-rename__label">
6
+ <span>{{ nameLabel }}</span>
7
+ <input
8
+ ref="inputRef"
9
+ v-model="draft"
10
+ type="text"
11
+ class="apphub-icon-rename__input"
12
+ maxlength="64"
13
+ @keydown.enter="onSave"
14
+ @keydown.esc="emit('cancel')"
15
+ />
16
+ </label>
17
+ <p v-if="error" class="apphub-icon-rename__error">{{ error }}</p>
18
+ <div class="apphub-icon-rename__actions">
19
+ <button type="button" class="apphub-icon-rename__btn apphub-icon-rename__btn--primary" @click="onSave">
20
+ {{ saveLabel }}
21
+ </button>
22
+ <button type="button" class="apphub-icon-rename__btn" @click="emit('cancel')">
23
+ {{ cancelLabel }}
24
+ </button>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup>
31
+ import { nextTick, ref, watch } from 'vue'
32
+
33
+ const props = defineProps({
34
+ open: { type: Boolean, default: false },
35
+ title: { type: String, default: '' },
36
+ nameLabel: { type: String, default: '' },
37
+ initialName: { type: String, default: '' },
38
+ saveLabel: { type: String, default: '' },
39
+ cancelLabel: { type: String, default: '' },
40
+ error: { type: String, default: '' },
41
+ })
42
+
43
+ const emit = defineEmits(['save', 'cancel'])
44
+
45
+ const draft = ref('')
46
+ const inputRef = ref(null)
47
+
48
+ watch(
49
+ () => props.open,
50
+ async (isOpen) => {
51
+ if (!isOpen) return
52
+ draft.value = props.initialName
53
+ await nextTick()
54
+ inputRef.value?.focus()
55
+ inputRef.value?.select()
56
+ },
57
+ )
58
+
59
+ function onSave() {
60
+ emit('save', draft.value.trim())
61
+ }
62
+ </script>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <div class="apphub-desktop-settings">
3
+ <label v-if="showThemeToggle" class="apphub-desktop-settings__row">
4
+ <input
5
+ type="checkbox"
6
+ :checked="theme === 'light'"
7
+ @change="emit('update:theme', $event.target.checked ? 'light' : 'dark')"
8
+ />
9
+ <span>{{ themeLabel }}</span>
10
+ </label>
11
+ <label class="apphub-desktop-settings__row">
12
+ <input type="checkbox" :checked="snapToGrid" @change="emit('update:snapToGrid', $event.target.checked)" />
13
+ <span>{{ snapLabel }}</span>
14
+ </label>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup>
19
+ defineProps({
20
+ theme: { type: String, default: 'dark' },
21
+ themeLabel: { type: String, default: '' },
22
+ showThemeToggle: { type: Boolean, default: true },
23
+ snapToGrid: { type: Boolean, default: true },
24
+ snapLabel: { type: String, default: '' },
25
+ })
26
+
27
+ const emit = defineEmits(['update:theme', 'update:snapToGrid'])
28
+ </script>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <div
3
+ class="apphub-drop-badge"
4
+ :class="{
5
+ 'apphub-drop-badge--installing': job.status === 'installing',
6
+ 'apphub-drop-badge--done': job.status === 'done',
7
+ 'apphub-drop-badge--error': job.status === 'error',
8
+ }"
9
+ :style="badgeStyle"
10
+ >
11
+ <div class="apphub-drop-badge__icon-wrap">
12
+ <div class="apphub-drop-badge__icon-bg" />
13
+ <span class="apphub-drop-badge__icon">{{ job.icon }}</span>
14
+ <svg v-if="job.status === 'installing'" class="apphub-drop-badge__ring" viewBox="0 0 64 64">
15
+ <circle class="apphub-drop-badge__ring-track" cx="32" cy="32" r="28" />
16
+ <circle
17
+ class="apphub-drop-badge__ring-progress"
18
+ cx="32"
19
+ cy="32"
20
+ r="28"
21
+ :style="{ strokeDashoffset: ringOffset }"
22
+ />
23
+ </svg>
24
+ <span v-if="job.status === 'done'" class="apphub-drop-badge__check">✓</span>
25
+ </div>
26
+ <p class="apphub-drop-badge__label">
27
+ <template v-if="job.status === 'installing'">{{ loadingLabel }}</template>
28
+ <template v-else-if="job.status === 'done'">{{ doneLabel }}</template>
29
+ <template v-else>{{ job.errorMessage || errorLabel }}</template>
30
+ </p>
31
+ <p v-if="job.status === 'installing'" class="apphub-drop-badge__method">{{ methodLabel }}</p>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { computed } from 'vue'
37
+
38
+ const props = defineProps({
39
+ job: { type: Object, required: true },
40
+ loadingLabel: { type: String, default: '' },
41
+ errorLabel: { type: String, default: '' },
42
+ methodLabel: { type: String, default: '' },
43
+ donePublishLabel: { type: String, default: '' },
44
+ })
45
+
46
+ const doneLabel = computed(() => {
47
+ if (props.job.publishSubmitted && props.job.name) {
48
+ return props.job.name
49
+ }
50
+ if (props.job.publishSubmitted && props.donePublishLabel) {
51
+ return props.donePublishLabel
52
+ }
53
+ return props.job.name
54
+ })
55
+
56
+ const badgeStyle = computed(() => ({
57
+ left: `${props.job.x}px`,
58
+ top: `${props.job.y}px`,
59
+ }))
60
+
61
+ const ringOffset = computed(() => {
62
+ const circumference = 2 * Math.PI * 28
63
+ return circumference - (circumference * props.job.progress) / 100
64
+ })
65
+ </script>
@@ -0,0 +1,38 @@
1
+ <template>
2
+ <div v-if="open" class="apphub-dup-dialog" @click.self="onCancel">
3
+ <div class="apphub-dup-dialog__panel" role="dialog" aria-modal="true">
4
+ <h3 class="apphub-dup-dialog__title">{{ title }}</h3>
5
+ <p class="apphub-dup-dialog__message">{{ message }}</p>
6
+ <p class="apphub-dup-dialog__hint">{{ hint }}</p>
7
+ <div class="apphub-dup-dialog__actions">
8
+ <button type="button" class="apphub-dup-dialog__btn apphub-dup-dialog__btn--primary" @click="emit('replace')">
9
+ {{ replaceLabel }}
10
+ </button>
11
+ <button type="button" class="apphub-dup-dialog__btn" @click="emit('keep')">
12
+ {{ keepLabel }}
13
+ </button>
14
+ <button type="button" class="apphub-dup-dialog__btn apphub-dup-dialog__btn--ghost" @click="onCancel">
15
+ {{ cancelLabel }}
16
+ </button>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script setup>
23
+ defineProps({
24
+ open: { type: Boolean, default: false },
25
+ title: { type: String, default: '' },
26
+ message: { type: String, default: '' },
27
+ hint: { type: String, default: '' },
28
+ replaceLabel: { type: String, default: '' },
29
+ keepLabel: { type: String, default: '' },
30
+ cancelLabel: { type: String, default: '' },
31
+ })
32
+
33
+ const emit = defineEmits(['replace', 'keep', 'cancel'])
34
+
35
+ function onCancel() {
36
+ emit('cancel')
37
+ }
38
+ </script>