@fdm-monster/client-next 0.0.1

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 (205) hide show
  1. package/.all-contributorsrc +57 -0
  2. package/.browserslistrc +4 -0
  3. package/.editorconfig +5 -0
  4. package/.env +1 -0
  5. package/.eslintrc-auto-import.json +73 -0
  6. package/.eslintrc.js +126 -0
  7. package/.github/FUNDING.yml +3 -0
  8. package/.github/workflows/release-client.yml +94 -0
  9. package/.github/workflows/vue-publish.yml +26 -0
  10. package/.prettierignore +15 -0
  11. package/.prettierrc.cjs +7 -0
  12. package/.whitesource +12 -0
  13. package/.yarn/releases/yarn-4.5.1.cjs +934 -0
  14. package/.yarnrc.yml +3 -0
  15. package/CODE_OF_CONDUCT.md +46 -0
  16. package/README.md +93 -0
  17. package/RELEASE_NOTES.MD +11 -0
  18. package/index.html +16 -0
  19. package/package.default.json +42 -0
  20. package/package.json +26 -0
  21. package/public/favicon.ico +0 -0
  22. package/public/img/DavidZwart.jpg +0 -0
  23. package/public/img/OIG.JYDC2RaWdz7g9.jpg +0 -0
  24. package/public/img/OIG.jpg +0 -0
  25. package/public/img/icons/android-chrome-192x192.png +0 -0
  26. package/public/img/icons/android-chrome-256x256.png +0 -0
  27. package/public/img/icons/android-chrome-384x384.png +0 -0
  28. package/public/img/icons/android-chrome-512x512.png +0 -0
  29. package/public/img/icons/favicon.svg +1 -0
  30. package/public/img/logo.png +0 -0
  31. package/public/img/logo.svg +1 -0
  32. package/public/img/manifest.webmanifest +33 -0
  33. package/public/img/octoprint-tentacle.svg +144 -0
  34. package/public/img/thumbail_unknown.jpg +0 -0
  35. package/public/img/vbanner.jpg +0 -0
  36. package/public/index.html +17 -0
  37. package/public/robots.txt +2 -0
  38. package/renovate.json +30 -0
  39. package/src/App.vue +60 -0
  40. package/src/AppLoader.vue +383 -0
  41. package/src/assets/adjectives.json +1468 -0
  42. package/src/assets/android-chrome-192x192.png +0 -0
  43. package/src/assets/logo.png +0 -0
  44. package/src/assets/logo.svg +6 -0
  45. package/src/assets/nouns.json +4309 -0
  46. package/src/auto-imports.d.ts +139 -0
  47. package/src/backend/app.service.ts +39 -0
  48. package/src/backend/auth.service.ts +56 -0
  49. package/src/backend/base.service.ts +57 -0
  50. package/src/backend/batch.service.ts +37 -0
  51. package/src/backend/camera-stream.service.ts +33 -0
  52. package/src/backend/custom-gcode.service.ts +11 -0
  53. package/src/backend/dto/octoprint-settings.dto.ts +168 -0
  54. package/src/backend/first-time-setup.service.ts +17 -0
  55. package/src/backend/floor.service.ts +84 -0
  56. package/src/backend/index.ts +4 -0
  57. package/src/backend/print-completions.service.ts +11 -0
  58. package/src/backend/printer-file.service.ts +91 -0
  59. package/src/backend/printer-group.service.ts +62 -0
  60. package/src/backend/printer-job.service.ts +20 -0
  61. package/src/backend/printer-settings.service.ts +28 -0
  62. package/src/backend/printers.service.ts +136 -0
  63. package/src/backend/server-private.service.ts +55 -0
  64. package/src/backend/server.api.ts +132 -0
  65. package/src/backend/settings.service.ts +85 -0
  66. package/src/backend/user.service.ts +51 -0
  67. package/src/components/AboutHelp/AboutView.vue +164 -0
  68. package/src/components/CameraGrid/CameraGridView.vue +111 -0
  69. package/src/components/FirstTimeSetup/FirstTimeSetupView.vue +354 -0
  70. package/src/components/Generic/Actions/PrinterConnectionAction.vue +56 -0
  71. package/src/components/Generic/Actions/PrinterCreateAction.vue +22 -0
  72. package/src/components/Generic/Actions/PrinterDeleteAction.vue +29 -0
  73. package/src/components/Generic/Actions/PrinterQuickStopAction.vue +35 -0
  74. package/src/components/Generic/Actions/PrinterSettingsAction.vue +35 -0
  75. package/src/components/Generic/Actions/PrinterUrlAction.vue +24 -0
  76. package/src/components/Generic/Actions/RefreshFilesAction.vue +50 -0
  77. package/src/components/Generic/Actions/SyncPrinterNameAction.vue +36 -0
  78. package/src/components/Generic/Dialogs/AddOrUpdateCameraStreamDialog.vue +131 -0
  79. package/src/components/Generic/Dialogs/AddOrUpdateFloorDialog.vue +141 -0
  80. package/src/components/Generic/Dialogs/AddOrUpdatePrinterDialog.vue +303 -0
  81. package/src/components/Generic/Dialogs/BaseDialog.vue +81 -0
  82. package/src/components/Generic/Dialogs/BatchJsonCreateDialog.vue +109 -0
  83. package/src/components/Generic/Dialogs/BatchReprintDialog.vue +190 -0
  84. package/src/components/Generic/Dialogs/PrinterChecksPanel.vue +37 -0
  85. package/src/components/Generic/Dialogs/PrinterControlDialog.vue +202 -0
  86. package/src/components/Generic/Dialogs/PrinterMaintenanceDialog.vue +130 -0
  87. package/src/components/Generic/Dialogs/YamlImportExportDialog.vue +186 -0
  88. package/src/components/Generic/Dialogs/dialog.constants.ts +19 -0
  89. package/src/components/Generic/FileExplorerSideNav.vue +734 -0
  90. package/src/components/Generic/Loaders/GridLoader.vue +68 -0
  91. package/src/components/Generic/NavigationDrawer.vue +69 -0
  92. package/src/components/Generic/PrintJobsMenu.vue +148 -0
  93. package/src/components/Generic/Snackbars/AppErrorSnackbar.vue +64 -0
  94. package/src/components/Generic/Snackbars/AppInfoSnackbar.vue +63 -0
  95. package/src/components/Generic/Snackbars/AppProgressSnackbar.vue +158 -0
  96. package/src/components/Generic/Vuetify/TooltipButton.vue +47 -0
  97. package/src/components/HelpOverlay/HelpOverlay.vue +57 -0
  98. package/src/components/Login/LoginForm.vue +206 -0
  99. package/src/components/Login/LoginView.spec.ts +64 -0
  100. package/src/components/Login/LoginView.vue +65 -0
  101. package/src/components/Login/Logo.vue +13 -0
  102. package/src/components/Login/PermissionDenied.vue +109 -0
  103. package/src/components/Login/RegistrationForm.vue +207 -0
  104. package/src/components/Login/RegistrationView.vue +17 -0
  105. package/src/components/Login/__snapshots__/LoginView.spec.ts.snap +1051 -0
  106. package/src/components/NotFound/NotFoundView.vue +39 -0
  107. package/src/components/PrintStatistics/PrintStatistics.vue +168 -0
  108. package/src/components/PrintStatistics/PrintStatisticsView.vue +15 -0
  109. package/src/components/PrinterGrid/HomeToolbar.vue +90 -0
  110. package/src/components/PrinterGrid/PrinterGrid.vue +164 -0
  111. package/src/components/PrinterGrid/PrinterGridTile.vue +438 -0
  112. package/src/components/PrinterGrid/PrinterGridView.vue +210 -0
  113. package/src/components/PrinterList/FileControlList.vue +40 -0
  114. package/src/components/PrinterList/PrinterDetails.vue +91 -0
  115. package/src/components/PrinterList/PrintersView.vue +492 -0
  116. package/src/components/Settings/AccountSettings.vue +163 -0
  117. package/src/components/Settings/DiagnosticsSettings.vue +137 -0
  118. package/src/components/Settings/EmergencyCommands.vue +265 -0
  119. package/src/components/Settings/FloorSettings.vue +276 -0
  120. package/src/components/Settings/GridSettings.vue +127 -0
  121. package/src/components/Settings/OctoPrintSettings.vue +188 -0
  122. package/src/components/Settings/ServerProtectionSettings.vue +370 -0
  123. package/src/components/Settings/SettingsView.vue +73 -0
  124. package/src/components/Settings/SoftwareUpgradeSettings.vue +297 -0
  125. package/src/components/Settings/UserManagementSettings.vue +257 -0
  126. package/src/components/TopBar.vue +147 -0
  127. package/src/components.d.ts +70 -0
  128. package/src/directives/file-upload.directive.ts +117 -0
  129. package/src/directives/printer-drop-position.directive.ts +92 -0
  130. package/src/env.d.ts +6 -0
  131. package/src/main.ts +76 -0
  132. package/src/models/batch/reprint.dto.ts +79 -0
  133. package/src/models/batch.model.ts +11 -0
  134. package/src/models/camera-streams/camera-stream.ts +19 -0
  135. package/src/models/floors/floor.model.ts +30 -0
  136. package/src/models/octoprint/connection-options.model.ts +8 -0
  137. package/src/models/plugins/firmware-updates/prusa-firmware-release.model.ts +57 -0
  138. package/src/models/print-completions/print-completions.model.ts +49 -0
  139. package/src/models/printers/crud/create-printer.model.ts +26 -0
  140. package/src/models/printers/file-upload-commands.model.ts +4 -0
  141. package/src/models/printers/gcode/gcode-analysis.model.ts +30 -0
  142. package/src/models/printers/printer-current-job.model.ts +90 -0
  143. package/src/models/printers/printer-file.model.ts +48 -0
  144. package/src/models/printers/printer.model.ts +18 -0
  145. package/src/models/server/client-releases.model.ts +27 -0
  146. package/src/models/server/export-yaml.model.ts +11 -0
  147. package/src/models/server/features.model.ts +37 -0
  148. package/src/models/server/github-rate-limit.model.ts +21 -0
  149. package/src/models/server/version.model.ts +14 -0
  150. package/src/models/settings/printer-file-clean-settings.model.ts +5 -0
  151. package/src/models/settings/server-settings.dto.ts +19 -0
  152. package/src/models/settings/settings.model.ts +57 -0
  153. package/src/models/socketio-messages/socketio-message.model.ts +53 -0
  154. package/src/models/uploads/queued-upload.model.ts +12 -0
  155. package/src/models/user.model.ts +15 -0
  156. package/src/plugins/README.md +3 -0
  157. package/src/plugins/index.ts +17 -0
  158. package/src/plugins/vuetify.ts +53 -0
  159. package/src/router/index.ts +192 -0
  160. package/src/router/route-names.ts +14 -0
  161. package/src/router/utils.ts +23 -0
  162. package/src/shared/alert.events.ts +14 -0
  163. package/src/shared/app.constants.ts +23 -0
  164. package/src/shared/auth.constants.ts +34 -0
  165. package/src/shared/dialog.composable.ts +41 -0
  166. package/src/shared/drag.constants.ts +19 -0
  167. package/src/shared/experimental.constants.ts +1 -0
  168. package/src/shared/http-client.ts +162 -0
  169. package/src/shared/noun-adjectives.data.ts +24 -0
  170. package/src/shared/printer-grid.constants.ts +5 -0
  171. package/src/shared/printer-state.constants.ts +194 -0
  172. package/src/shared/snackbar.composable.ts +66 -0
  173. package/src/shared/socketio.service.ts +104 -0
  174. package/src/store/auth.store.ts +255 -0
  175. package/src/store/connection.store.ts +66 -0
  176. package/src/store/dialog.store.ts +114 -0
  177. package/src/store/features.store.ts +57 -0
  178. package/src/store/floor.store.ts +173 -0
  179. package/src/store/grid.store.ts +10 -0
  180. package/src/store/index.ts +4 -0
  181. package/src/store/printer-state.store.ts +246 -0
  182. package/src/store/printer.store.ts +236 -0
  183. package/src/store/profile.store.ts +25 -0
  184. package/src/store/settings.store.ts +64 -0
  185. package/src/store/test-printer.store.ts +70 -0
  186. package/src/store/uploads.store.ts +75 -0
  187. package/src/styles/README.md +3 -0
  188. package/src/styles/settings.scss +10 -0
  189. package/src/types/global.d.ts +15 -0
  190. package/src/utils/array.utils.ts +15 -0
  191. package/src/utils/date.utils.ts +5 -0
  192. package/src/utils/download-file.util.ts +25 -0
  193. package/src/utils/error.utils.ts +3 -0
  194. package/src/utils/file-size.util.ts +11 -0
  195. package/src/utils/id.type.ts +1 -0
  196. package/src/utils/sentry.util.ts +8 -0
  197. package/src/utils/test.util.ts +30 -0
  198. package/src/utils/time.utils.ts +2 -0
  199. package/src/utils/uploads-state.utils.ts +58 -0
  200. package/src/utils/validation.utils.ts +14 -0
  201. package/src/vite-env.d.ts +7 -0
  202. package/test/setup-axios-mock.ts +15 -0
  203. package/tsconfig.json +47 -0
  204. package/tsconfig.node.json +9 -0
  205. package/vite.config.mts +106 -0
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <div
3
+ v-if="loading"
4
+ :style="getWrapperStyle()"
5
+ >
6
+ <div
7
+ v-for="index in 9"
8
+ :key="index"
9
+ :style="getCircleStyle(random(100))"
10
+ />
11
+ </div>
12
+ </template>
13
+
14
+ <script>
15
+ const random = (top) => Math.random() * top
16
+
17
+ export default {
18
+ name: 'GridLoader',
19
+ props: {
20
+ loading: { type: Boolean, default: true },
21
+ color: { type: String, default: '#000000' },
22
+ size: { type: Number, default: 15 },
23
+ margin: { type: String, default: '2px' },
24
+ sizeUnit: { type: String, default: 'px' }
25
+ },
26
+
27
+ methods: {
28
+ random,
29
+ getWrapperStyle() {
30
+ const width = `${parseFloat(this.size) * 3 + parseFloat(this.margin) * 6}${this.sizeUnit}`
31
+ return {
32
+ width: width,
33
+ fontSize: 0
34
+ }
35
+ },
36
+
37
+ getCircleStyle(rand) {
38
+ const animation = `grid ${rand / 100 + 0.6}s ${rand / 100 - 0.2}s infinite ease`
39
+ return {
40
+ display: 'inline-block',
41
+ backgroundColor: this.color,
42
+ width: `${this.size}${this.sizeUnit}`,
43
+ height: `${this.size}${this.sizeUnit}`,
44
+ margin: this.margin,
45
+ borderRadius: '100%',
46
+ animationFillMode: 'both',
47
+ animation: animation
48
+ }
49
+ }
50
+ }
51
+ }
52
+ </script>
53
+
54
+ <style>
55
+ @keyframes grid {
56
+ 0% {
57
+ transform: scale(1);
58
+ }
59
+ 50% {
60
+ transform: scale(0.5);
61
+ opacity: 0.7;
62
+ }
63
+ 100% {
64
+ transform: scale(1);
65
+ opacity: 1;
66
+ }
67
+ }
68
+ </style>
@@ -0,0 +1,69 @@
1
+ <template>
2
+ <v-navigation-drawer
3
+ permanent
4
+ rail
5
+ theme="dark"
6
+ class="nav-side-background"
7
+ >
8
+ <v-app-bar-nav-icon class="ml-1 mt-2">
9
+ <v-img
10
+ alt="FDM Monster Logo"
11
+ :src="imgLogo"
12
+ class="shrink mr-1 pt-3 ml-1"
13
+ transition="scale-transition"
14
+ width="40"
15
+ />
16
+ </v-app-bar-nav-icon>
17
+
18
+ <v-divider />
19
+
20
+ <v-list
21
+ density="compact"
22
+ nav
23
+ >
24
+ <v-list-item
25
+ v-for="([icon, title, path], i) in items"
26
+ :key="i"
27
+ :to="path"
28
+ :prepend-icon="icon"
29
+ :title="title"
30
+ density="comfortable"
31
+ router-link
32
+ />
33
+ </v-list>
34
+ </v-navigation-drawer>
35
+ </template>
36
+
37
+ <script lang="ts" setup>
38
+ import imgLogo from '@/assets/logo.png'
39
+
40
+ const items = [
41
+ ['home', 'Devices', '/'],
42
+ ['print', 'Printers', '/printers'],
43
+ ['camera_alt', 'CameraGridView', '/cameras'],
44
+ ['settings', 'Settings', '/settings'],
45
+ ['timeline', 'PrintStatistics', '/statistics'],
46
+ ['contact_support', 'About', '/about']
47
+ ]
48
+ </script>
49
+
50
+ <style>
51
+ /*https://mdbootstrap.com/docs/vue/css/background-image/*/
52
+ .nav-side-background {
53
+ background: -moz-linear-gradient(
54
+ 45deg,
55
+ rgba(18, 18, 18, 1),
56
+ rgba(155, 5, 5, 1) 100%
57
+ );
58
+ background: -webkit-linear-gradient(
59
+ 45deg,
60
+ rgba(18, 18, 18, 1),
61
+ rgba(155, 5, 5, 1) 100%
62
+ );
63
+ background: linear-gradient(
64
+ 45deg,
65
+ rgba(18, 18, 18, 1),
66
+ rgba(155, 5, 5, 1) 100%
67
+ );
68
+ }
69
+ </style>
@@ -0,0 +1,148 @@
1
+ <template>
2
+ <div class="text-center">
3
+ <v-menu
4
+ v-model="menu"
5
+ :close-on-content-click="false"
6
+ :nudge-width="400"
7
+ location="bottom right"
8
+ offset-x
9
+ offset-y
10
+ transition="slide-x-transition"
11
+ >
12
+ <template #activator="{ props }">
13
+ <v-btn
14
+ :color="activePrintCount ? 'green' : 'secondary'"
15
+ dark
16
+ v-bind="props"
17
+ >
18
+ <span>
19
+ Print jobs {{ activePrintCount ? `(${activePrintCount})` : '' }}
20
+ </span>
21
+ <v-icon end> work </v-icon>
22
+ </v-btn>
23
+ </template>
24
+
25
+ <v-card
26
+ class="d-flex flex-column"
27
+ min-width="300"
28
+ >
29
+ <v-list style="overflow-y: hidden; flex-shrink: 0">
30
+ <v-list-item>
31
+ <template #prepend>
32
+ <v-avatar
33
+ class="font-weight-bold"
34
+ color="primary"
35
+ size="44"
36
+ >
37
+ {{ activePrintCount }}
38
+ </v-avatar>
39
+ </template>
40
+
41
+ <v-list-item-title>
42
+ Print Jobs
43
+ <span class="float-end">
44
+ <v-btn
45
+ variant="tonal"
46
+ @click="menu = false"
47
+ >
48
+ <v-icon>close</v-icon>Close
49
+ </v-btn>
50
+ </span>
51
+ </v-list-item-title>
52
+
53
+ <v-list-item-action class="mt-2">
54
+ <v-text-field
55
+ v-model="searchString"
56
+ autofocus
57
+ class="p-2"
58
+ clearable
59
+ label="Search jobs or printers"
60
+ persistent-placeholder
61
+ placeholder="Type part of a filename or printer name to search"
62
+ prepend-icon="search"
63
+ style="min-width: 900px"
64
+ />
65
+ </v-list-item-action>
66
+ </v-list-item>
67
+ </v-list>
68
+
69
+ <v-divider />
70
+
71
+ <v-list style="overflow-y: auto; flex-shrink: 1">
72
+ <v-list-item v-if="!activePrintCount"> No active prints </v-list-item>
73
+ <v-list-item
74
+ v-for="{ printer, job } of activePrintJobs"
75
+ :key="printer.id"
76
+ lines="two"
77
+ >
78
+ <template #prepend>
79
+ <v-avatar size="70">
80
+ <v-progress-circular
81
+ :model-value="job?.progress?.completion"
82
+ :width="5"
83
+ color="green"
84
+ size="50"
85
+ >
86
+ {{ truncateProgress(job.progress?.completion) + '%' || '' }}
87
+ </v-progress-circular>
88
+ </v-avatar>
89
+ </template>
90
+
91
+ <v-list-item-title>
92
+ {{ job.job?.file?.name }}
93
+ </v-list-item-title>
94
+
95
+ <v-list-item-subtitle>
96
+ Elapsed:
97
+ {{ Math.round(job?.progress.printTime ?? 0 / 60) }} minutes
98
+ <br />
99
+ Printer: {{ printer.name }}
100
+ </v-list-item-subtitle>
101
+ </v-list-item>
102
+ </v-list>
103
+
104
+ <v-card-actions>
105
+ <v-spacer />
106
+
107
+ <v-btn @click="menu = false"> Close </v-btn>
108
+ </v-card-actions>
109
+ </v-card>
110
+ </v-menu>
111
+ </div>
112
+ </template>
113
+
114
+ <script lang="ts" setup>
115
+ import { usePrinterStateStore } from '@/store/printer-state.store'
116
+
117
+ const printerStateStore = usePrinterStateStore()
118
+ const searchString = ref('')
119
+ const menu = ref(false)
120
+
121
+ const activePrintJobs = computed(() => {
122
+ return printerStateStore.printersWithJob.filter((p) => {
123
+ const fileName = p.job?.job?.file.name
124
+ const fileNameSearch = fileName?.toLowerCase() || ''
125
+ const printerUrlSearch = p.printer.printerURL?.toLowerCase() || ''
126
+ const searchSearch = p.printer.name?.toLowerCase() || ''
127
+
128
+ const combineSearch = `${fileNameSearch} ${printerUrlSearch} ${searchSearch}`
129
+ return (
130
+ !searchString.value ||
131
+ combineSearch.includes(searchString.value.toLowerCase())
132
+ )
133
+ })
134
+ })
135
+
136
+ const activePrintCount = computed(() => {
137
+ return activePrintJobs.value.length || 0
138
+ })
139
+
140
+ function truncateProgress(progress: number) {
141
+ if (!progress) return ''
142
+ return progress?.toFixed(0)
143
+ }
144
+
145
+ watch(menu, () => {
146
+ searchString.value = ''
147
+ })
148
+ </script>
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <v-snackbar
3
+ v-model="snackbarOpened"
4
+ :timeout="snackbarTimeout"
5
+ absolute
6
+ location="bottom"
7
+ class="ml-16 mb-16 ma-3 elevation-24"
8
+ color="error-darken-1"
9
+ rounded="pill"
10
+ multi-line
11
+ style="z-index: 1000"
12
+ width="450"
13
+ >
14
+ <v-row>
15
+ <v-col cols="2">
16
+ <v-btn size="large">
17
+ <v-icon>error</v-icon>
18
+ <v-icon />
19
+ </v-btn>
20
+ </v-col>
21
+ <v-col
22
+ class="d-flex align-center flex-row"
23
+ cols="8"
24
+ >
25
+ <div>
26
+ <span class="font-weight-bold text-button">
27
+ {{ snackbarTitle }}
28
+ </span>
29
+ <div v-if="snackbarSubtitle?.length">
30
+ {{ snackbarSubtitle }}
31
+ </div>
32
+ </div>
33
+ </v-col>
34
+
35
+ <v-col cols="1">
36
+ <v-btn
37
+ size="large"
38
+ @click="snackbarOpened = false"
39
+ >
40
+ <v-icon>close</v-icon>
41
+ </v-btn>
42
+ </v-col>
43
+ </v-row>
44
+ </v-snackbar>
45
+ </template>
46
+ <script lang="ts" setup>
47
+ import { ErrorMessage, useSnackbar } from '@/shared/snackbar.composable'
48
+ import { onMounted, ref } from 'vue'
49
+
50
+ const snackbar = useSnackbar()
51
+ const snackbarTimeout = ref(-1)
52
+ const snackbarOpened = ref(false)
53
+ const snackbarTitle = ref('')
54
+ const snackbarSubtitle = ref('')
55
+
56
+ onMounted(() => {
57
+ snackbar.onErrorMessage((data: ErrorMessage) => {
58
+ snackbarTitle.value = data.title
59
+ snackbarSubtitle.value = data.subtitle ?? ''
60
+ snackbarOpened.value = true
61
+ snackbarTimeout.value = data.timeout ?? 10000
62
+ })
63
+ })
64
+ </script>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <v-snackbar
3
+ v-model="snackbarOpened"
4
+ absolute
5
+ location="bottom"
6
+ :color="isWarning ? 'warning darken-2' : 'success'"
7
+ class="ma-3 elevation-24"
8
+ rounded="pill"
9
+ :timeout="timeout"
10
+ style="z-index: 1000"
11
+ multi-line
12
+ >
13
+ <v-row>
14
+ <v-col cols="2">
15
+ <v-btn size="large">
16
+ <v-icon>info</v-icon>
17
+ </v-btn>
18
+ </v-col>
19
+ <v-col
20
+ cols="8"
21
+ class="d-flex align-center flex-row"
22
+ >
23
+ <div>
24
+ <span class="font-weight-bold text-button">
25
+ {{ infoTitle }}
26
+ </span>
27
+ <div v-if="infoSubtitle?.length">
28
+ {{ infoSubtitle }}
29
+ </div>
30
+ </div>
31
+ </v-col>
32
+ <v-col cols="1">
33
+ <v-btn
34
+ size="large"
35
+ @click="snackbarOpened = false"
36
+ >
37
+ <v-icon>close</v-icon>
38
+ </v-btn>
39
+ </v-col>
40
+ </v-row>
41
+ </v-snackbar>
42
+ </template>
43
+ <script lang="ts" setup>
44
+ import { InfoMessage, useSnackbar } from '@/shared/snackbar.composable'
45
+ import { onMounted, ref } from 'vue'
46
+
47
+ const snackbar = useSnackbar()
48
+ const snackbarOpened = ref(false)
49
+ const infoTitle = ref('')
50
+ const infoSubtitle = ref('')
51
+ const timeout = ref(2000)
52
+ const isWarning = ref(false)
53
+
54
+ onMounted(() => {
55
+ snackbar.onInfoMessage((data: InfoMessage) => {
56
+ infoTitle.value = data.title
57
+ infoSubtitle.value = data.subtitle ?? ''
58
+ isWarning.value = data.warning ?? false
59
+ timeout.value = data.timeout ?? 2000
60
+ snackbarOpened.value = true
61
+ })
62
+ })
63
+ </script>
@@ -0,0 +1,158 @@
1
+ <template>
2
+ <v-snackbar
3
+ v-model="snackbarOpened"
4
+ :timeout="progressTimeout"
5
+ absolute
6
+ location="bottom right"
7
+ class="ma-3 elevation-24"
8
+ min-width="450px"
9
+ multi-line
10
+ rounded="pill"
11
+ style="z-index: 1000"
12
+ >
13
+ <v-row>
14
+ <v-col cols="2">
15
+ <v-btn
16
+ icon
17
+ size="large"
18
+ >
19
+ <v-icon>file_upload</v-icon>
20
+ </v-btn>
21
+ </v-col>
22
+ <v-col
23
+ class="d-flex align-center flex-row"
24
+ cols="8"
25
+ >
26
+ <div style="width: 100%">
27
+ <span class="font-weight-bold text-button">
28
+ {{ snackbarTitle }}
29
+ </span>
30
+ <div
31
+ v-for="(progress, index) in progressTracked"
32
+ :key="index"
33
+ class="mb-2"
34
+ >
35
+ <v-icon v-if="progress.completed">check</v-icon>
36
+ <v-icon v-else-if="progress.timeoutAt">pause</v-icon>
37
+ <v-icon v-else>hourglass_bottom</v-icon>
38
+ {{ progress.title }}
39
+ {{ (progress.completed ? 100 : progress.value).toFixed(1) }}%
40
+ <br />
41
+ <v-progress-linear
42
+ :key="progress.key"
43
+ :model-value="progress.completed ? 100 : progress.value"
44
+ :color="progress.timeoutAt ? 'red' : 'success'"
45
+ />
46
+ </div>
47
+ </div>
48
+ </v-col>
49
+ <v-col cols="1">
50
+ <v-btn
51
+ icon
52
+ size="large"
53
+ @click="snackbarOpened = false"
54
+ >
55
+ <v-icon>close</v-icon>
56
+ </v-btn>
57
+ </v-col>
58
+ </v-row>
59
+ </v-snackbar>
60
+ </template>
61
+ <script lang="ts" setup>
62
+ import { ProgressMessage, useSnackbar } from '@/shared/snackbar.composable'
63
+ import { onMounted, ref } from 'vue'
64
+
65
+ const snackbar = useSnackbar()
66
+ const snackbarOpened = ref(false)
67
+ const snackbarTitle = ref('')
68
+
69
+ // Merged upload progress tracking
70
+ interface ProgressTracked {
71
+ value: number
72
+ key: string
73
+ title: string
74
+ completed: boolean
75
+ startedAt: number
76
+ expiresAt: number
77
+ timeoutAt?: number
78
+ }
79
+
80
+ const progressTracked = ref<ProgressTracked[]>([])
81
+ const progressTimeout = ref<number>(100)
82
+
83
+ function getProgressByKey(key: string) {
84
+ return progressTracked.value.find((p) => p.key === key)
85
+ }
86
+
87
+ function addProgressTracker(
88
+ // Tracking key
89
+ key: string,
90
+ title: string,
91
+ value: number = 0,
92
+ completed: boolean = false,
93
+ expiresAt: number = Date.now() + 1500
94
+ ) {
95
+ console.log(
96
+ `[AppProgressSnackbar] Adding ${key} tracker with progress ${value}`
97
+ )
98
+ progressTracked.value.push({
99
+ key,
100
+ title,
101
+ value,
102
+ completed,
103
+ startedAt: Date.now(),
104
+ expiresAt,
105
+ timeoutAt: undefined
106
+ })
107
+ }
108
+
109
+ function removeProgressTracker(key: string) {
110
+ progressTracked.value = progressTracked.value.filter((p) => p.key !== key)
111
+ }
112
+
113
+ onMounted(() => {
114
+ setInterval(() => {
115
+ if (!progressTracked.value.length) {
116
+ return
117
+ }
118
+
119
+ for (const progress of progressTracked.value) {
120
+ const { value, completed, expiresAt, key } = progress
121
+ if ((completed || value >= 100) && expiresAt < Date.now()) {
122
+ removeProgressTracker(key)
123
+ } else if (progress.timeoutAt && progress.timeoutAt < Date.now()) {
124
+ removeProgressTracker(key)
125
+ } else if (!progress.timeoutAt && expiresAt < Date.now()) {
126
+ progress.timeoutAt = Date.now() + 5000
127
+ }
128
+ }
129
+ if (!progressTracked.value.length) {
130
+ // Dwell the notification snackbar for a timeout duration
131
+ progressTimeout.value = 2000
132
+ snackbarTitle.value = 'Upload ended'
133
+ console.debug(
134
+ `[AppSnackbars] Setting timeout to ${progressTimeout.value}`
135
+ )
136
+ } else {
137
+ progressTimeout.value = -1
138
+ snackbarOpened.value = true
139
+ }
140
+ }, 1000)
141
+ snackbar.onProgressMessage((data: ProgressMessage) => {
142
+ const { key, value, title, completed } = data
143
+ const record = getProgressByKey(key)
144
+ if (!record) {
145
+ if (value >= 100) {
146
+ // If the value is above 100, don't consider it (bug/noise)
147
+ return
148
+ }
149
+ addProgressTracker(key, title, value, false, Date.now() + 1500)
150
+ } else if (Math.min(100, value) >= record.value) {
151
+ record.expiresAt = Date.now() + 1500
152
+ record.value = value
153
+ record.completed = completed
154
+ }
155
+ snackbarTitle.value = 'Uploading files'
156
+ })
157
+ })
158
+ </script>
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <v-tooltip :location="props.location">
3
+ <template #activator="{ props: tooltipProps }">
4
+ <v-btn
5
+ v-bind="tooltipProps"
6
+ variant="outlined"
7
+ class="ml-2"
8
+ :color="color"
9
+ @click="$emit('click')"
10
+ >
11
+ <slot>
12
+ <v-icon
13
+ v-if="props.icon?.length"
14
+ :class="'mr-2 ' + props.iconClass"
15
+ >
16
+ {{ props.icon }}
17
+ </v-icon>
18
+ {{ props.text }}
19
+ </slot>
20
+ </v-btn>
21
+ </template>
22
+ <span>
23
+ {{ props.tooltip }}
24
+ </span>
25
+ </v-tooltip>
26
+ </template>
27
+
28
+ <script lang="ts" setup>
29
+ defineEmits(['click'])
30
+
31
+ export interface Props {
32
+ tooltip: string
33
+ color?: string | 'success' | 'warning' | 'danger' | 'primary' | 'secondary'
34
+ icon?: string
35
+ iconClass?: string
36
+ text?: string
37
+ location?: 'start' | 'end' | 'left' | 'right' | 'top' | 'bottom'
38
+ }
39
+
40
+ const props = withDefaults(defineProps<Props>(), {
41
+ color: 'white',
42
+ icon: '',
43
+ iconClass: '',
44
+ text: '',
45
+ location: 'bottom'
46
+ })
47
+ </script>
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <v-dialog
3
+ v-model="isOpened"
4
+ fullscreen
5
+ eager
6
+ transition="dialog-bottom-transition"
7
+ width="100%"
8
+ >
9
+ <!-- Dialog Activator -->
10
+ <template #activator="{ props: dialogActivatorProps }">
11
+ <v-tooltip location="top">
12
+ <template #activator="{ props: tooltipProps }">
13
+ <v-btn
14
+ icon="help"
15
+ v-bind="{ ...dialogActivatorProps, ...tooltipProps }"
16
+ >
17
+ <v-icon>help</v-icon>
18
+ </v-btn>
19
+ </template>
20
+ <span> Load the online documentation webpage </span>
21
+ </v-tooltip>
22
+ </template>
23
+
24
+ <!-- Content-->
25
+ <template #default="{ isActive }">
26
+ <v-card
27
+ height="100%"
28
+ width="100%"
29
+ >
30
+ <v-toolbar color="primary">
31
+ <v-icon class="ml-5">help</v-icon>
32
+ <h2 class="ml-10">FDM Monster Documentation</h2>
33
+ <v-spacer />
34
+ <span class="mr-10"> docs.fdm-monster.net </span>
35
+
36
+ <v-btn
37
+ variant="flat"
38
+ @click="isActive.value = false"
39
+ >
40
+ <v-icon class="mr-2">close</v-icon>
41
+ Close help
42
+ </v-btn>
43
+ </v-toolbar>
44
+
45
+ <iframe
46
+ height="100%"
47
+ src="https://docs.fdm-monster.net"
48
+ style="background-color: black; border-width: 0"
49
+ width="100%"
50
+ />
51
+ </v-card>
52
+ </template>
53
+ </v-dialog>
54
+ </template>
55
+ <script setup lang="ts">
56
+ const isOpened = ref(false)
57
+ </script>