c0ckp1t 1.0.11 → 1.0.13

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.
package/Config.mjs ADDED
@@ -0,0 +1,248 @@
1
+ import {deepMerge, DEFAULTS} from "CoreUtils";
2
+ /**
3
+ * Configuration Factory for C0ckp1t Applications
4
+ *
5
+ * Usage:
6
+ * import { createConfig } from './Config.mjs';
7
+ *
8
+ * // Local mode (all defaults)
9
+ * const config = createConfig();
10
+ *
11
+ * // CDN mode
12
+ * const config = createConfig({
13
+ * appEndpoint: "https://cdn.jsdelivr.net/npm/c0ckp1t@latest"
14
+ * });
15
+ *
16
+ * // Custom instance
17
+ * const config = createConfig({
18
+ * instanceId: "myapp",
19
+ * appName: "My App",
20
+ * isDev: false,
21
+ * });
22
+ */
23
+ // ________________________________________________________________________________
24
+ // Default Nav Tree Builder
25
+ // ________________________________________________________________________________
26
+ function buildNavTree(instanceId) {
27
+ return {
28
+ icon: "fa-house",
29
+ depth: 0,
30
+ endpoint: "/",
31
+ isLeaf: false,
32
+ isRoot: true,
33
+ name: "",
34
+ path: [],
35
+ children: [
36
+ {
37
+ depth: 1,
38
+ endpoint: `/${instanceId}/connections`,
39
+ isLeaf: true,
40
+ isRoot: false,
41
+ path: ["connections"],
42
+ name: "Connections",
43
+ children: []
44
+ },
45
+ {
46
+ depth: 1,
47
+ endpoint: `/${instanceId}/cache`,
48
+ isLeaf: true,
49
+ isRoot: false,
50
+ path: ["cache"],
51
+ name: "Cache",
52
+ children: []
53
+ },
54
+ {
55
+ icon: "fa-network-wired",
56
+ depth: 1,
57
+ endpoint: `/${instanceId}/traffic`,
58
+ isLeaf: true,
59
+ isRoot: false,
60
+ path: ["traffic"],
61
+ name: "Traffic",
62
+ children: []
63
+ },
64
+ {
65
+ icon: "fa-bell",
66
+ depth: 1,
67
+ endpoint: `/${instanceId}/notifies`,
68
+ isLeaf: true,
69
+ isRoot: false,
70
+ path: ["notifies"],
71
+ name: "Notifies",
72
+ children: []
73
+ },
74
+ {
75
+ icon: "fa-info",
76
+ depth: 1,
77
+ endpoint: `/${instanceId}/docs`,
78
+ isLeaf: true,
79
+ isRoot: false,
80
+ path: ["docs"],
81
+ name: "Documentation",
82
+ children: []
83
+ },
84
+ {
85
+ icon: "fa-info",
86
+ depth: 1,
87
+ endpoint: `/${instanceId}/components`,
88
+ isLeaf: true,
89
+ isRoot: false,
90
+ path: ["components"],
91
+ name: "Components",
92
+ children: [
93
+ {
94
+ icon: "fa-info",
95
+ depth: 2,
96
+ endpoint: `/${instanceId}/components/bootstrap`,
97
+ isLeaf: true,
98
+ isRoot: false,
99
+ path: ["bootstrap"],
100
+ name: "Bootstrap",
101
+ children: []
102
+ },
103
+ {
104
+ icon: "fa-info",
105
+ depth: 2,
106
+ endpoint: `/${instanceId}/components/basic`,
107
+ isLeaf: true,
108
+ isRoot: false,
109
+ path: ["basic"],
110
+ name: "Basic",
111
+ children: []
112
+ },
113
+ {
114
+ icon: "fa-info",
115
+ depth: 2,
116
+ endpoint: `/${instanceId}/components/advanced`,
117
+ isLeaf: true,
118
+ isRoot: false,
119
+ path: ["advanced"],
120
+ name: "Advanced",
121
+ children: []
122
+ },
123
+ {
124
+ icon: "fa-info",
125
+ depth: 2,
126
+ endpoint: `/${instanceId}/components/theme`,
127
+ isLeaf: true,
128
+ isRoot: false,
129
+ path: ["theme"],
130
+ name: "Theme",
131
+ children: []
132
+ },
133
+ ]
134
+ }
135
+ ]
136
+ };
137
+ }
138
+
139
+ // ________________________________________________________________________________
140
+ // Routes Builder
141
+ // ________________________________________________________________________________
142
+ function buildRoutes(instanceId = "default", prefix = "") {
143
+ return [
144
+ { path: '/', name: 'root', children: [
145
+ {path: '', redirect: `/${instanceId}/docs/Introduction.md`},
146
+ {path: `${instanceId}`, children: [
147
+ {path: 'docs', redirect: `/${instanceId}/docs/Introduction.md`},
148
+ {path: 'docs/:pathMatch(.*)*', location: `${prefix}/core/pages/Documentation.vue`},
149
+ {path: 'connections', location: `${prefix}/core/pages/Connections.vue`},
150
+ {path: 'connections/:id', location: `${prefix}/core/pages/Connection.vue`},
151
+ {path: 'cache', location: `${prefix}/core/pages/Cache.vue`},
152
+ {path: 'traffic', location: `${prefix}/core/pages/Traffic.vue`},
153
+ {path: 'notifies', location: `${prefix}/core/pages/Notifies.vue`},
154
+ {path: 'components', location: `${prefix}/core/pages/frontend/Components.vue`, children: [
155
+ {path: 'basic', location: `${prefix}/core/pages/frontend/ComponentsBasic.vue`},
156
+ {path: 'advanced', location: `${prefix}/core/pages/frontend/ComponentsAdv.vue`},
157
+ {path: 'theme', location: `${prefix}/core/pages/frontend/Theme.vue`},
158
+ {path: 'bootstrap', location: `${prefix}/core/pages/frontend/Bootstrap.vue`},
159
+ ]},
160
+ ]}
161
+ ] },
162
+ { path: '/:pathMatch(.*)*', name: '404', location: `${prefix}/core/Page404.vue` }
163
+ ];
164
+ }
165
+
166
+ // ________________________________________________________________________________
167
+ // Components
168
+ // ________________________________________________________________________________
169
+ /**
170
+ * Return the default Vue components
171
+ * using sha1 hashes
172
+ * @returns {Object}
173
+ */
174
+ export function defaultVueComponents(prefix = "") {
175
+ return {
176
+ ExecButton: { path: `${prefix}/components/ExecButton.vue`, hash: `97e3d2ce89808c5a69f41404e1337f743015f0cc` },
177
+ XInput: { path: `${prefix}/components/xinput.vue`, hash: `cd47d7d038316367df6fd7e265aa3625b72a7777` },
178
+ XInput2: { path: `${prefix}/components/xinput2.vue`, hash: `320e52ac991baebded7c2a9f52a4cfc2cde47b55` },
179
+ XLabel: { path: `${prefix}/components/xlabel.vue`, hash: `90a9837aa8e06f1d3a5b7601337afd78a872d181` },
180
+ XDropdown: { path: `${prefix}/components/xdropdown.vue`, hash: `52b29b72fd6512eefccd9caa76e26dd91d9f9f9e` },
181
+ XDropdown2: { path: `${prefix}/components/xdropdown2.vue`, hash: `e7b163eda42fb7e1a6f07b011c423f8f275eb65d` },
182
+ XSection: { path: `${prefix}/components/xsection.vue`, hash: `27285d606f57d13f80156bb11cd34449f88df950` },
183
+ XTableOpen: { path: `${prefix}/components/xtable-open.vue`, hash: `a1b9a4f670817978022a4c596e2e2f89dd4568c2` },
184
+ XCollapse: { path: `${prefix}/components/xcollapse.vue`, hash: `ba1479bd1080a4fa5abdf6c91c7328ae679f78e6` },
185
+ XToggle: { path: `${prefix}/components/xtoggle.vue`, hash: `2f6871d2d3069ac8f35b6bf76de6c2109f42d9d1` },
186
+ XToggle3: { path: `${prefix}/components/xtoggle3.vue`, hash: `61d0464e6ed9983e817eb0cde928dceb7c0fdc75` },
187
+ XCheck: { path: `${prefix}/components/xcheck.vue`, hash: `ee0d6b30600fb41589123f6d66f2791f1332d4f7` },
188
+ XCheckbox: { path: `${prefix}/components/xcheckbox.vue`, hash: `a290c0cbb7bffce83c235dd5a1c98ed9c441a5b0` },
189
+ XTextarea: { path: `${prefix}/components/xtextarea.vue`, hash: `f8bb08419082aa5443630ab07172674b50c7a248` },
190
+ XHidden: { path: `${prefix}/components/xhidden.vue`, hash: `ecb396e12dd894040e715c0854275e4d5016fcb9` },
191
+ XCode: { path: `${prefix}/components/xcode.vue`, hash: `4d9d9165fea0539c9a983fcdffae8dedcfd537ae` },
192
+ XButton: { path: `${prefix}/components/xbutton.vue`, hash: `2e956caa47e46377ea5a809f7438d0fc38be73b9` },
193
+ XTabs: { path: `${prefix}/components/xtabs.vue`, hash: `83dc219106bdc86ae86dcd16cf95ebd7f11bc952` },
194
+ XKv: { path: `${prefix}/components/xkv.vue`, hash: `8951d3a5e3786cfc9c705b13c1f71e3f90dd2552` },
195
+ XNav: { path: `${prefix}/components/xnav.vue`, hash: `8d51c73e5716deed3577652e362c50526ddbe4e1` },
196
+ XMap: { path: `${prefix}/components/xmap.vue`, hash: `daee357d9e2ef96df0166dd7add0339d46a1cc01` },
197
+ XList: { path: `${prefix}/components/xlist.vue`, hash: `217ced04a333238d169c300a721b75f0ddd5e95b` },
198
+ XJson: { path: `${prefix}/components/xjson.vue`, hash: `0a3ef6265b4070b0f002d659776c02756cc1da5a` },
199
+ XCard: { path: `${prefix}/components/xcard.vue`, hash: `de3fbb23ae7b00d4c90a717dd361cb9315e9ded6` },
200
+ XCardH: { path: `${prefix}/components/xcard-h.vue`, hash: `de4d42f1056c5d2b8431f15e6b1180d9f9898ac2` },
201
+ XColor: { path: `${prefix}/components/xcolor.vue`, hash: `9bf9497ff66e213277f17af290c21c0a35752510` },
202
+ "v-ace-editor": { path: `${prefix}/components/vue3-ace-editor.vue`, hash: `70ce4a39152af5cf0f7cb6b1d4fdafc8b6225edc` },
203
+ XMarkdown: { path: `${prefix}/components/xmarkdown.vue`, hash: `15f835547fab8a8c8aad47d72640c8e918a7b9da` },
204
+ XSound: { path: `${prefix}/components/xsound.vue`, hash: `3e8ad4aa3c767f757dd49b99aa6f961547caf970` },
205
+ XUpload: { path: `${prefix}/components/xupload.vue`, hash: `7a872277e0047fca11e950efe08f2bffa670abdb` },
206
+ XTree: { path: `${prefix}/components/xtree.vue`, hash: `3b6534e86996c48ab05072a9b793ecc78d83a0eb` },
207
+ CodeMirror: { path: `${prefix}/components/code-mirror.vue`, hash: `3ce1028adb75831e01c4264d5764c14f60a1bd00` },
208
+ XTerminal: { path: `${prefix}/components/xterminal.vue`, hash: `1c01f92c0a08bd4937f9768a1d49f12a9a84feea` },
209
+ }
210
+ }
211
+
212
+
213
+ // ________________________________________________________________________________
214
+ // Factory
215
+ // ________________________________________________________________________________
216
+ /**
217
+ * Create a C0ckp1t configuration object.
218
+ *
219
+ * @param {Object} overrides - Properties to override.
220
+ * @returns {Object} The configuration object.
221
+ */
222
+ export function createConfig(overrides = {}) {
223
+ // 1. Separate root/routes from scalar overrides so deepMerge handles scalars
224
+ const { root: rootOverride, components: componentsOverride, routes: routesOverride, ...scalarOverrides } = overrides;
225
+
226
+ // 2. Merge scalar defaults with overrides
227
+ const merged = deepMerge(DEFAULTS, scalarOverrides);
228
+
229
+ // 3. Resolve the key variables that nav/routes depend on
230
+ const { instanceId, routePrefix, componentPrefix } = merged;
231
+
232
+ // 4. Build or use provided root nav tree
233
+ const root = rootOverride !== undefined ? rootOverride : buildNavTree(instanceId);
234
+
235
+ // 5. Build or use provided routes
236
+ const routes = routesOverride !== undefined ? routesOverride : buildRoutes(instanceId, routePrefix);
237
+
238
+ const components = componentsOverride !== undefined ? componentsOverride : defaultVueComponents(componentPrefix);
239
+ // 6. Assemble final config
240
+ return {
241
+ ...merged,
242
+ root,
243
+ routes,
244
+ components
245
+ };
246
+ }
247
+
248
+ export default createConfig;
package/README.md CHANGED
@@ -45,12 +45,15 @@ tar -zxvf c0ckp1t-1.0.2.tgz
45
45
  # package/js_ext/Makefile
46
46
  # package/css/bootstrap-c0ckp1t.css
47
47
  # ...
48
+
49
+ # to expand to webroot directory
50
+ tar -zxvf c0ckp1t-1.0.2.tgz --strip-components=1 -C webroot
48
51
  ```
49
52
 
50
53
 
51
54
  ## Releases
52
55
 
53
- * 1.0.10 - Beta: fixing cdn index-cdn.html making sure it works with jsfiddle.net
56
+ * 1.0.12 - Beta: fixing cdn index-cdn.html making sure it works with jsfiddle.net
54
57
  * Had to publish many times to get CDN working with different configurations
55
58
  * 1.0.2 - Beta: fixing cdn index-cdn.html example
56
59
  * 1.0.1 - Beta: removing `Constants.mjs` dependencies
@@ -10,6 +10,8 @@ const instanceId = "demo";
10
10
  // Used for requestion app components and files
11
11
  const appEndpoint = "";
12
12
 
13
+ const islandDir = "c0ckp1t-demo"
14
+
13
15
  // ________________________________________________________________________________
14
16
  // GLOBAL CONSTANTS
15
17
  // ________________________________________________________________________________
@@ -21,6 +23,7 @@ export default {
21
23
  type: "LOCAL",
22
24
  appName: "C0ckp1t Demo",
23
25
  appEndpoint: appEndpoint,
26
+ islandDir: islandDir,
24
27
 
25
28
  // This creates the navigation tree
26
29
  root: {
@@ -55,11 +58,11 @@ export default {
55
58
 
56
59
  // This is used to create routes for the vue router
57
60
  routes: [
58
- {path: instanceId, location: `${appEndpoint}/c0ckp1t-demo/main.vue`, children: [
61
+ {path: instanceId, location: `${appEndpoint}/${islandDir}/main.vue`, children: [
59
62
  {path: '', redirect: `/${instanceId}/homepage`},
60
- {path: 'homepage', location: `${appEndpoint}/c0ckp1t-demo/pages/homepage.vue`},
63
+ {path: 'homepage', location: `${appEndpoint}/${islandDir}/pages/homepage.vue`},
61
64
  {path: 'docs', redirect: `/${instanceId}/docs/Introduction.md`},
62
- {path: 'docs/:pathMatch(.*)*', location: `${appEndpoint}/core/pages/Documentation.vue`},
65
+ {path: 'docs/:pathMatch(.*)*', location: `${appEndpoint}/${islandDir}/pages/documentation.vue`},
63
66
  ] },
64
67
  ]
65
68
 
@@ -39,8 +39,7 @@ logger.debug("[INIT]")
39
39
 
40
40
  <ul class="nav flex-column mb-auto">
41
41
  <RouterLink class="nav-link d-flex align-items-center gap-2" :to="`${routerEndpoint}/homepage`">Homepage</RouterLink>
42
- <RouterLink class="nav-link d-flex align-items-center gap-2" :class="{ 'router-link-active': isDocsActive }" :to="`${routerEndpoint}/docs`" v-if="registry?.store?.context?.accessLevel <= 500">Documentation</RouterLink>
43
- <RouterLink class="nav-link d-flex align-items-center gap-2" :to="`${routerEndpoint}/admin`" v-if="registry?.store?.context?.accessLevel <= 500">Admin</RouterLink>
42
+ <RouterLink class="nav-link d-flex align-items-center gap-2" :class="{ 'router-link-active': isDocsActive }" :to="`${routerEndpoint}/docs`" >Documentation</RouterLink>
44
43
  </ul>
45
44
 
46
45
  <hr class="my-2">
@@ -1,3 +1,4 @@
1
1
 
2
2
  # C0ckp1t Demo Application
3
3
 
4
+ * [Issues](./Issues.md)
@@ -0,0 +1,3 @@
1
+
2
+ # Issues
3
+
@@ -0,0 +1,64 @@
1
+ <script setup>
2
+ /*
3
+ [VARS] - init, imports, template, script, style, name
4
+
5
+ Usage:
6
+ const documentation = defineAsyncComponent(() => import("/v3/actions/auth/www/pages/documentation.vue"))
7
+ <documentation/>
8
+ */
9
+ // ________________________________________________________________________________
10
+ // IMPORTS
11
+ // ________________________________________________________________________________
12
+ import {reactive, computed, ref, onMounted, onUnmounted, defineAsyncComponent, watch} from 'vue'
13
+ import {getLogger} from "Logging";
14
+ import {store as storeLocal, api as apiLocal, registry, instanceId} from '../store.mjs'
15
+ import Documentation from "/core/pages/Documentation.vue"
16
+
17
+ // !# C0CKP1T_START imports
18
+
19
+ // !# C0CKP1T_END imports
20
+
21
+ // ________________________________________________________________________________
22
+ // LOGGING
23
+ // ________________________________________________________________________________
24
+ const LOG_HEADER = "pages/homepage.vue"
25
+ const logger = getLogger(LOG_HEADER)
26
+ logger.debug("[INIT]")
27
+
28
+ // !# C0CKP1T_START script
29
+ const local = reactive({
30
+ id: LOG_HEADER,
31
+ instanceId: instanceId
32
+ })
33
+ // !# C0CKP1T_END script
34
+
35
+ // ________________________________________________________________________________
36
+ // INIT
37
+ // ________________________________________________________________________________
38
+ async function init() {
39
+ if (registry.state.isReady) {
40
+ // !# C0CKP1T_START init
41
+
42
+ // !# C0CKP1T_END init
43
+ } else {
44
+ setTimeout(async () => { await init() }, 1000)
45
+ }
46
+ }
47
+ onMounted(async () => { init() })
48
+ </script>
49
+
50
+ <template>
51
+ <!-- !# C0CKP1T_START template -->
52
+ <x-section extra="fs-3" k="Documentation" :visible="true">
53
+ <Documentation :remotePathMapping="`/${storeLocal.islandDir}/docs`"></Documentation>
54
+ </x-section>
55
+ <!-- !# C0CKP1T_END template -->
56
+ </template>
57
+
58
+ <style scoped>
59
+ /* !# C0CKP1T_START style */
60
+
61
+ /* !# C0CKP1T_END style */
62
+ </style>
63
+
64
+
@@ -12,7 +12,7 @@ import {getLogger} from "Logging";
12
12
  import {store as storeMain, api as apiMain} from 'GlobalStore'
13
13
 
14
14
  // !# C0CKP1T_START import
15
- import C0ckp1tConfig from "./C0ckp1tConfig.mjs";
15
+ import C0ckp1tConfig from "./Config.mjs";
16
16
  // !# C0CKP1T_END import
17
17
 
18
18
  export const instanceId = C0ckp1tConfig.instanceId
@@ -38,7 +38,7 @@ export const store = reactive({
38
38
  endpoint: routerEndpoint,
39
39
 
40
40
  // !# C0CKP1T_START store
41
-
41
+ islandDir: C0ckp1tConfig.islandDir,
42
42
  // !# C0CKP1T_END store
43
43
  })
44
44
 
@@ -73,6 +73,9 @@ let _ro = null;
73
73
  let _contentBackup = '';
74
74
  let _isSettingContent = false;
75
75
 
76
+ let _mounted = false;
77
+ let _initAborted = false;
78
+
76
79
  // CodeMirror modules - loaded dynamically
77
80
  let EditorView = null;
78
81
  let EditorState = null;
@@ -215,6 +218,12 @@ watch(() => local.currentReadonly, (val) => {
215
218
  });
216
219
 
217
220
  onMounted(async () => {
221
+ _mounted = true;
222
+ // --- guard helper ---
223
+ const alive = () => _mounted && root.value != null;
224
+ if (!alive()) return;
225
+
226
+
218
227
  // Load core CodeMirror modules
219
228
  const [stateModule, viewModule, basicSetupModule] = await Promise.all([
220
229
  import('https://esm.sh/@codemirror/state'),
@@ -222,6 +231,9 @@ onMounted(async () => {
222
231
  import('https://esm.sh/codemirror'),
223
232
  ]);
224
233
 
234
+ // Bail if component unmounted during async load
235
+ if (!alive()) return;
236
+
225
237
  EditorState = stateModule.EditorState;
226
238
  Compartment = stateModule.Compartment;
227
239
  EditorView = viewModule.EditorView;
@@ -239,6 +251,9 @@ onMounted(async () => {
239
251
  getThemeExtension(props.theme),
240
252
  ]);
241
253
 
254
+ // Bail again after second async gap
255
+ if (!alive()) return;
256
+
242
257
  const extensions = [
243
258
  basicSetup,
244
259
  languageCompartment.of(langExt),
@@ -301,14 +316,14 @@ onMounted(async () => {
301
316
  });
302
317
 
303
318
  onBeforeUnmount(() => {
304
- if (_ro) {
305
- _ro.disconnect();
306
- _ro = null;
307
- }
308
- if (_editor) {
309
- _editor.destroy();
310
- _editor = null;
311
- }
319
+ _mounted = false;
320
+
321
+ _ro?.disconnect();
322
+ _ro = null;
323
+
324
+ _editor?.destroy();
325
+ _editor = null;
326
+
312
327
  });
313
328
 
314
329
  // Public methods
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # tools.sh - Compute the SHA-1 hash of each file in this directory (non-recursive).
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ for file in "$SCRIPT_DIR"/*; do
7
+ [ -f "$file" ] || continue
8
+ sha1sum "$file"
9
+ done
@@ -1,6 +1,5 @@
1
1
  <script setup>
2
2
  import { reactive, computed, markRaw, onMounted, onBeforeUnmount } from 'vue'
3
- import XDropdown2 from '/components/xdropdown2.vue'
4
3
 
5
4
  // https://wavesurfer-js.org/docs/
6
5
  // NOTE: streaming not supported?
@@ -197,7 +196,7 @@ onBeforeUnmount(() => {
197
196
  </div>
198
197
 
199
198
  <!-- Playback Rate -->
200
- <XDropdown2
199
+ <x-dropdown2
201
200
  v-model="local.playbackRate"
202
201
  :items="playbackRateOptions"
203
202
  :on-change="onPlaybackRateChange"
@@ -11,6 +11,9 @@ export function findHostnamePortProtocol() {
11
11
  return {hostname, port, protocol, isSecure, serverUrl}
12
12
  }
13
13
 
14
+ // ________________________________________________________________________________
15
+ // Island Configuration
16
+ // ________________________________________________________________________________
14
17
  /**
15
18
  * Validate and set defaults for the island config object.
16
19
  * @param config
@@ -35,43 +38,108 @@ export function validateIslandConfig(config) {
35
38
  return config
36
39
  }
37
40
 
41
+ // ________________________________________________________________________________
42
+ // Application Configuration
43
+ // ________________________________________________________________________________
44
+ export const DEFAULTS = {
45
+ isDev: true,
46
+ // XMLHttpRequest from a different domain cannot set cookie values for their own
47
+ // domain unless withCredentials is set to true before making the request.
48
+ // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
49
+ WITH_CREDENTIALS: false,
50
+
51
+ defaultInstanceId: "default",
52
+ instanceId: "default",
53
+ type: "LOCAL",
54
+ // Determines GlobalStore.name
55
+ appName: "C0ckp1t Application",
56
+ // Used for default routes prefix (used only in config)
57
+ routePrefix: "",
58
+ // Used for default components prefix (used only in config)
59
+ componentPrefix: "",
60
+ // Main entry point
61
+ appMainComponent: "/core/PageMain.vue",
62
+ // Used for requestion app components and files (GlobalStore.appEndpoint)
63
+ appEndpoint: "",
64
+
65
+ // Nav Configuration
66
+ navCloseLogo: "/core/img/logo_v1.svg",
67
+ navOpenLogo: "/core/img/logo_v2.svg",
68
+ navHasSearch: false,
69
+ navHasThemeSel: true,
70
+
71
+ // Determine if VueRouter is createWebHashHistory or createWebHistory
72
+ vueRouterModeIsHash: true,
73
+
74
+ // Logger Config (see Logging.mjs)
75
+ defaultLogLevel: "INFO",
76
+ defaultLoggerLevels: {
77
+ "GlobalStore.mjs": "INFO",
78
+ "VueUtils.mjs": "INFO",
79
+ "Connection.mjs": "INFO",
80
+ "default": "INFO",
81
+ "anonymous": "INFO",
82
+ "demo": "INFO"
83
+ },
84
+ };
85
+
86
+
38
87
  /**
39
- * Return the default Vue components
40
- * @returns {Object}
88
+ * Validate and set defaults for the config object.
89
+ * @param config
90
+ * @returns {*}
41
91
  */
42
- export function defaultVueComponents() {
43
- return {
44
- ExecButton: `/components/ExecButton.vue`,
45
- XInput: `/components/xinput.vue`,
46
- XLabel: `/components/xlabel.vue`,
47
- XDropdown: `/components/xdropdown.vue`,
48
- XDropdown2: `/components/xdropdown2.vue`,
49
- XSection: `/components/xsection.vue`,
50
- XTableOpen: `/components/xtable-open.vue`,
51
- XCollapse: `/components/xcollapse.vue`,
52
- XToggle: `/components/xtoggle.vue`,
53
- XToggle3: `/components/xtoggle3.vue`,
54
- XCheck: `/components/xcheck.vue`,
55
- XCheckbox: `/components/xcheckbox.vue`,
56
- XTextarea: `/components/xtextarea.vue`,
57
- XHidden: `/components/xhidden.vue`,
58
-
59
- XTabs: `/components/xtabs.vue`,
60
- XKv: `/components/xkv.vue`,
61
- XNav: `/components/xnav.vue`,
62
- XMap: `/components/xmap.vue`,
63
- XList: `/components/xlist.vue`,
64
- XJson: `/components/xjson.vue`,
65
- XCard: `/components/xcard.vue`,
66
- XCardH: `/components/xcard-h.vue`,
67
- XColor: `/components/xcolor.vue`,
92
+ export function validateAppConfig(config) {
93
+ if (!config) {
94
+ throw new Error("config is required")
95
+ }
96
+ if (typeof config !== 'object') {
97
+ throw new Error("Config must be an object must was `" + typeof config + "`")
98
+ }
99
+ if (!Array.isArray(config?.routes) || config.routes.length === 0) {
100
+ config.routes = []
101
+ }
102
+ if (typeof config.components !== 'object' || config.components === null) {
103
+ config.components = {}
104
+ }
68
105
 
69
- "v-ace-editor": `/components/vue3-ace-editor.vue`,
70
- XMarkdown: `/components/xmarkdown.vue`,
71
- XSound: `/components/xsound.vue`,
72
- XUpload: `/components/xupload.vue`,
73
- XTree: `/components/xtree.vue`,
74
- CodeMirror: `/components/CodeMirror.vue`,
75
- XTerminal: `/components/XTerminal.vue`,
106
+ if (typeof config.instanceId !== `string` || config.instanceId.trim() === ``) {
107
+ config.instanceId = DEFAULTS.instanceId
108
+ }
109
+ if (typeof config.type !== `string` || config.type.trim() === ``) {
110
+ config.type = DEFAULTS.type
76
111
  }
112
+
113
+ return config
77
114
  }
115
+
116
+
117
+ // ________________________________________________________________________________
118
+ // Deep Merge Utility
119
+ // ________________________________________________________________________________
120
+ /**
121
+ * Deep merge source into target. Returns a new object.
122
+ * - Objects are recursively merged (source keys override target keys)
123
+ * - Arrays are replaced entirely (source array wins)
124
+ * - Scalars are replaced (source wins)
125
+ */
126
+ export function deepMerge(target, source) {
127
+ const result = { ...target };
128
+ for (const key of Object.keys(source)) {
129
+ const sourceVal = source[key];
130
+ const targetVal = target[key];
131
+ if (
132
+ sourceVal !== null &&
133
+ typeof sourceVal === 'object' &&
134
+ !Array.isArray(sourceVal) &&
135
+ targetVal !== null &&
136
+ typeof targetVal === 'object' &&
137
+ !Array.isArray(targetVal)
138
+ ) {
139
+ result[key] = deepMerge(targetVal, sourceVal);
140
+ } else {
141
+ result[key] = sourceVal;
142
+ }
143
+ }
144
+ return result;
145
+ }