glib-web 4.41.0 → 4.42.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.
@@ -0,0 +1,24 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Read(//home/hgani/workspace/glib-web/app/views/json_ui/garage/**)",
5
+ "Read(//home/hgani/workspace/glib-web-npm/doc/garage/**)",
6
+ "Read(//home/hgani/workspace/glib-web-npm/doc/common/**)",
7
+ "Bash(find:*)",
8
+ "Bash(npx cypress run:*)",
9
+ "Read(//home/hgani/workspace/glib-web/**)",
10
+ "Bash(curl:*)",
11
+ "Bash(pkill:*)",
12
+ "Bash(gh pr list:*)",
13
+ "WebSearch",
14
+ "WebFetch(domain:vuetifyjs.com)",
15
+ "Bash(lsof:*)",
16
+ "Bash(readlink:*)",
17
+ "WebFetch(domain:github.com)",
18
+ "Bash(npm run dev)",
19
+ "Bash(npm run)"
20
+ ],
21
+ "deny": [],
22
+ "ask": []
23
+ }
24
+ }
package/.nycrc.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "reporter": [
3
+ "text-summary",
4
+ "html"
5
+ ],
6
+ "report-dir": "./coverage",
7
+ "extension": [
8
+ ".js",
9
+ ".ts",
10
+ ".vue"
11
+ ],
12
+ "exclude": [
13
+ "cypress/**",
14
+ "coverage/**",
15
+ "doc/**",
16
+ "node_modules/**"
17
+ ]
18
+ }
@@ -1,6 +1,16 @@
1
1
  export default class {
2
2
  execute(properties, component) {
3
- const target = GLib.component.findById(properties.targetId) || component;
3
+ let target = component;
4
+
5
+ if (properties.targetId) {
6
+ target = GLib.component.findById(properties.targetId);
7
+
8
+ if (!target) {
9
+ console.warn("Component ID not found for form submission:", properties.targetId);
10
+ target = component;
11
+ }
12
+ }
13
+
4
14
  target.$dispatchEvent("forms/directSubmit", {
5
15
  url: properties.overrideUrl,
6
16
  method: properties.overrideMethod,
@@ -1,11 +1,11 @@
1
1
  <template>
2
- <div ref="container" :style="$styles()" :class="$classes()" v-if="loadIf">
2
+ <div ref="container" :style="$styles()" :class="$classes()" v-if="loadIf" class="fields-select-wrapper">
3
3
  <!-- Set `menu-props` so the menu will never be wider than the select field.
4
4
  See https://github.com/vuetifyjs/vuetify/issues/17751 -->
5
5
  <component ref="comp" :is="compName" :color="gcolor" v-model="fieldModel" :label="label" :items="normalizedOptions"
6
- :chips="useChips" :disabled="inputDisabled" :multiple="spec.multiple" :readonly="spec.readOnly"
6
+ :disabled="inputDisabled" :multiple="spec.multiple" :readonly="spec.readOnly"
7
7
  :clearable="spec.clearable" :placeholder="spec.placeholder" :rules="$validation()" persistent-hint
8
- :append-icon="append.icon" validate-on="blur" item-title='text' :variant="variant" :closable-chips="spec.multiple"
8
+ :append-icon="append.icon" validate-on="blur" item-title='text' :variant="variant"
9
9
  :density="density" persistent-placeholder @update:modelValue="onChange" @focus="focused = true"
10
10
  @blur="focused = false" :menu-props="{ maxWidth: 0 }">
11
11
 
@@ -23,7 +23,7 @@
23
23
  <select-item-default v-else :context="props" :item="item" :spec="spec"></select-item-default>
24
24
  </div>
25
25
  </template>
26
-
26
+
27
27
  <template v-slot:prepend-item>
28
28
  <template v-if="spec.prependSelectAll">
29
29
  <v-list-item title="Select All" @click="checkAll">
@@ -39,6 +39,41 @@
39
39
  <common-responsive v-if="spec.header" :spec="spec.header" />
40
40
  </template>
41
41
 
42
+ <template v-if="useChips" #selection="{ item, index }">
43
+ <v-chip
44
+ v-if="index < maxVisibleChips"
45
+ :density="density"
46
+ closable
47
+ @click:close="removeItem(item)"
48
+ >
49
+ <span>{{ item.title }}</span>
50
+ </v-chip>
51
+ <v-chip
52
+ v-if="!expanded && chipExceedsTwoLines && visibleChipCount < fieldModel.length && index === visibleChipCount"
53
+ :density="density"
54
+ clickable
55
+ @click="expanded = true"
56
+ class="text-caption expansion-chip"
57
+ variant="outlined"
58
+ color="primary"
59
+ >
60
+ {{ fieldModel.length - visibleChipCount }} more
61
+ <common-icon :spec="{ material: { name: 'expand_more' } }" class="ml-1" />
62
+ </v-chip>
63
+ <v-chip
64
+ v-if="expanded && chipExceedsTwoLines && index === fieldModel.length - 1"
65
+ :density="density"
66
+ clickable
67
+ @click="collapseChips"
68
+ class="text-caption expansion-chip"
69
+ variant="outlined"
70
+ color="primary"
71
+ >
72
+ Show less
73
+ <common-icon :spec="{ material: { name: 'expand_less' } }" class="ml-1" />
74
+ </v-chip>
75
+ </template>
76
+
42
77
  <template v-slot:append-item v-if="spec.footer">
43
78
  <common-responsive :spec="footer" />
44
79
  </template>
@@ -62,7 +97,7 @@ import { triggerOnChange, triggerOnInput, useGlibInput } from "../composable/for
62
97
  import { isBoolean } from '../../utils/type';
63
98
 
64
99
  import { useGlibSelectable, watchNoneOfAbove } from '../composable/selectable';
65
- import { ref, defineExpose } from 'vue';
100
+ import { ref, defineExpose, computed, watch, nextTick, onMounted, onUnmounted } from 'vue';
66
101
  import SelectItemDefault from "./_selectItemDefault.vue";
67
102
  import SelectItemWithImage from "./_selectItemWithImage.vue";
68
103
  import SelectItemWithIcon from "./_selectItemWithIcon.vue";
@@ -85,6 +120,166 @@ export default {
85
120
  const options = ref(props.spec.options);
86
121
  const comp = ref(null);
87
122
  const append = props.spec.append || {};
123
+ const expanded = ref(false);
124
+ const chipExceedsMaxLines = ref(false);
125
+ const visibleChipCount = ref(Infinity);
126
+
127
+ // Constants for chip line calculation
128
+ const DEFAULT_MAX_LINES = 2;
129
+ const DEFAULT_LINE_HEIGHT = 40;
130
+ const CHIP_GAP = 12; // Gap between chips + margins
131
+
132
+ // Computed property for determining how many chips to show
133
+ const maxVisibleChips = computed(() => {
134
+ if (!expanded.value) {
135
+ return visibleChipCount.value;
136
+ }
137
+ // When expanded, show all chips except reserve space for "Show less" if needed
138
+ return chipExceedsMaxLines.value ? fieldModel.value.length - 1 : fieldModel.value.length;
139
+ });
140
+
141
+ // Helper function to find the container that holds all chips
142
+ const findChipsContainer = (allChips) => {
143
+ if (allChips.length === 0) return null;
144
+
145
+ let container = allChips[0].parentElement;
146
+ // Walk up the DOM to find a container that has multiple chips
147
+ while (container && container.querySelectorAll('.v-chip').length <= 1) {
148
+ container = container.parentElement;
149
+ }
150
+ return container;
151
+ };
152
+
153
+ // Method to check if chips exceed max lines
154
+ const checkChipLines = (isResizeTriggered = false) => {
155
+ if (!comp.value) return;
156
+
157
+ // Get max lines config: 0 = no limit, null/undefined = default 2
158
+ const maxLines = props.spec.maxChipLines ?? DEFAULT_MAX_LINES;
159
+
160
+ // If maxLines is 0, don't limit chips at all
161
+ if (maxLines === 0) {
162
+ chipExceedsMaxLines.value = false;
163
+ visibleChipCount.value = Infinity;
164
+ return;
165
+ }
166
+
167
+ // Skip the measurement logic if this is a resize-triggered event and we're collapsed
168
+ // This prevents incorrect measurements when collapsed with fewer chips visible
169
+ if (isResizeTriggered && !expanded.value && chipExceedsMaxLines.value) {
170
+ return;
171
+ }
172
+
173
+ // Try different selectors to find the correct container
174
+ let selectionEl = comp.value.$el.querySelector('.v-select__selection, .v-autocomplete__selection');
175
+ if (!selectionEl) {
176
+ // Fallback: try to find the component root and look for chips there
177
+ selectionEl = comp.value.$el;
178
+ }
179
+
180
+ // Find all chips
181
+ const allChips = comp.value.$el.querySelectorAll('.v-chip');
182
+
183
+ // Early return if no chips
184
+ if (allChips.length === 0) {
185
+ chipExceedsMaxLines.value = false;
186
+ visibleChipCount.value = Infinity;
187
+ return;
188
+ }
189
+
190
+ // Find the common parent of all chips
191
+ const chipsContainer = findChipsContainer(allChips);
192
+
193
+ // Get the height of a single line dynamically from actual chip height
194
+ // This adapts to density settings, custom styles, and theme changes
195
+ const lineHeight = allChips[0].offsetHeight || DEFAULT_LINE_HEIGHT;
196
+ const currentHeight = chipsContainer ? chipsContainer.scrollHeight : selectionEl.scrollHeight;
197
+ const lines = Math.floor(currentHeight / lineHeight);
198
+
199
+ const exceedsMaxLines = lines > maxLines;
200
+
201
+ if (exceedsMaxLines) {
202
+ // Calculate how many chips fit in maxLines using line-by-line simulation
203
+ const chips = chipsContainer ? chipsContainer.querySelectorAll('.v-chip') : allChips;
204
+ const containerWidth = chipsContainer ? chipsContainer.offsetWidth : selectionEl.offsetWidth;
205
+
206
+ let currentLineWidth = 0;
207
+ let currentLine = 1;
208
+ let count = 0;
209
+
210
+ // Simulate actual chip wrapping line by line
211
+ for (const chip of chips) {
212
+ const chipWidth = chip.offsetWidth + CHIP_GAP;
213
+
214
+ // Check if chip fits on current line
215
+ if (currentLineWidth + chipWidth > containerWidth) {
216
+ // Move to next line
217
+ currentLine++;
218
+ currentLineWidth = chipWidth;
219
+
220
+ if (currentLine > maxLines) {
221
+ break; // Exceeded max lines
222
+ }
223
+ } else {
224
+ // Fits on current line
225
+ currentLineWidth += chipWidth;
226
+ }
227
+
228
+ count++;
229
+ }
230
+
231
+ // Only limit if we actually exceeded maxLines during simulation
232
+ if (currentLine > maxLines || count < chips.length) {
233
+ // Some chips didn't fit - reserve space for expansion chip
234
+ visibleChipCount.value = Math.max(1, count - 1);
235
+ chipExceedsMaxLines.value = true;
236
+ } else {
237
+ // All chips fit within maxLines - show them all!
238
+ visibleChipCount.value = Infinity;
239
+ chipExceedsMaxLines.value = false;
240
+ }
241
+ } else {
242
+ visibleChipCount.value = Infinity;
243
+ chipExceedsMaxLines.value = false;
244
+ }
245
+ };
246
+
247
+ // Watch for changes in model and expansion state
248
+ watch([fieldModel, expanded], () => {
249
+ nextTick(() => {
250
+ checkChipLines();
251
+ });
252
+ });
253
+
254
+ // Add resize observer to recalculate on window resize
255
+ onMounted(() => {
256
+ const resizeObserver = new ResizeObserver(() => {
257
+ // CRITICAL: Don't recalculate when collapsed
258
+ // The collapsed state has fewer chips visible, giving wrong measurements
259
+ // Only measure on resize when expanded (showing all chips)
260
+ if (!expanded.value && chipExceedsMaxLines.value) {
261
+ return; // Skip resize-triggered recalculation when collapsed
262
+ }
263
+ checkChipLines(true); // Pass true to indicate this is a resize-triggered calculation
264
+ });
265
+
266
+ const observeComponent = () => {
267
+ if (comp.value && comp.value.$el) {
268
+ resizeObserver.observe(comp.value.$el);
269
+ return true;
270
+ }
271
+ return false;
272
+ };
273
+
274
+ // Try to observe immediately, or wait a bit for component to mount
275
+ if (!observeComponent()) {
276
+ setTimeout(observeComponent, 100);
277
+ }
278
+
279
+ onUnmounted(() => {
280
+ resizeObserver.disconnect();
281
+ });
282
+ });
88
283
 
89
284
  const valueForDisableAll = props.spec.valueForDisableAll;
90
285
  const { checkAll, isAllSelected, isIndeterminate } = useGlibSelectable({ model: fieldModel, options: options, valueForDisableAll });
@@ -92,13 +287,41 @@ export default {
92
287
  watchNoneOfAbove({ model: fieldModel, options: options, valueForDisableAll });
93
288
  }
94
289
 
290
+ // Method to handle chip collapse
291
+ function collapseChips() {
292
+ expanded.value = false;
293
+ // IMPORTANT: Force recalculation with multiple nextTick calls to ensure DOM is fully updated
294
+ nextTick(() => {
295
+ checkChipLines(false);
296
+ // IMPORTANT: Second nextTick is necessary because DOM updates are asynchronous and
297
+ // the first measurement may occur before all chips are properly removed/added
298
+ // Do not remove this nested nextTick - it's required for correct functionality
299
+ nextTick(() => {
300
+ checkChipLines(false);
301
+ });
302
+ });
303
+ }
304
+
95
305
  // This is a public method that is called by other parts of the code, do not delete it.
96
306
  function toggle() {
97
307
  comp.value.menu = !comp.value.menu;
98
308
  }
99
309
  defineExpose(['toggle']);
100
310
 
101
- return { fieldModel, checkAll, isIndeterminate, isAllSelected, append, toggle, comp };
311
+ return {
312
+ fieldModel,
313
+ checkAll,
314
+ isIndeterminate,
315
+ isAllSelected,
316
+ append,
317
+ toggle,
318
+ comp,
319
+ expanded,
320
+ chipExceedsTwoLines: chipExceedsMaxLines,
321
+ visibleChipCount,
322
+ maxVisibleChips,
323
+ collapseChips
324
+ };
102
325
  },
103
326
  data() {
104
327
  return {
@@ -178,6 +401,12 @@ export default {
178
401
  triggerOnChange(containerEl);
179
402
  }
180
403
  },
404
+ removeItem(item) {
405
+ const index = this.fieldModel.indexOf(item.value);
406
+ if (index >= 0) {
407
+ this.fieldModel.splice(index, 1);
408
+ }
409
+ },
181
410
  $registryEnabled() {
182
411
  return false;
183
412
  }
@@ -212,4 +441,21 @@ export default {
212
441
  display: flex;
213
442
  align-items: center;
214
443
  }
444
+
445
+ .expansion-chip {
446
+ transition: all 0.2s ease-in-out;
447
+ box-sizing: content-box;
448
+ margin-left: 1px;
449
+
450
+ &:hover {
451
+ background-color: rgba(0, 0, 0, 0.04) !important;
452
+ transform: translateY(-1px);
453
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
454
+ }
455
+
456
+ &:active {
457
+ transform: translateY(0);
458
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
459
+ }
460
+ }
215
461
  </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div :class="$classes()" :style="$styles()" v-if="loadIf" @click="$onClick()">
2
+ <div :class="$classes()" :style="$styles()" v-if="loadIf" @click="onClick($event)">
3
3
  <!-- This hidden field should always be there to make sure the submitted param is not empty,
4
4
  which could cause "Not accessible" error on the server. -->
5
5
  <input v-if="showUncheck" v-model="uncheckValue" type="hidden" :name="fieldName" />
@@ -70,6 +70,10 @@ export default {
70
70
  };
71
71
  },
72
72
  methods: {
73
+ onClick(event) {
74
+ event.stopPropagation(); // Prevent event bubbling to parent thumbnail
75
+ this.$onClick();
76
+ },
73
77
  _linkFieldModels(valueChanged) {
74
78
  if (!this.parentModel && valueChanged) {
75
79
  this.fieldModel = this.spec.value;
@@ -42,7 +42,7 @@ export default {
42
42
  // This regex follows the RFC 5321 standard for email validation,
43
43
  // with a slight improvement: it does not allow emails without a proper domain (e.g., "user@test" is invalid, must be "user@test.com").
44
44
  //https://stackoverflow.com/questions/13992403/regex-validation-of-email-addresses-according-to-rfc5321-rfc5322
45
- /^([!#-'*+\/-9=?A-Z^-~-]+(\.[!#-'*+\/-9=?A-Z^-~-]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([!#-'*+\/-9=?A-Z^-~-]+\.[!#-'*+\/-9=?A-Z^-~-]+|\[[\t -Z^-~]*])$/i.test(
45
+ /^([!#-'*+\/-9=?A-Z^-~-]+(\.[!#-'*+\/-9=?A-Z^-~-]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([A-Z0-9-]+\.)+[A-Z]{2,}$/i.test(
46
46
  v
47
47
  ) ||
48
48
  "E-mail must be valid",
@@ -15,6 +15,7 @@
15
15
 
16
16
  // Import commands.js using ES2015 syntax:
17
17
  import './commands'
18
+ import '@cypress/code-coverage/support'
18
19
 
19
20
  // Alternatively you can use CommonJS syntax:
20
- // require('./commands')
21
+ // require('./commands')
package/cypress.config.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { defineConfig } from "cypress";
2
+ import codeCoverageTask from "@cypress/code-coverage/task";
2
3
 
3
4
  export default defineConfig({
4
5
  e2e: {
5
6
  setupNodeEvents(on, config) {
6
- // implement node event listeners here
7
+ codeCoverageTask(on, config);
8
+ return config;
7
9
  },
8
10
  },
9
11
  });
package/doc/TESTING.md CHANGED
@@ -54,3 +54,17 @@ You **cannot write frontend tests in isolation**.
54
54
  ---
55
55
 
56
56
  These 6 concepts explain **why the test structure exists** and **what you need to get started**. Everything else (selectors, assertions, etc.) is standard Cypress mechanics.
57
+
58
+ ## Cypress Coverage Workflow
59
+
60
+ - **Prerequisites**: Keep the backend `glib-web` server running with the matching `test_page/*` fixtures (same as normal Cypress runs) and make sure the frontend package is linked into that app: `yarn link glib-web` (run from the backend repo).
61
+ - **Instrument code**: Coverage is only collected when `VITE_COVERAGE=true` because `vite-plugin-istanbul` has `requireEnv` enabled.
62
+ - **Step-by-step run**:
63
+ 1. From the backend repo (`glib-web`), ensure the link is active: `yarn link glib-web`.
64
+ 2. From the backend repo, start Vite with instrumentation: `VITE_COVERAGE=true bin/vite dev`.
65
+ 3. From the backend repo, start Rails: `bin/rails s`.
66
+ 4. From this repo (`glib-web-npm`), execute `yarn test:coverage` to run Cypress and emit coverage reports via `nyc` (uses `.nycrc.json`).
67
+ - **Manual run (if you need custom flags)**:
68
+ 1. `VITE_COVERAGE=true yarn test`
69
+ 2. `nyc report` (or add reporters, e.g. `nyc report --reporter=lcov`).
70
+ - **Where to read results**: HTML report at `coverage/index.html`; summary printed in the terminal; raw data in `.nyc_output` and `coverage/coverage-final.json`.
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
+ "type": "module",
2
3
  "name": "glib-web",
3
- "version": "4.41.0",
4
+ "version": "4.42.0",
4
5
  "description": "",
5
6
  "main": "index.js",
6
7
  "scripts": {
7
- "test": "cypress run --browser chrome"
8
+ "test": "cypress run --browser chrome",
9
+ "test:coverage": "VITE_COVERAGE=true cypress run --browser chrome && nyc report"
8
10
  },
9
11
  "author": "",
10
12
  "license": "ISC",
@@ -42,11 +44,18 @@
42
44
  "vuetify": "3.8.6"
43
45
  },
44
46
  "devDependencies": {
47
+ "@cypress/code-coverage": "^3.14.7",
45
48
  "@types/chart.js": "^2.9.34",
49
+ "@vitejs/plugin-vue": "^6.0.2",
46
50
  "cypress": "^13.13.1",
47
51
  "eslint": "^8.36.0",
48
52
  "eslint-plugin-vue": "^9.26.0",
49
53
  "prettier": "^1.18.2",
50
- "typescript": "^4.9.5"
54
+ "typescript": "^4.9.5",
55
+ "vite": "^7.2.7",
56
+ "vite-plugin-compression": "^0.5.1",
57
+ "vite-plugin-environment": "^1.1.3",
58
+ "vite-plugin-istanbul": "^7.2.1",
59
+ "vite-plugin-ruby": "^5.1.1"
51
60
  }
52
61
  }
@@ -0,0 +1,88 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { defineConfig } from "vite";
4
+ import RubyPlugin from "vite-plugin-ruby";
5
+ import vue from "@vitejs/plugin-vue";
6
+ import viteCompression from "vite-plugin-compression";
7
+ import EnvironmentPlugin from "vite-plugin-environment";
8
+ import istanbul from "vite-plugin-istanbul";
9
+ import * as TypeUtils from "../utils/type.js";
10
+
11
+ function pickStringArray(value) {
12
+ if (!TypeUtils.isArray(value)) {
13
+ return null;
14
+ }
15
+
16
+ const filtered = value.filter((item) => TypeUtils.isString(item));
17
+ return filtered.length > 0 ? filtered : null;
18
+ }
19
+
20
+ function resolveGlibRoot(customRoot) {
21
+ if (TypeUtils.isString(customRoot)) {
22
+ return customRoot;
23
+ }
24
+
25
+ return path.resolve(fileURLToPath(new URL("..", import.meta.url)));
26
+ }
27
+
28
+
29
+ /**
30
+ * Builds the default Vite configuration used by glib projects.
31
+ *
32
+ * @param {object} [options] Configuration overrides.
33
+ * @param {string} [options.glibRoot] Root directory for the glib package (defaults to repo root).
34
+ * @param {string} [options.aliasName] Module alias name for glib imports.
35
+ * @param {string} [options.aliasPath] Filesystem path used for the alias.
36
+ * @param {string} [options.workspaceRoot] Workspace root for coverage and path resolution.
37
+ * @param {string[]} [options.istanbulInclude] Globs to include when running Istanbul.
38
+ * @param {string[]} [options.environmentVariables] Environment variables exposed to the client.
39
+ * @param {string[]} [options.serverAllow] Additional filesystem paths allowed by Vite dev server.
40
+ * @param {import("vite").Plugin[]} [options.plugins] Extra Vite plugins to append.
41
+ * @returns {import("vite").UserConfig} Config object passed to `defineConfig`.
42
+ */
43
+ function useGlibViteDefineConfig(options = {}) {
44
+ if (TypeUtils.isNotNull(globalThis.window)) {
45
+ throw new Error("useGlibViteDefineConfig is only available in Node environments.");
46
+ }
47
+
48
+ const configOptions = TypeUtils.isObject(options) && !TypeUtils.isArray(options) ? options : {};
49
+
50
+ const glibRoot = resolveGlibRoot(configOptions.glibRoot);
51
+ const aliasName = TypeUtils.isString(configOptions.aliasName) ? configOptions.aliasName : "glib-web";
52
+ const aliasPath = TypeUtils.isString(configOptions.aliasPath) ? configOptions.aliasPath : glibRoot;
53
+ const workspaceRoot = TypeUtils.isString(configOptions.workspaceRoot)
54
+ ? configOptions.workspaceRoot
55
+ : path.resolve(glibRoot, "..");
56
+ const istanbulInclude = pickStringArray(configOptions.istanbulInclude) ?? [`${path.basename(glibRoot)}/**/*`];
57
+ const environmentVariables = pickStringArray(configOptions.environmentVariables) ?? ["VITE_GMAPS_API_KEY"];
58
+ const serverAllow = pickStringArray(configOptions.serverAllow) ?? [glibRoot];
59
+ const additionalPlugins = TypeUtils.isArray(configOptions.plugins) ? configOptions.plugins : [];
60
+
61
+ return defineConfig({
62
+ server: {
63
+ fs: {
64
+ allow: serverAllow.map((allowedPath) => path.resolve(allowedPath))
65
+ }
66
+ },
67
+ resolve: {
68
+ alias: {
69
+ [aliasName]: path.resolve(aliasPath)
70
+ }
71
+ },
72
+ plugins: [
73
+ vue(),
74
+ RubyPlugin(),
75
+ istanbul({
76
+ cwd: path.resolve(workspaceRoot),
77
+ include: istanbulInclude,
78
+ requireEnv: true
79
+ }),
80
+ viteCompression(),
81
+ EnvironmentPlugin(environmentVariables, { defineOn: "import.meta.env" }),
82
+ ...additionalPlugins
83
+ ]
84
+ });
85
+ }
86
+
87
+ export { useGlibViteDefineConfig };
88
+ export default useGlibViteDefineConfig;
@@ -10,6 +10,7 @@
10
10
  </template>
11
11
  </div> -->
12
12
 
13
+ <panels-responsive v-if="spec.left" :spec="spec.left" />
13
14
  <v-list-item v-longclick="$onLongPress" class="item-content" :style="columnStyles()">
14
15
  <!-- <v-icon v-if="spec.onReorder" class="handle">drag_indicator</v-icon> -->
15
16
 
package/utils/type.js CHANGED
@@ -1,4 +1,4 @@
1
- import Hash from "./hash";
1
+ import Hash from "./hash.js";
2
2
 
3
3
  export function isObject(obj) {
4
4
  return typeof obj === "object" && obj !== null;