glib-web 4.27.2 → 4.28.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.
@@ -1,3 +1,4 @@
1
+ import { strandom } from "../../components/helper";
1
2
  import { vueApp } from "../../store";
2
3
  import http from "../../utils/http";
3
4
 
@@ -12,7 +13,7 @@ export default class {
12
13
  component,
13
14
  (response) => {
14
15
  vueApp.sheet = {
15
- spec: response.body,
16
+ spec: Object.assign({}, response.body, { key: strandom() }),
16
17
  show: true,
17
18
  placement
18
19
  };
package/app.vue CHANGED
@@ -32,7 +32,7 @@
32
32
  <Transition name="slide-fade">
33
33
  <v-sheet v-if="vueApp.sheet.show" position="fixed" :class="`views-sheet ${vueApp.sheet.placement}`">
34
34
  <glib-component v-for="(rbSpec, index) in vueApp.sheet.spec.childViews" :spec="rbSpec"
35
- :key="index"></glib-component>
35
+ :key="`${vueApp.sheet.spec.key}-${index}`"></glib-component>
36
36
  </v-sheet>
37
37
  </Transition>
38
38
  <div class="glib-bottomBanner">
@@ -1,11 +1,7 @@
1
1
  <template>
2
- <!-- Use `click.prevent` to prevent bubbling when clicking a dropdown button that is
3
- located in a list row that has href. -->
4
2
  <v-btn :type="type" :disabled="spec.disabled" :style="styles()" :class="$classes()" :href="$href()" :rel="$rel()"
5
3
  :variant="variant" :rounded="$classes().includes('rounded') || null" :density="density" :size="size" :color="color"
6
- :active="$classesInclude('active')" :icon="$classes().includes('icon') ? $vuetify : null" @click.prevent="
7
- type == 'submit' ? $dispatchEvent('forms/submit') : $onClick()
8
- ">
4
+ :active="$classesInclude('active')" :icon="$classes().includes('icon') ? $vuetify : null" @click="onClick">
9
5
  <!-- <span v-if="spec.icon"><common-icon :spec="spec.icon || {}" /></span> -->
10
6
  <common-icon v-if="spec.icon" :spec="spec.icon || {}" />
11
7
  <div :class="hideTextOnXs && spec.icon ? 'd-none d-sm-flex' : null">
@@ -15,7 +11,6 @@
15
11
  </template>
16
12
 
17
13
  <script>
18
- import { nextTick } from "vue";
19
14
  import { determineColor, determineDensity, determineVariant, determineSize } from '../utils/constant';
20
15
 
21
16
 
@@ -42,6 +37,15 @@ export default {
42
37
  }
43
38
  },
44
39
  methods: {
40
+ onClick(e) {
41
+ if (this.type == 'submit') {
42
+ e.preventDefault(); // prevent get submitted twice
43
+ this.$dispatchEvent('forms/submit');
44
+ }
45
+ e.stopPropagation(); // prevent event bubbling
46
+
47
+ this.$onClick();
48
+ },
45
49
  // $ready() {
46
50
  // this.$type.ifArray(this.spec.styleClasses, val => {
47
51
  // this.linkStyling = val.includes("link");
@@ -1,7 +1,7 @@
1
1
  import { Chart, Colors } from "chart.js";
2
2
  import chartDataLabels from 'chartjs-plugin-datalabels';
3
3
  import doughnutLabel from 'chartjs-plugin-doughnutlabel-v3';
4
- import { Vue, vueApp } from "../..";
4
+ import { settings, Vue, vueApp } from "../..";
5
5
  import { computePosition, flip, offset } from '@floating-ui/dom';
6
6
 
7
7
  import 'chartkick/chart.js';
@@ -12,6 +12,8 @@ Chart.register(chartDataLabels);
12
12
  Chart.register(doughnutLabel);
13
13
  Chart.register(Colors);
14
14
 
15
+ if (settings.chartPlugin.htmlLegendPlugin) Chart.register(settings.chartPlugin.htmlLegendPlugin);
16
+
15
17
  import VueChartkick from 'vue-chartkick';
16
18
  import { computed } from "vue";
17
19
  Vue.use(VueChartkick);
@@ -122,7 +124,24 @@ function useChart({ dataSeries, spec, multiple = true }) {
122
124
  let colors = undefined;
123
125
  if (spec.colors) colors = spec.colors;
124
126
 
125
- const options = {};
127
+ let options = {}
128
+
129
+ if (spec.formatYAxis) {
130
+ options = {
131
+ scales: {
132
+ y: {
133
+ ticks: {
134
+ callback: function (value) {
135
+ return `${spec.prefix || ''}${value.toLocaleString()}${spec.suffix || ''}`;
136
+ }
137
+ }
138
+ }
139
+ },
140
+ plugins: {}
141
+ };
142
+ }
143
+
144
+
126
145
  options.plugins = {};
127
146
  if (legend) options.plugins.legend = legend;
128
147
  if (isDonut) options.cutout = '75%';
@@ -169,6 +188,14 @@ function useChart({ dataSeries, spec, multiple = true }) {
169
188
  };
170
189
  }
171
190
 
191
+ if (legend.override) {
192
+ options.plugins.htmlLegend = {
193
+ display: true
194
+ };
195
+ options.plugins.legend = {
196
+ display: false
197
+ };
198
+ }
172
199
  return { series, colors, options };
173
200
  }
174
201
 
@@ -153,19 +153,87 @@ export default defineComponent({
153
153
  ]
154
154
  };
155
155
  if (props.spec.mentionList) {
156
+ const simpleSource = (searchTerm, renderList, mentionChar) => {
157
+ const matches = props.spec.mentionList.toSorted().map((v, index) => ({ id: index + 1, value: v }));
158
+
159
+ if (searchTerm.length === 0) {
160
+ renderList(matches, searchTerm);
161
+ } else {
162
+ renderList(matches.filter((v) => v.value.toLowerCase().includes(searchTerm)), searchTerm);
163
+ }
164
+ };
165
+
166
+ const advancedSource = (searchTerm, renderList, mentionChar) => {
167
+
168
+ const compareFn = (a, b) => a.value > b.value;
169
+ const options = props.spec.mentionList.toSorted(compareFn);
170
+ const isGrouped = !!props.spec.mentionList[0].group;
171
+ let matches;
172
+
173
+ if (searchTerm.length === 0) {
174
+ matches = options;
175
+ } else {
176
+ matches = options.filter((v) => v.value.toLowerCase().includes(searchTerm));
177
+ }
178
+
179
+ if (isGrouped) {
180
+ const groupedMatches = Object.groupBy(matches, ({ group }) => group);
181
+ let tmpMatches = [];
182
+ Object.keys(groupedMatches).toSorted().forEach((group) => {
183
+ tmpMatches.push({ id: group, value: group, disabled: true });
184
+ groupedMatches[group].forEach((item) => {
185
+ tmpMatches.push(item);
186
+ });
187
+ });
188
+ matches = tmpMatches;
189
+ }
190
+
191
+ renderList(matches, searchTerm);
192
+ };
193
+
194
+ const source = typeof props.spec.mentionList[0] == 'object' ? advancedSource : simpleSource;
195
+
156
196
  Quill.register({ "blots/mention": MentionBlot, "modules/mention": Mention });
157
197
  modules.mention = {
158
198
  allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/,
159
199
  mentionDenotationChars: ["@"],
160
- source: function (searchTerm, renderList, mentionChar) {
161
- const values = props.spec.mentionList.map((v, index) => ({ id: index + 1, value: v }));
200
+ renderItem: (data) => {
201
+ if (data.disabled) {
202
+ const div = document.createElement("div");
203
+ div.classList.add('glib-quill-group');
204
+ div.innerText = data.value;
205
+ return div;
206
+ }
207
+ const div = document.createElement("div");
208
+ div.classList.add('glib-quill-item');
209
+
210
+ if (data.avatar) {
211
+ const img = document.createElement('img');
212
+ img.src = data.avatar;
213
+ img.classList.add('avatar');
214
+ div.appendChild(img);
215
+ }
162
216
 
163
- if (searchTerm.length === 0) {
164
- renderList(values, searchTerm);
165
- } else {
166
- renderList(values.filter((v) => v.value.toLowerCase().includes(searchTerm)), searchTerm);
217
+ const childDiv = document.createElement('div');
218
+ div.appendChild(childDiv);
219
+
220
+ if (data.text) {
221
+ const title = document.createElement('div');
222
+ title.style.lineHeight = '170%';
223
+ title.innerText = data.text;
224
+ childDiv.appendChild(title);
167
225
  }
168
- }
226
+
227
+ const subtitle = document.createElement('div');
228
+ subtitle.innerText = data.value;
229
+ childDiv.appendChild(subtitle);
230
+
231
+ return div;
232
+ },
233
+ renderLoading: () => {
234
+ return "Loading...";
235
+ },
236
+ source: source
169
237
  };
170
238
  }
171
239
  onMounted(() => {
@@ -218,4 +286,36 @@ export default defineComponent({
218
286
  .ql-container {
219
287
  height: fit-content;
220
288
  }
289
+
290
+ .glib-quill-group {
291
+ /* padding: 16px 0; */
292
+ color: #A7ADB5;
293
+ font-size: 12px;
294
+ height: 12px;
295
+ line-height: 12px;
296
+ }
297
+
298
+ .glib-quill-item {
299
+ padding: 8px 0;
300
+ display: flex;
301
+ gap: 12px;
302
+ flex-wrap: nowrap;
303
+ align-items: center;
304
+
305
+ .avatar {
306
+ width: 40px;
307
+ height: 40px;
308
+ border-radius: calc(infinity * 1px);
309
+ }
310
+
311
+ div {
312
+ font-size: 14px;
313
+ line-height: 100%;
314
+ }
315
+ }
316
+
317
+ .ql-mention-list-container {
318
+ padding-top: 8px !important;
319
+ padding-bottom: 8px !important;
320
+ }
221
321
  </style>
@@ -30,4 +30,8 @@ function closest(component, name) {
30
30
  }
31
31
  }
32
32
 
33
- export { realComponent, htmlElement, closest };
33
+ function strandom() {
34
+ return (Math.random() + 1).toString(36).substring(7) + Date.now().toString();
35
+ }
36
+
37
+ export { realComponent, htmlElement, closest, strandom };
@@ -88,7 +88,8 @@ export default {
88
88
  return `#${onClick.viewId}`;
89
89
  }
90
90
  }
91
- return '';
91
+
92
+ return null;
92
93
  },
93
94
  $onClick: function (explicitEvent, spec) {
94
95
  const properties = spec || this.spec;
@@ -1,17 +1,44 @@
1
1
  <template>
2
- <v-progress-circular :class="$classes()" :rotate="spec.rotate || 0" :size="spec.size || 100" :width="spec.width || 20"
3
- :color="spec.color" :model-value="spec.value" :indeterminate="spec.indeterminate">
4
- <div>
2
+ <div ref="container" :class="containerClass" :style="containerStyle">
3
+ <v-progress-circular ref="progress" :class="$classes()" :rotate="rotate" :size="spec.size || 100"
4
+ :width="spec.width || 20" :color="spec.color" :model-value="model" :indeterminate="spec.indeterminate">
5
+ <div :style="middleTextStyle">
5
6
  <div class="value-style text-center">{{ spec.value }}%</div>
6
7
  <span v-if="spec.text" class="text-style">{{ spec.text }}</span>
7
8
  </div>
8
- </v-progress-circular>
9
+ </v-progress-circular>
10
+ </div>
9
11
  </template>
10
12
 
11
13
  <script>
14
+ import { onMounted, ref } from "vue";
15
+
12
16
  export default {
13
17
  props: {
14
18
  spec: { type: Object, required: true }
19
+ },
20
+ setup(props) {
21
+ const container = ref(null);
22
+ const progress = ref(null);
23
+ const model = ref(props.spec.half ? props.spec.value / 2 : props.spec.value);
24
+ const rotate = ref(props.spec.half ? 270 : 0);
25
+ const containerStyle = ref({ width: '100%', height: '100%' });
26
+ const middleTextStyle = ref({});
27
+ const containerClass = ref([]);
28
+
29
+ function getHeight() {
30
+ return parseInt(progress.value.$el.style.height.replace('px', ''));
31
+ }
32
+
33
+ onMounted(() => {
34
+ if (props.spec.half) {
35
+ middleTextStyle.value = { marginBottom: `${getHeight() / 3}px` };
36
+ containerStyle.value = { width: '100%', height: `${getHeight() / 2}px` };
37
+ containerClass.value = ['half-circle'];
38
+ }
39
+ });
40
+
41
+ return { container, containerClass, containerStyle, middleTextStyle, model, rotate, progress };
15
42
  }
16
43
  };
17
44
  </script>
@@ -28,4 +55,9 @@ export default {
28
55
  line-height: 150%;
29
56
  font-weight: 400;
30
57
  }
58
+
59
+ .half-circle {
60
+ overflow-y: hidden;
61
+ position: relative;
62
+ }
31
63
  </style>
@@ -1,4 +1,5 @@
1
1
  <template>
2
+
2
3
  <v-progress-linear :class="$classes()" :height="spec.height" :color="spec.color"
3
4
  :background-color="spec.backgroundColor" :disabled="true" :model-value="value_in_percentage"
4
5
  :striped="$classes().includes('striped')" :reverse="spec.reversed || false" :rounded="spec.rounded">
package/nav/dialog.vue CHANGED
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <v-dialog :class="spec.styleClass" :model-value="model" :width="spec.width || 600" :dark="false"
3
3
  :fullscreen="fullscreen" :sm-and-down="false" :persistent="true" @click:outside="clickOutside">
4
- <v-card :style="hamburgerStyles" class="dialog-hamburger">
4
+ <v-card :style="hamburgerStyles" class="dialog-hamburger" :key="dialogKey">
5
5
 
6
6
  <panels-responsive v-if="header" :spec="header" />
7
7
  <div :class="`dialog-title ${title ? '' : 'dialog-absolute'}`">
@@ -46,6 +46,7 @@ import FormPanel from "../components/panels/form.vue";
46
46
  import { confirmDirty, dialogs } from "../store";
47
47
  import { onMounted, ref } from "vue";
48
48
  import { usePasteable } from "../components/composable/pasteable";
49
+ import { strandom } from "../components/helper.js";
49
50
 
50
51
  export default {
51
52
  expose: ['model', 'close', 'isFormDirty'],
@@ -84,7 +85,8 @@ export default {
84
85
  url: null,
85
86
  urlLoaded: false,
86
87
  isMobile: false,
87
- formSpec: null
88
+ formSpec: null,
89
+ dialogKey: 'init',
88
90
  };
89
91
  },
90
92
  computed: {
@@ -157,6 +159,7 @@ export default {
157
159
  this.url = newSpec.url;
158
160
  this.urlLoaded = false;
159
161
  this.show(true);
162
+ this.dialogKey = strandom();
160
163
  },
161
164
  onResize() {
162
165
  this.isMobile = window.innerWidth < 600;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "4.27.2",
3
+ "version": "4.28.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/utils/settings.js CHANGED
@@ -9,6 +9,7 @@ class MutableSettings {
9
9
  retries: 3,
10
10
  interval: 50
11
11
  };
12
+ this.chartPlugin = {};
12
13
  this.errorHandler = (err, message) => {
13
14
  console.error(message || err.message, err);
14
15
  };