@owdproject/core 3.1.3 → 3.1.4

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 (50) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +6 -7
  3. package/bin/owd.js +14 -14
  4. package/index.ts +5 -2
  5. package/module.ts +78 -61
  6. package/package.json +19 -17
  7. package/project.json +4 -0
  8. package/runtime/components/Core/Application/CoreApplicationRender.vue +6 -5
  9. package/runtime/components/Core/Application/CoreApplicationWindowsRender.vue +8 -8
  10. package/runtime/components/Core/Background/CoreBackground.vue +1 -1
  11. package/runtime/components/Core/Desktop/CoreDesktop.vue +31 -24
  12. package/runtime/components/Core/Explorer/CoreExplorerFile.vue +47 -38
  13. package/runtime/components/Core/Explorer/CoreExplorerFolder.vue +0 -1
  14. package/runtime/components/Core/Time/CoreTime.vue +10 -7
  15. package/runtime/components/Core/Window/CoreWindow.vue +49 -35
  16. package/runtime/components/Core/Window/CoreWindowContent.vue +3 -5
  17. package/runtime/components/Core/Window/CoreWindowNav.vue +12 -7
  18. package/runtime/composables/useApplicationEntries.ts +51 -0
  19. package/runtime/composables/useApplicationManager.ts +278 -5
  20. package/runtime/composables/useDesktopManager.ts +22 -4
  21. package/runtime/composables/useTerminalManager.ts +63 -4
  22. package/runtime/core/controllers/ApplicationController.ts +203 -192
  23. package/runtime/core/controllers/WindowController.ts +342 -334
  24. package/runtime/plugins/resize.client.ts +4 -4
  25. package/runtime/stores/storeApplication.ts +236 -0
  26. package/runtime/stores/storeApplicationMeta.ts +12 -8
  27. package/runtime/stores/storeApplicationWindows.ts +16 -12
  28. package/runtime/stores/storeDesktop.ts +35 -29
  29. package/runtime/stores/storeDesktopDefaultApps.ts +53 -0
  30. package/runtime/stores/storeDesktopVolume.ts +12 -12
  31. package/runtime/stores/storeDesktopWindow.ts +13 -13
  32. package/runtime/stores/storeDesktopWorkspace.ts +35 -31
  33. package/runtime/utils/utilApp.ts +27 -22
  34. package/runtime/utils/utilCommon.ts +43 -43
  35. package/runtime/utils/utilDebug.ts +17 -17
  36. package/runtime/utils/utilDesktop.ts +4 -4
  37. package/runtime/utils/utilTerminal.ts +6 -5
  38. package/runtime/utils/utilWindow.ts +11 -11
  39. package/test/fixtures/basic/app.vue +1 -2
  40. package/test/fixtures/basic/nuxt.config.ts +1 -3
  41. package/tsconfig.app.json +1 -1
  42. package/tsconfig.json +1 -1
  43. package/types/index.d.ts +235 -207
  44. package/vitest.config.ts +3 -3
  45. package/eslint.config.mjs +0 -30
  46. package/runtime/composables/useAppEntries.ts +0 -36
  47. package/runtime/composables/useClipboardFs.ts +0 -25
  48. package/runtime/core/managers/ApplicationManager.ts +0 -237
  49. package/runtime/core/managers/DesktopManager.ts +0 -37
  50. package/runtime/core/managers/TerminalManager.ts +0 -44
@@ -1,6 +1,16 @@
1
1
  <template>
2
- <li :class="['owd-explorer-file', { 'owd-explorer-file--inactive': props.inactive }]">
3
- <a :href="props.url" :target="props.target" :title="props.title" @click="props.fn">
2
+ <li
3
+ :class="[
4
+ 'owd-explorer-file',
5
+ { 'owd-explorer-file--inactive': props.inactive },
6
+ ]"
7
+ >
8
+ <a
9
+ :href="props.url"
10
+ :target="props.target"
11
+ :title="props.title"
12
+ @click="props.fn"
13
+ >
4
14
  <img :src="props.icon" :alt="props.title" />
5
15
  <div class="owd-explorer-file__name" v-html="props.name" />
6
16
  </a>
@@ -19,49 +29,48 @@ const props = defineProps<{
19
29
  </script>
20
30
 
21
31
  <style scoped lang="scss">
22
- li.file {
23
- width: 100px;
24
- margin: 0 5px 5px 0;
25
- text-align: center;
26
- float: left;
27
- border-radius: 2px;
28
- border: 1px solid rgba(0, 0, 0, 0);
32
+ li.file {
33
+ width: 100px;
34
+ margin: 0 5px 5px 0;
35
+ text-align: center;
36
+ float: left;
37
+ border-radius: 2px;
38
+ border: 1px solid rgba(0, 0, 0, 0);
29
39
 
30
- @media(max-width: 480px) {
31
- margin-right: 0;
32
- }
33
-
34
- @media(min-width: 480px) {
35
- &:not(.inactive):hover {
40
+ @media (max-width: 480px) {
41
+ margin-right: 0;
42
+ }
36
43
 
37
- }
44
+ @media (min-width: 480px) {
45
+ &:not(.inactive):hover {
38
46
  }
47
+ }
39
48
 
40
- &.inactive {
41
- filter: grayscale(100%);
42
- opacity: 0.2;
43
- }
49
+ &.inactive {
50
+ filter: grayscale(100%);
51
+ opacity: 0.2;
52
+ }
44
53
 
45
- a {
46
- display: block;
47
- color: #CCC;
48
- text-decoration: none;
49
- transition: color 0.15s ease-in-out;
50
- padding: 10px 0;
54
+ a {
55
+ display: block;
56
+ color: #ccc;
57
+ text-decoration: none;
58
+ transition: color 0.15s ease-in-out;
59
+ padding: 10px 0;
51
60
 
52
- img {
53
- vertical-align: middle;
54
- height: 32px;
55
- border-radius: 2px;
56
- }
61
+ img {
62
+ vertical-align: middle;
63
+ height: 32px;
64
+ border-radius: 2px;
65
+ }
57
66
 
58
- div.name {
59
- font-size: 12px;
60
- letter-spacing: -0.2px;
61
- line-height: 10px;
62
- margin-top: 10px;
63
- cursor: pointer;
64
- }
67
+ div.name {
68
+ font-size: 12px;
69
+ letter-spacing: -0.2px;
70
+ line-height: 10px;
71
+ margin-top: 10px;
72
+ cursor: pointer;
65
73
  }
66
74
  }
75
+ }
67
76
  </style>
@@ -6,6 +6,5 @@
6
6
 
7
7
  <style scoped lang="scss">
8
8
  .owd-explorer-folder {
9
-
10
9
  }
11
10
  </style>
@@ -1,17 +1,18 @@
1
1
  <script setup lang="ts">
2
- import {ref, onMounted, onUnmounted} from 'vue'
3
- import {computed} from "@vue/reactivity"
4
- import {useI18n} from "vue-i18n"
2
+ import { ref, onMounted, onUnmounted } from 'vue'
3
+ import { computed } from '@vue/reactivity'
4
+ import { useI18n } from 'vue-i18n'
5
5
 
6
6
  const date: Ref<Date> = ref(new Date())
7
- const {locale} = useI18n()
7
+ const { locale } = useI18n()
8
8
 
9
9
  let minuteTimeout: any
10
10
 
11
11
  onMounted(() => {
12
12
  function scheduleNextMinute() {
13
13
  const now = new Date()
14
- const millisecondsToNextMinute = (60 - now.getSeconds()) * 1000 - now.getMilliseconds()
14
+ const millisecondsToNextMinute =
15
+ (60 - now.getSeconds()) * 1000 - now.getMilliseconds()
15
16
  minuteTimeout = setTimeout(() => {
16
17
  date.value = new Date()
17
18
  scheduleNextMinute()
@@ -44,10 +45,12 @@ const time = computed(() => {
44
45
 
45
46
  if (minutes.length === 1) minutes = `0${minutes}`
46
47
 
47
- return locale.value === 'it' ? `${hours}:${minutes}` : `${hours}:${minutes} ${period}`
48
+ return locale.value === 'it'
49
+ ? `${hours}:${minutes}`
50
+ : `${hours}:${minutes} ${period}`
48
51
  })
49
52
  </script>
50
53
 
51
54
  <template>
52
55
  <time class="owd-time" v-text="time" />
53
- </template>
56
+ </template>
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
- import {ref,provide} from 'vue'
3
- import {computed} from "@vue/reactivity"
4
- import {useDesktopManager} from "@owdproject/core/runtime/composables/useDesktopManager"
5
- import {handleWindowControllerProps} from "@owdproject/core/runtime/utils/utilWindow"
2
+ import { ref, provide } from 'vue'
3
+ import { computed } from '@vue/reactivity'
4
+ import { useAppConfig } from 'nuxt/app'
5
+ import { useDesktopManager } from '@owdproject/core/runtime/composables/useDesktopManager'
6
+ import { handleWindowControllerProps } from '@owdproject/core/runtime/utils/utilWindow'
6
7
 
7
8
  const props = defineProps<{
8
9
  window?: IWindowController
@@ -24,11 +25,14 @@ const emit = defineEmits([
24
25
  'restore',
25
26
  'maximize',
26
27
  'unmaximize',
27
- 'toggle-maximize'
28
+ 'toggle-maximize',
28
29
  ])
29
30
 
31
+ const appConfig = useAppConfig()
30
32
  const desktopManager = useDesktopManager() // used in <style v-bind>
31
- const windowController: IWindowController = handleWindowControllerProps(props.window)
33
+ const windowController: IWindowController = handleWindowControllerProps(
34
+ props.window,
35
+ )
32
36
 
33
37
  provide<IWindowController>('windowController', windowController)
34
38
  provide<WindowContent>('windowContent', props.content ?? {})
@@ -54,7 +58,7 @@ function onWindowDragMove(data: any) {
54
58
  function onWindowDragEnd(data: { left: number; top: number }) {
55
59
  windowController?.actions?.setPosition({
56
60
  x: data.left,
57
- y: data.top
61
+ y: data.top,
58
62
  })
59
63
 
60
64
  isDragging.value = false
@@ -75,17 +79,22 @@ function onWindowResizeMove(data: any) {
75
79
  /**
76
80
  * Window end resize event
77
81
  */
78
- function onWindowResizeEnd(data: { left: number; top: number, width: number, height: number }) {
82
+ function onWindowResizeEnd(data: {
83
+ left: number
84
+ top: number
85
+ width: number
86
+ height: number
87
+ }) {
79
88
  isResizing.value = false
80
89
 
81
90
  windowController?.actions?.setPosition({
82
91
  x: data.left,
83
- y: data.top
92
+ y: data.top,
84
93
  })
85
94
 
86
95
  windowController?.actions?.setSize({
87
96
  width: data.width,
88
- height: data.height
97
+ height: data.height,
89
98
  })
90
99
 
91
100
  emit('resize:end', data)
@@ -112,37 +121,42 @@ const classes = computed(() => {
112
121
 
113
122
  <template>
114
123
  <vue-resizable
115
- ref="windowResizableController"
116
- :class="classes"
117
- :width="windowController?.size.width"
118
- :height="windowController?.size.height"
119
- :min-height="windowController?.size.minHeight"
120
- :max-width="windowController?.size.maxWidth"
121
- :max-height="windowController?.size.maxHeight"
122
- :left="windowController?.position?.x"
123
- :top="windowController?.position?.y"
124
- :active="windowController?.isResizable ? undefined : []"
125
- @drag:start="onWindowDragStart"
126
- @drag:move="onWindowDragMove"
127
- @drag:end="onWindowDragEnd"
128
- @resize:start="onWindowResizeStart"
129
- @resize:move="onWindowResizeMove"
130
- @resize:end="onWindowResizeEnd"
131
- :style="{zIndex: windowController?.position?.z}"
132
- :drag-selector="windowController?.isDraggable ? '.owd-window-nav__draggable' : '.owd-window-nav__draggable-none'"
133
- @pointerdown="onWindowPointerDown"
124
+ ref="windowResizableController"
125
+ :class="classes"
126
+ :width="windowController?.size.width"
127
+ :height="windowController?.size.height"
128
+ :min-width="windowController?.size.minWidth"
129
+ :min-height="windowController?.size.minHeight"
130
+ :max-width="windowController?.size.maxWidth"
131
+ :max-height="windowController?.size.maxHeight"
132
+ :left="windowController?.position?.x"
133
+ :top="windowController?.position?.y"
134
+ :active="windowController?.isResizable ? undefined : []"
135
+ fit-parent
136
+ @drag:start="onWindowDragStart"
137
+ @drag:move="onWindowDragMove"
138
+ @drag:end="onWindowDragEnd"
139
+ @resize:start="onWindowResizeStart"
140
+ @resize:move="onWindowResizeMove"
141
+ @resize:end="onWindowResizeEnd"
142
+ :style="{ zIndex: windowController?.position?.z }"
143
+ :drag-selector="
144
+ windowController?.isDraggable
145
+ ? '.owd-window-nav__draggable'
146
+ : '.owd-window-nav__draggable-none'
147
+ "
148
+ @pointerdown="onWindowPointerDown"
134
149
  >
135
-
136
- <slot/>
137
-
150
+ <slot />
138
151
  </vue-resizable>
139
152
  </template>
140
153
 
141
154
  <style scoped lang="scss">
142
155
  .owd-window {
143
- position: v-bind('desktopManager.config.windows.position');
156
+ position: v-bind('appConfig.desktop.windows.position');
144
157
 
145
- &--dragging, &--resizing {
158
+ &--dragging,
159
+ &--resizing {
146
160
  :deep(.owd-window__content) {
147
161
  pointer-events: none;
148
162
  }
@@ -153,4 +167,4 @@ const classes = computed(() => {
153
167
  height: 100%;
154
168
  }
155
169
  }
156
- </style>
170
+ </style>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import {inject} from "vue"
3
- import {computed} from "@vue/reactivity"
2
+ import { inject } from 'vue'
3
+ import { computed } from '@vue/reactivity'
4
4
 
5
5
  const windowContent = inject<WindowContent>('windowContent')
6
6
 
@@ -25,6 +25,4 @@ const classes = computed(() => {
25
25
  </div>
26
26
  </template>
27
27
 
28
- <style scoped lang="scss">
29
-
30
- </style>
28
+ <style scoped lang="scss"></style>
@@ -1,17 +1,23 @@
1
1
  <script setup lang="ts">
2
- import {inject} from "vue"
3
- import {computed} from "@vue/reactivity"
2
+ import { inject } from 'vue'
3
+ import { computed } from '@vue/reactivity'
4
4
 
5
5
  const windowController = inject<IWindowController>('windowController')
6
6
 
7
7
  const classes = computed(() => {
8
8
  const list = ['owd-window-nav']
9
9
 
10
- if (typeof windowController?.state.active === 'undefined' || !!windowController?.state.active) {
10
+ if (
11
+ typeof windowController?.state.active === 'undefined' ||
12
+ !!windowController?.state.active
13
+ ) {
11
14
  list.push('owd-window-nav--active')
12
15
  }
13
16
 
14
- if (typeof windowController?.state.focused === 'undefined' || !!windowController?.state.focused) {
17
+ if (
18
+ typeof windowController?.state.focused === 'undefined' ||
19
+ !!windowController?.state.focused
20
+ ) {
15
21
  list.push('owd-window-nav--focused')
16
22
  }
17
23
 
@@ -21,10 +27,9 @@ const classes = computed(() => {
21
27
 
22
28
  <template>
23
29
  <div :class="classes">
24
- <div class="owd-window-nav__draggable"/>
25
-
26
- <slot/>
30
+ <div class="owd-window-nav__draggable" />
27
31
 
32
+ <slot />
28
33
  </div>
29
34
  </template>
30
35
 
@@ -0,0 +1,51 @@
1
+ import { useApplicationManager } from './useApplicationManager'
2
+ import { computed } from '@vue/reactivity'
3
+
4
+ type SortBy =
5
+ | 'title'
6
+ | 'category'
7
+ | ((
8
+ a: ApplicationEntryWithInherited,
9
+ b: ApplicationEntryWithInherited,
10
+ ) => number)
11
+ type Visibility =
12
+ | 'primary'
13
+ | 'all'
14
+ | ((entry: ApplicationEntryWithInherited) => boolean)
15
+
16
+ export function useApplicationEntries() {
17
+ const applicationManager = useApplicationManager()
18
+
19
+ const sortedAppEntries = function (
20
+ sortBy: SortBy = 'title',
21
+ visibility: Visibility = 'primary',
22
+ ): Ref<ApplicationEntryWithInherited[]> {
23
+ return computed(() => {
24
+ const currentEntries = [...applicationManager.appsEntries.value]
25
+
26
+ // filtering
27
+ const filtered =
28
+ typeof visibility === 'function'
29
+ ? currentEntries.filter(visibility)
30
+ : visibility === 'primary'
31
+ ? currentEntries.filter((e) => e.visibility !== 'secondary')
32
+ : currentEntries
33
+
34
+ // sorting
35
+ const sorted =
36
+ typeof sortBy === 'function'
37
+ ? filtered.sort(sortBy)
38
+ : sortBy === 'title'
39
+ ? filtered.sort((a, b) =>
40
+ (a.title || '').localeCompare(b.title || ''),
41
+ )
42
+ : filtered.sort((a, b) =>
43
+ (a.category || '').localeCompare(b.category || ''),
44
+ )
45
+
46
+ return sorted
47
+ })
48
+ }
49
+
50
+ return { sortedAppEntries }
51
+ }
@@ -1,7 +1,280 @@
1
- import {ApplicationManager} from "../core/managers/ApplicationManager";
1
+ import { reactive, markRaw, computed } from 'vue'
2
+ import { ApplicationController } from '../core/controllers/ApplicationController'
3
+ import { normalizeApplicationConfig } from '../utils/utilApp'
4
+ import { debugLog } from '../utils/utilDebug'
5
+ import * as shellwords from 'shellwords'
6
+ import getopts from "getopts"
2
7
 
3
- const applicationManager = new ApplicationManager()
8
+ const apps = reactive(new Map<string, IApplicationController>())
4
9
 
5
- export function useApplicationManager(): IApplicationManager {
6
- return applicationManager
7
- }
10
+ /**
11
+ * Application Manager Composable (Singleton)
12
+ */
13
+ export function useApplicationManager() {
14
+ /**
15
+ * Define new application
16
+ *
17
+ * @param id
18
+ * @param config
19
+ */
20
+ async function defineApp(id: string, config: ApplicationConfig) {
21
+ if (isAppDefined(id)) {
22
+ debugLog(`App "${id}" is already defined`)
23
+ return getAppById(id)!
24
+ }
25
+
26
+ const normalizedConfig = normalizeApplicationConfig(config)
27
+ const applicationConfig = markRaw(normalizedConfig)
28
+
29
+ const applicationController: IApplicationController = new ApplicationController(id, applicationConfig)
30
+ await applicationController.initApplication()
31
+
32
+ apps.set(id, applicationController)
33
+
34
+ return applicationController
35
+ }
36
+
37
+ /**
38
+ * Check if app has been defined
39
+ *
40
+ * @param {string} id
41
+ */
42
+ function isAppDefined(id: string) {
43
+ return getAppById(id)
44
+ }
45
+
46
+ /**
47
+ * Retrieves an app instance by its unique identifier
48
+ *
49
+ * @param {string} id
50
+ */
51
+ function getAppById(id: string) {
52
+ return apps.get(id)
53
+ }
54
+
55
+ /**
56
+ * Check if app is running
57
+ *
58
+ * @param {string} id
59
+ */
60
+ function isAppRunning(id: string) {
61
+ if (!isAppDefined(id)) {
62
+ throw Error(`App "${id}" is not defined`)
63
+ }
64
+
65
+ const applicationController = getAppById(id)!
66
+ return applicationController.isRunning
67
+ }
68
+
69
+ /**
70
+ * Launch app entry
71
+ *
72
+ * @param id
73
+ * @param entryKey
74
+ * @param argument
75
+ */
76
+ async function launchAppEntry(
77
+ id: string,
78
+ entryKey: string,
79
+ argument: string = ''
80
+ ): Promise<IApplicationController | undefined | void> {
81
+ if (!isAppDefined(id)) {
82
+ throw Error(`App "${id}" is not defined`)
83
+ }
84
+
85
+ const applicationController = getAppById(id)!
86
+
87
+ if (
88
+ applicationController.config.entries &&
89
+ !applicationController.config.entries.hasOwnProperty(entryKey)
90
+ ) {
91
+ throw Error(`App entry "${entryKey}" is not defined in ${id} application`)
92
+ }
93
+
94
+ const entry: ApplicationEntry = applicationController.config.entries[entryKey]!
95
+
96
+ if (argument) argument = ` ${argument}`
97
+
98
+ await execAppCommand(applicationController.id, entry.command + argument)
99
+ }
100
+
101
+ /**
102
+ * Run app command
103
+ *
104
+ * @param id
105
+ * @param rawCommand
106
+ */
107
+ async function execAppCommand(
108
+ id: string,
109
+ rawCommand: string,
110
+ ): Promise<CommandOutput> {
111
+ if (!isAppDefined(id)) {
112
+ throw Error(`App "${id}" is not defined`)
113
+ }
114
+
115
+ const applicationController = getAppById(id)!
116
+
117
+ const args = shellwords.split(rawCommand)
118
+ const parsed = getopts(args)
119
+ const command: string = parsed._[0]
120
+
121
+ if (
122
+ applicationController.config.commands &&
123
+ !applicationController.config.commands.hasOwnProperty(command)
124
+ ) {
125
+ throw Error(
126
+ `App command "${command}" is not defined in ${id} application`,
127
+ )
128
+ }
129
+
130
+ const commandFn: any = applicationController.config.commands![command]
131
+
132
+ const commandOutput = await commandFn(applicationController, parsed)
133
+
134
+ applicationController.setRunning(true)
135
+
136
+ return commandOutput
137
+ }
138
+
139
+ /**
140
+ * Close application
141
+ *
142
+ * @param id
143
+ */
144
+ function closeApp(id: string) {
145
+ if (!isAppDefined(id)) {
146
+ throw Error(`App "${id}" is not defined`)
147
+ }
148
+
149
+ const applicationController = getAppById(id)!
150
+
151
+ applicationController.closeAllWindows()
152
+ applicationController.setRunning(false)
153
+ }
154
+
155
+ /**
156
+ * Array of available menu entries for system bars, docks
157
+ */
158
+ const appsEntries = computed(() => {
159
+ const entries: ApplicationEntryWithInherited[] = []
160
+
161
+ for (const applicationController of apps.values()) {
162
+ if (!applicationController.config.entries) {
163
+ continue
164
+ }
165
+
166
+ for (const entryKey of Object.keys(applicationController.config.entries)) {
167
+ const entry = applicationController.config.entries[entryKey]!
168
+
169
+ entries.push({
170
+ application: applicationController,
171
+ title: entry.title ?? applicationController.config.title,
172
+ icon: entry.icon ?? applicationController.config.icon,
173
+ category: entry.category ?? applicationController.config.category,
174
+ visibility: entry.visibility ?? 'primary',
175
+ command: entry.command,
176
+ })
177
+ }
178
+ }
179
+
180
+ return entries
181
+ })
182
+
183
+ /**
184
+ * Array of opened windows for system bars, docks
185
+ */
186
+ const windowsOpened = computed(() => {
187
+ const windows: Map<string, IWindowController>[] = []
188
+
189
+ for (const applicationController of apps.values()) {
190
+ if (applicationController.isRunning) {
191
+ windows.push(...applicationController.windows)
192
+ }
193
+ }
194
+
195
+ return windows
196
+ })
197
+
198
+ /**
199
+ * Array of running applications
200
+ */
201
+ const appsRunning = computed(() => {
202
+ const runningApps: IApplicationController[] = []
203
+
204
+ for (const applicationController of apps.values()) {
205
+ if (applicationController.isRunning) {
206
+ runningApps.push(applicationController)
207
+ }
208
+ }
209
+
210
+ return runningApps
211
+ })
212
+
213
+ function getWindowOpenedId(windowId: string) {
214
+ const mapWindowFound = windowsOpened.value.find((window: any) => {
215
+ return window[1].state.id === windowId
216
+ })
217
+
218
+ if (mapWindowFound) {
219
+ // @ts-ignore
220
+ return mapWindowFound[1] as IWindowController
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Gets all unique categories from the installed apps
226
+ */
227
+ const appCategories = computed(() => {
228
+ const categories = new Set<string>()
229
+
230
+ for (const applicationController of apps.values()) {
231
+ if (applicationController.config.category) {
232
+ categories.add(applicationController.config.category)
233
+ }
234
+ }
235
+
236
+ return Array.from(categories).sort()
237
+ })
238
+
239
+ /**
240
+ * Gets the apps ordered by category
241
+ */
242
+ const appsByCategory = computed(() => {
243
+ const categorizedApps: {
244
+ [category: string]: IApplicationController[]
245
+ } = {}
246
+
247
+ for (const applicationController of apps.values()) {
248
+ const category = applicationController.config.category || 'other'
249
+ if (!categorizedApps[category]) {
250
+ categorizedApps[category] = []
251
+ }
252
+ categorizedApps[category].push(applicationController)
253
+ }
254
+
255
+ for (const category in categorizedApps) {
256
+ categorizedApps[category].sort((a, b) =>
257
+ a.config.title.localeCompare(b.config.title),
258
+ )
259
+ }
260
+
261
+ return categorizedApps
262
+ })
263
+
264
+ return {
265
+ apps,
266
+ defineApp,
267
+ isAppDefined,
268
+ getAppById,
269
+ isAppRunning,
270
+ launchAppEntry,
271
+ execAppCommand,
272
+ closeApp,
273
+ getWindowOpenedId,
274
+ appsEntries,
275
+ appsRunning,
276
+ windowsOpened,
277
+ appCategories,
278
+ appsByCategory,
279
+ }
280
+ }