@opencor/opencor 0.20250813.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 (63) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +61 -0
  3. package/dist/index-BxzyBYAw.js +98574 -0
  4. package/dist/index.d.ts +8 -0
  5. package/dist/opencor.css +1 -0
  6. package/dist/opencor.es.js +5 -0
  7. package/dist/opencor.umd.js +6275 -0
  8. package/dist/quill-BGLY3Ud3.js +7521 -0
  9. package/package.json +90 -0
  10. package/src/App.vue +27 -0
  11. package/src/assets/app.css +14 -0
  12. package/src/assets/base.css +28 -0
  13. package/src/assets/logo.svg +17 -0
  14. package/src/common/common.ts +86 -0
  15. package/src/common/constants.ts +12 -0
  16. package/src/common/electron.ts +23 -0
  17. package/src/common/electronApi.ts +63 -0
  18. package/src/common/locCommon.ts +170 -0
  19. package/src/common/settings.ts +95 -0
  20. package/src/common/vueCommon.ts +50 -0
  21. package/src/components/BackgroundComponent.vue +26 -0
  22. package/src/components/ContentsComponent.vue +262 -0
  23. package/src/components/DragNDropComponent.vue +29 -0
  24. package/src/components/LoadOpencorComponent.vue +33 -0
  25. package/src/components/MainMenu.vue +220 -0
  26. package/src/components/OpenCOR.vue +546 -0
  27. package/src/components/SpinningWheelComponent.vue +15 -0
  28. package/src/components/dialogs/AboutDialog.vue +51 -0
  29. package/src/components/dialogs/BaseDialog.vue +11 -0
  30. package/src/components/dialogs/OpenRemoteDialog.vue +37 -0
  31. package/src/components/dialogs/ResetAllDialog.vue +13 -0
  32. package/src/components/dialogs/SettingsDialog.vue +42 -0
  33. package/src/components/dialogs/UpdateAvailableDialog.vue +16 -0
  34. package/src/components/dialogs/UpdateDownloadProgressDialog.vue +17 -0
  35. package/src/components/dialogs/UpdateErrorDialog.vue +18 -0
  36. package/src/components/dialogs/UpdateNotAvailableDialog.vue +12 -0
  37. package/src/components/propertyEditors/GraphsPropertyEditor.vue +3 -0
  38. package/src/components/propertyEditors/ParametersPropertyEditor.vue +3 -0
  39. package/src/components/propertyEditors/PropertyEditor.vue +61 -0
  40. package/src/components/propertyEditors/SimulationPropertyEditor.vue +45 -0
  41. package/src/components/propertyEditors/SolversPropertyEditor.vue +3 -0
  42. package/src/components/views/IssuesView.vue +37 -0
  43. package/src/components/views/SimulationExperimentUiView.vue +152 -0
  44. package/src/components/views/SimulationExperimentView.vue +214 -0
  45. package/src/components/widgets/GraphPanelWidget.vue +137 -0
  46. package/src/components/widgets/InputWidget.vue +128 -0
  47. package/src/libopencor/locApi.ts +167 -0
  48. package/src/libopencor/locFileApi.ts +263 -0
  49. package/src/libopencor/locLoggerApi.ts +36 -0
  50. package/src/libopencor/locSedApi.ts +486 -0
  51. package/src/libopencor/locUiJsonApi.ts +485 -0
  52. package/src/libopencor/locVersionApi.ts +17 -0
  53. package/src/libopencor/src/common.cpp +67 -0
  54. package/src/libopencor/src/common.h +27 -0
  55. package/src/libopencor/src/file.cpp +72 -0
  56. package/src/libopencor/src/file.h +15 -0
  57. package/src/libopencor/src/main.cpp +78 -0
  58. package/src/libopencor/src/sed.cpp +348 -0
  59. package/src/libopencor/src/sed.h +53 -0
  60. package/src/libopencor/src/version.cpp +8 -0
  61. package/src/libopencor/src/version.h +5 -0
  62. package/src/main.ts +5 -0
  63. package/src/types/types.d.ts +9 -0
@@ -0,0 +1,50 @@
1
+ import * as vue from 'vue'
2
+
3
+ // Some constants to know whether the operating system uses light mode or dark mode.
4
+
5
+ const prefersColorScheme = window.matchMedia('(prefers-color-scheme: light)')
6
+
7
+ export const isLightMode = vue.ref(prefersColorScheme.matches)
8
+ export const isDarkMode = vue.ref(!prefersColorScheme.matches)
9
+
10
+ prefersColorScheme.addEventListener('change', (event) => {
11
+ isLightMode.value = event.matches
12
+ isDarkMode.value = !event.matches
13
+ })
14
+
15
+ // A method to track the height of a given element.
16
+
17
+ export function trackElementHeight(id: string): void {
18
+ vue.onMounted(() => {
19
+ const element = document.getElementById(id)
20
+
21
+ if (element !== null) {
22
+ const observer = new ResizeObserver(() => {
23
+ let elementHeight = window.getComputedStyle(element).height
24
+
25
+ if (elementHeight === '' || elementHeight === 'auto') {
26
+ elementHeight = '0px'
27
+ }
28
+
29
+ const cssVariableName =
30
+ '--' +
31
+ id
32
+ .split('_')[0]
33
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
34
+ .toLowerCase() +
35
+ '-height'
36
+ const oldElementHeight = window.getComputedStyle(document.documentElement).getPropertyValue(cssVariableName)
37
+
38
+ if (oldElementHeight === '' || (elementHeight !== '0px' && oldElementHeight !== elementHeight)) {
39
+ document.documentElement.style.setProperty(cssVariableName, elementHeight)
40
+ }
41
+ })
42
+
43
+ observer.observe(element)
44
+
45
+ vue.onUnmounted(() => {
46
+ observer.disconnect()
47
+ })
48
+ }
49
+ })
50
+ }
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <img class="logo" src="../assets/logo.svg" />
3
+ </template>
4
+
5
+ <style scoped>
6
+ .logo {
7
+ /* Original dimensions:
8
+ * - Width: 828.12px; and
9
+ * - Height: 704.92px.
10
+ * Final dimensions:
11
+ * - Width: 303px + 8px = 311px; and
12
+ * - Height: 257.92px + 8px = ~266px
13
+ */
14
+
15
+ max-width: 311px;
16
+ max-height: 266px;
17
+ border-radius: 1rem;
18
+ padding: 0.5rem;
19
+ box-shadow: 0 0 0.75rem 0.375rem var(--p-content-border-color);
20
+ position: absolute;
21
+ top: 50%;
22
+ left: 50%;
23
+ transform: translate(-50%, -50%);
24
+ z-index: -1;
25
+ }
26
+ </style>
@@ -0,0 +1,262 @@
1
+ <template>
2
+ <div v-if="simulationOnly" class="h-full">
3
+ <div v-for="(fileTab, index) in fileTabs" :key="`tabPanel_${fileTab.file.path()}`" :value="fileTab.file.path()">
4
+ <IssuesView v-if="fileTab.file.issues().length !== 0" :issues="fileTab.file.issues()" />
5
+ <SimulationExperimentView
6
+ v-else-if="fileTab.uiJson === undefined"
7
+ :file="fileTabs[index].file"
8
+ :isActiveFile="fileTab.file.path() === activeFile"
9
+ :simulationOnly="true"
10
+ />
11
+ <SimulationExperimentUiView
12
+ v-else
13
+ :file="fileTabs[index].file"
14
+ :simulationOnly="true"
15
+ :uiJson="fileTabs[index].uiJson"
16
+ />
17
+ </div>
18
+ </div>
19
+ <div v-else class="h-full">
20
+ <BackgroundComponent v-show="fileTabs.length === 0" />
21
+ <Tabs
22
+ v-show="fileTabs.length !== 0"
23
+ id="fileTabs"
24
+ v-model:value="activeFile"
25
+ :scrollable="true"
26
+ :selectOnFocus="true"
27
+ >
28
+ <TabList id="fileTablist" class="file-tablist">
29
+ <Tab
30
+ v-for="fileTab in fileTabs"
31
+ :id="`tab_${fileTab.file.path()}`"
32
+ :key="`tab_${fileTab.file.path()}`"
33
+ :value="fileTab.file.path()"
34
+ >
35
+ <div class="flex gap-2 items-center">
36
+ <div>
37
+ {{
38
+ fileTab.file
39
+ .path()
40
+ .split(/(\\|\/)/g)
41
+ .pop()
42
+ }}
43
+ </div>
44
+ <div class="pi pi-times remove-button" @mousedown.prevent @click.stop="closeFile(fileTab.file.path())" />
45
+ </div>
46
+ </Tab>
47
+ </TabList>
48
+ <TabPanels class="p-0!">
49
+ <TabPanel
50
+ v-for="(fileTab, index) in fileTabs"
51
+ :key="`tabPanel_${fileTab.file.path()}`"
52
+ :value="fileTab.file.path()"
53
+ >
54
+ <IssuesView v-if="fileTab.file.issues().length !== 0" :issues="fileTab.file.issues()" />
55
+ <SimulationExperimentView
56
+ v-else-if="fileTab.uiJson === undefined"
57
+ :file="fileTabs[index].file"
58
+ :isActiveFile="fileTab.file.path() === activeFile"
59
+ />
60
+ <SimulationExperimentUiView v-else :file="fileTabs[index].file" :uiJson="fileTabs[index].uiJson" />
61
+ </TabPanel>
62
+ </TabPanels>
63
+ </Tabs>
64
+ </div>
65
+ </template>
66
+
67
+ <script setup lang="ts">
68
+ import * as vueusecore from '@vueuse/core'
69
+
70
+ import * as vue from 'vue'
71
+
72
+ import * as common from '../common/common'
73
+ import { electronApi } from '../common/electronApi'
74
+ import * as vueCommon from '../common/vueCommon'
75
+ import * as locApi from '../libopencor/locApi'
76
+
77
+ export interface IFileTab {
78
+ file: locApi.File
79
+ uiJson?: locApi.IUiJson
80
+ }
81
+
82
+ defineProps<{
83
+ simulationOnly?: boolean
84
+ }>()
85
+ defineExpose({ openFile, closeCurrentFile, closeAllFiles, hasFile, hasFiles, selectFile })
86
+
87
+ export interface IContentsComponent {
88
+ openFile(file: locApi.File): void
89
+ closeCurrentFile(): void
90
+ closeAllFiles(): void
91
+ hasFile(filePath: string): boolean
92
+ hasFiles(): boolean
93
+ selectFile(filePath: string): void
94
+ }
95
+
96
+ const fileTabs = vue.ref<IFileTab[]>([])
97
+ const activeFile = vue.ref<string>('')
98
+
99
+ const filePaths = vue.computed(() => {
100
+ const res: string[] = []
101
+
102
+ for (const fileTab of fileTabs.value) {
103
+ res.push(fileTab.file.path())
104
+ }
105
+
106
+ return res
107
+ })
108
+
109
+ vue.watch(filePaths, (filePaths) => {
110
+ electronApi?.filesOpened(filePaths)
111
+ })
112
+
113
+ vue.watch(activeFile, (filePath) => {
114
+ // Note: activeFile can get updated by clicking on a tab or by calling selectFile(), hence we need to watch it to let
115
+ // people know that a file has been selected.
116
+
117
+ electronApi?.fileSelected(filePath)
118
+ })
119
+
120
+ function openFile(file: locApi.File): void {
121
+ const filePath = file.path()
122
+ const prevActiveFile = activeFile.value
123
+
124
+ selectFile(filePath)
125
+
126
+ fileTabs.value.splice(fileTabs.value.findIndex((fileTab) => fileTab.file.path() === prevActiveFile) + 1, 0, {
127
+ file: file,
128
+ uiJson: file.uiJson()
129
+ })
130
+
131
+ electronApi?.fileOpened(filePath)
132
+ }
133
+
134
+ function hasFile(filePath: string): boolean {
135
+ return fileTabs.value.find((fileTab) => fileTab.file.path() === filePath) !== undefined
136
+ }
137
+
138
+ function hasFiles(): boolean {
139
+ return fileTabs.value.length > 0
140
+ }
141
+
142
+ function selectFile(filePath: string): void {
143
+ activeFile.value = filePath
144
+ }
145
+
146
+ function selectNextFile(): void {
147
+ const activeFileIndex = fileTabs.value.findIndex((fileTab) => fileTab.file.path() === activeFile.value)
148
+ const nextFileIndex = (activeFileIndex + 1) % fileTabs.value.length
149
+
150
+ selectFile(fileTabs.value[nextFileIndex].file.path())
151
+ }
152
+
153
+ function selectPreviousFile(): void {
154
+ const activeFileIndex = fileTabs.value.findIndex((fileTab) => fileTab.file.path() === activeFile.value)
155
+ const nextFileIndex = (activeFileIndex - 1 + fileTabs.value.length) % fileTabs.value.length
156
+
157
+ selectFile(fileTabs.value[nextFileIndex].file.path())
158
+ }
159
+
160
+ function closeFile(filePath: string): void {
161
+ locApi.fileManager.unmanage(filePath)
162
+
163
+ const activeFileIndex = fileTabs.value.findIndex((fileTab) => fileTab.file.path() === filePath)
164
+
165
+ fileTabs.value.splice(activeFileIndex, 1)
166
+
167
+ if (activeFile.value === filePath && fileTabs.value.length > 0) {
168
+ selectFile(fileTabs.value[Math.min(activeFileIndex, fileTabs.value.length - 1)].file.path())
169
+ }
170
+
171
+ electronApi?.fileClosed(filePath)
172
+ }
173
+
174
+ function closeCurrentFile(): void {
175
+ closeFile(activeFile.value)
176
+ }
177
+
178
+ function closeAllFiles(): void {
179
+ while (fileTabs.value.length > 0) {
180
+ closeCurrentFile()
181
+ }
182
+ }
183
+
184
+ // Track the height of our file tablist.
185
+
186
+ vueCommon.trackElementHeight('fileTablist')
187
+
188
+ // Keyboard shortcuts.
189
+
190
+ if (!common.isMobile()) {
191
+ vueusecore.onKeyStroke((event: KeyboardEvent) => {
192
+ if (fileTabs.value.length === 0) {
193
+ return
194
+ }
195
+
196
+ if (event.ctrlKey && !event.shiftKey && event.code === 'Tab') {
197
+ event.preventDefault()
198
+
199
+ selectNextFile()
200
+ } else if (event.ctrlKey && event.shiftKey && event.code === 'Tab') {
201
+ event.preventDefault()
202
+
203
+ selectPreviousFile()
204
+ }
205
+ })
206
+ }
207
+ </script>
208
+
209
+ <style scoped>
210
+ .file-tablist {
211
+ border-bottom: 1px solid var(--p-primary-color);
212
+ }
213
+
214
+ .p-tab {
215
+ padding: 0.25rem 0.5rem;
216
+ border-right: 1px solid var(--p-content-border-color);
217
+ }
218
+
219
+ .p-tab:first-of-type {
220
+ border-left: 1px solid var(--p-content-border-color);
221
+ }
222
+
223
+ .p-tab:hover {
224
+ background-color: var(--p-content-hover-background) !important;
225
+ }
226
+
227
+ .p-tab .remove-button {
228
+ visibility: hidden;
229
+ }
230
+
231
+ .p-tab:hover .remove-button,
232
+ .p-tab-active .remove-button {
233
+ visibility: visible;
234
+ }
235
+
236
+ .p-tab-active,
237
+ .p-tab-active:hover {
238
+ background-color: var(--p-primary-color) !important;
239
+ color: var(--p-primary-contrast-color);
240
+ }
241
+
242
+ :deep(.p-tablist-active-bar) {
243
+ display: none;
244
+ }
245
+
246
+ .remove-button {
247
+ padding: 0.15rem;
248
+ font-size: 0.75rem;
249
+ }
250
+
251
+ .remove-button:hover {
252
+ border-radius: var(--p-border-radius-sm);
253
+ background-color: var(--p-red-500);
254
+ color: var(--p-red-50);
255
+ }
256
+
257
+ @media (prefers-color-scheme: dark) {
258
+ .remove-button:hover {
259
+ background-color: var(--p-red-400);
260
+ }
261
+ }
262
+ </style>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div class="drop-area p-overlay-mask">
3
+ <div class="message">CellML files, SED-ML files, and COMBINE archives can be dropped here.</div>
4
+ </div>
5
+ </template>
6
+
7
+ <style scoped>
8
+ .drop-area {
9
+ width: 100%;
10
+ height: 100%;
11
+ border: 0.375rem dashed;
12
+ border-color: var(--p-primary-color);
13
+ z-index: 99999;
14
+ }
15
+
16
+ .message {
17
+ font-size: 1.5rem;
18
+ text-align: center;
19
+ border-radius: 1rem;
20
+ padding: 0.5rem 1rem;
21
+ background-color: var(--p-primary-color);
22
+ color: var(--p-primary-contrast-color);
23
+ box-shadow: 0 0 0.75rem 0.375rem var(--p-content-border-color);
24
+ position: fixed;
25
+ top: 50%;
26
+ left: 50%;
27
+ transform: translate(-50%, -50%);
28
+ }
29
+ </style>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div class="loading p-overlay-mask">
3
+ <Message class="message" severity="secondary">
4
+ <i class="message-icon pi pi-spin pi-cog" />
5
+ <br />
6
+ <span class="message-text">Loading OpenCOR...</span>
7
+ </Message>
8
+ </div>
9
+ </template>
10
+
11
+ <style scoped>
12
+ .loading {
13
+ z-index: 99999;
14
+ }
15
+
16
+ .message {
17
+ position: fixed;
18
+ top: 50%;
19
+ left: 50%;
20
+ transform: translate(-50%, -50%);
21
+ padding: 1.5rem 1rem 0.75rem;
22
+ }
23
+
24
+ .message-icon {
25
+ text-align: center;
26
+ display: block;
27
+ font-size: 3rem;
28
+ }
29
+
30
+ .message-text {
31
+ font-size: 1.5rem;
32
+ }
33
+ </style>
@@ -0,0 +1,220 @@
1
+ <template>
2
+ <Menubar id="mainMenu" :model="items">
3
+ <template #item="{ item, props }">
4
+ <a v-bind="props.action">
5
+ <div class="p-menubar-item-label">{{ item.label }}</div>
6
+ <svg
7
+ v-if="item.items !== undefined"
8
+ width="14"
9
+ height="14"
10
+ viewBox="0 0 14 14"
11
+ class="ml-auto p-icon p-menubar-submenu-icon"
12
+ >
13
+ <path
14
+ d="M5.25 11.1728C5.14929 11.1694 5.05033 11.1455 4.9592 11.1025C4.86806 11.0595 4.78666 10.9984 4.72 10.9228C4.57955 10.7822 4.50066 10.5916 4.50066 10.3928C4.50066 10.1941 4.57955 10.0035 4.72 9.86283L7.72 6.86283L4.72 3.86283C4.66067 3.71882 4.64765 3.55991 4.68275 3.40816C4.71785 3.25642 4.79932 3.11936 4.91585 3.01602C5.03238 2.91268 5.17819 2.84819 5.33305 2.83149C5.4879 2.81479 5.64411 2.84671 5.78 2.92283L9.28 6.42283C9.42045 6.56346 9.49934 6.75408 9.49934 6.95283C9.49934 7.15158 9.42045 7.34221 9.28 7.48283L5.78 10.9228C5.71333 10.9984 5.63193 11.0595 5.5408 11.1025C5.44966 11.1455 5.35071 11.1694 5.25 11.1728Z"
15
+ fill="currentColor"
16
+ />
17
+ </svg>
18
+ <div v-if="item.shortcut !== undefined" class="ml-auto border border-surface rounded bg-emphasis text-xs/3">
19
+ {{ item.shortcut }}
20
+ </div>
21
+ </a>
22
+ </template>
23
+ </Menubar>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import * as vueusecore from '@vueuse/core'
28
+
29
+ import * as vue from 'vue'
30
+
31
+ import * as common from '../common/common'
32
+
33
+ const props = defineProps<{
34
+ hasFiles: boolean
35
+ }>()
36
+
37
+ const emit = defineEmits([
38
+ 'about',
39
+ 'close',
40
+ 'closeAll',
41
+ 'open',
42
+ 'openRemote',
43
+ 'openSampleLorenz',
44
+ 'openSampleInteractiveLorenz',
45
+ 'settings'
46
+ ])
47
+ const isWindowsOrLinux = common.isWindows() || common.isLinux()
48
+ const isMacOs = common.isMacOs()
49
+
50
+ const items = [
51
+ {
52
+ label: 'File',
53
+ items: [
54
+ {
55
+ label: 'Open...',
56
+ shortcut: isWindowsOrLinux ? 'Ctrl+Alt+O' : isMacOs ? '⌘⌥O' : undefined,
57
+ command: () => {
58
+ emit('open')
59
+ }
60
+ },
61
+ {
62
+ label: 'Open Remote...',
63
+ shortcut: isWindowsOrLinux ? 'Ctrl+Shift+Alt+O' : isMacOs ? '⇧⌘⌥O' : undefined,
64
+ command: () => {
65
+ emit('openRemote')
66
+ }
67
+ },
68
+ {
69
+ label: 'Open Sample',
70
+ items: [
71
+ {
72
+ label: 'Lorenz',
73
+ command: () => {
74
+ emit('openSampleLorenz')
75
+ }
76
+ },
77
+ {
78
+ label: 'Interactive Lorenz',
79
+ command: () => {
80
+ emit('openSampleInteractiveLorenz')
81
+ }
82
+ }
83
+ ]
84
+ },
85
+ { separator: true },
86
+ {
87
+ label: 'Close',
88
+ shortcut: isWindowsOrLinux ? 'Ctrl+Alt+W' : isMacOs ? '⌘⌥W' : undefined,
89
+ command: () => {
90
+ emit('close')
91
+ },
92
+ disabled: () => !props.hasFiles
93
+ },
94
+ {
95
+ label: 'Close All',
96
+ command: () => {
97
+ emit('closeAll')
98
+ },
99
+ disabled: () => !props.hasFiles
100
+ }
101
+ ]
102
+ },
103
+ /*---OPENCOR--- Enable the settings menu once we have settings for OpenCOR's Web app.
104
+ {
105
+ label: 'Tools',
106
+ items: [
107
+ {
108
+ label: 'Settings...',
109
+ shortcut: isWindowsOrLinux ? 'Ctrl+Alt+,' : isMacOs ? '⌘⌥,' : undefined,
110
+ command: () => {
111
+ emit('settings')
112
+ }
113
+ }
114
+ ]
115
+ },
116
+ */
117
+ {
118
+ label: 'Help',
119
+ items: [
120
+ {
121
+ label: 'Home Page',
122
+ command: () => {
123
+ window.open('https://opencor.ws/')
124
+ }
125
+ },
126
+ { separator: true },
127
+ {
128
+ label: 'Report Issue',
129
+ command: () => {
130
+ window.open('https://github.com/opencor/webapp/issues/new')
131
+ }
132
+ },
133
+ { separator: true },
134
+ {
135
+ label: 'About OpenCOR',
136
+ command: () => {
137
+ emit('about')
138
+ }
139
+ }
140
+ ]
141
+ }
142
+ ]
143
+
144
+ // Never display our menu as a hamburger menu.
145
+
146
+ vue.onMounted(() => {
147
+ const mainMenu = document.getElementById('mainMenu')
148
+
149
+ if (mainMenu !== null) {
150
+ const observer = new MutationObserver(() => {
151
+ if (mainMenu.className.includes('p-menubar-mobile')) {
152
+ mainMenu.classList.remove('p-menubar-mobile')
153
+ }
154
+ })
155
+
156
+ observer.observe(mainMenu, { attributes: true })
157
+ }
158
+ })
159
+
160
+ // Keyboard shortcuts.
161
+
162
+ if (!common.isMobile()) {
163
+ vueusecore.onKeyStroke((event: KeyboardEvent) => {
164
+ if (common.isCtrlOrCmd(event) && !event.shiftKey && event.code === 'KeyO') {
165
+ event.preventDefault()
166
+
167
+ emit('open')
168
+ } else if (common.isCtrlOrCmd(event) && event.shiftKey && event.code === 'KeyO') {
169
+ event.preventDefault()
170
+
171
+ emit('openRemote')
172
+ } else if (props.hasFiles && common.isCtrlOrCmd(event) && !event.shiftKey && event.code === 'KeyW') {
173
+ event.preventDefault()
174
+
175
+ emit('close')
176
+ } else if (common.isCtrlOrCmd(event) && !event.shiftKey && event.code === 'Comma') {
177
+ event.preventDefault()
178
+
179
+ emit('settings')
180
+ }
181
+ })
182
+ }
183
+ </script>
184
+
185
+ <style scoped>
186
+ .p-menubar {
187
+ padding: 0.1rem;
188
+ border: none;
189
+ border-radius: 0;
190
+ border-bottom: 1px solid var(--border-color);
191
+ }
192
+
193
+ .p-menubar
194
+ > .p-menubar-root-list
195
+ > .p-menubar-item
196
+ > .p-menubar-item-content
197
+ > .p-menubar-item-link
198
+ .p-menubar-submenu-icon {
199
+ display: none;
200
+ }
201
+
202
+ .p-menubar-item-link {
203
+ padding: 0.25rem 0.5rem !important;
204
+ }
205
+
206
+ :deep(.p-menubar-root-list) {
207
+ gap: 0.1rem;
208
+ }
209
+
210
+ :deep(.p-menubar-submenu) {
211
+ padding: 0.1rem;
212
+ z-index: 10;
213
+ }
214
+
215
+ .shortcut {
216
+ border-color: var(--p-content-border-color);
217
+ background: var(--p-content-hover-background);
218
+ color: var(--p-text-muted-color);
219
+ }
220
+ </style>