glib-web 4.5.1 → 4.6.1

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,5 +1,6 @@
1
1
  <template>
2
- <component :is="componentName" :class="cssClasses" :style="$styles()" :href="$href()" @click="$onClick()">
2
+ <component v-if="loadIf" :is="componentName" :class="cssClasses" :style="$styles()" :href="$href()"
3
+ @click="$onClick()">
3
4
  <panels-responsive v-if="header" :spec="header" />
4
5
 
5
6
  <!--
@@ -24,7 +24,7 @@
24
24
 
25
25
  <component :draggable="!!spec.dragData" ref="delegate" v-else-if="name" :is="name" :id="spec.id" :spec="spec"
26
26
  @[menter]="handlePopover(spec.onMouseEnter)" @[mleave]="handlePopover(spec.onMouseLeave)"
27
- @[dstart]="handleDragStart" />
27
+ @[dstart()]="handleDragStart" />
28
28
  <div v-else>Unsupported view: {{ spec.view }}</div>
29
29
  </Suspense>
30
30
  </template>
@@ -228,17 +228,6 @@ export default {
228
228
  // $passthrough: true
229
229
  };
230
230
  },
231
- computed: {
232
- menter() {
233
- return this.spec.onMouseEnter ? 'mouseenter' : null;
234
- },
235
- mleave() {
236
- return this.spec.onMouseLeave ? 'mouseleave' : null;
237
- },
238
- dstart() {
239
- return this.spec.dragData ? 'dragstart' : null;
240
- }
241
- },
242
231
  watch: {
243
232
  spec: {
244
233
  handler(spec) {
@@ -284,9 +273,6 @@ export default {
284
273
  },
285
274
  handlePopover(spec) {
286
275
  GLib.action.execute(spec, this);
287
- },
288
- handleDragStart(event) {
289
- event.dataTransfer.setData('text', JSON.stringify(this.spec.dragData));
290
276
  }
291
277
  },
292
278
  };
@@ -12,14 +12,14 @@ export function useDirtyState() {
12
12
  };
13
13
 
14
14
  const clearDirtyState = (context) => {
15
- const ctx = context || ctx;
15
+ context ||= ctx();
16
16
  ctx.isFormDirty = false;
17
17
  };
18
18
 
19
19
  const setDirty = (context) => (context || ctx()).isFormDirty = true;
20
20
 
21
21
  const updateDirtyState = (context) => {
22
- const ctx = context || ctx;
22
+ context ||= ctx();
23
23
  const clean = Object.keys(dirtySpecs).reduce((prev, curr) => {
24
24
  if (dirtySpecs[curr].disable) return prev;
25
25
  const initValue = dirtySpecs[curr].initValue || '';
@@ -27,7 +27,7 @@ export function useDirtyState() {
27
27
  return prev && (currValue.toString() == initValue.toString() || !currValue.toString());
28
28
  }, true);
29
29
 
30
- clean ? clearDirtyState(ctx) : setDirty(ctx);
30
+ clean ? clearDirtyState(context) : setDirty(context);
31
31
  };
32
32
 
33
33
  return { clearDirtyState, setDirty, isDirty, updateDirtyState };
@@ -13,6 +13,7 @@ function useFileUtils() {
13
13
  this._url = options.url;
14
14
  this._type = options.type;
15
15
  this.el = options.el;
16
+ this.size = options.size;
16
17
  }
17
18
 
18
19
  isImage() {
@@ -37,7 +37,7 @@ function useGlibTreeNode(props) {
37
37
  }
38
38
 
39
39
  function handleDragLeave(e, node) {
40
- node.selected = false;
40
+ if (selected.value !== node.id) node.selected = false;
41
41
  currTreeNode.value = null;
42
42
  }
43
43
 
@@ -1,5 +1,6 @@
1
1
  import { vueApp } from "../../store";
2
2
  import { useFileUtils } from "./file";
3
+ import Uploader from "../../utils/glibDirectUpload";
3
4
 
4
5
  const { makeKey } = useFileUtils();
5
6
 
@@ -11,6 +12,11 @@ function uploadFiles(obj) {
11
12
  files: Array.from(droppedFiles).reduce((prev, curr) => {
12
13
  prev[makeKey()] = curr;
13
14
 
15
+ const uploader = new Uploader(curr, spec.directUploadUrl, undefined, spec.storagePrefix, spec.metadata, spec.tagging || spec.tags);
16
+ if (!uploader.validateFile({ accepts: spec.accepts })) {
17
+ return;
18
+ }
19
+
14
20
  return prev;
15
21
  }, {}),
16
22
  spec: spec
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div ref="container" :style="$styles()" :class="$classes()" v-if="loadIf">
3
- <v-autocomplete :color="gcolor" v-model="fieldModel" :label="label" :items="normalizedOptions"
3
+ <component :is="compName" :color="gcolor" v-model="fieldModel" :label="label" :items="normalizedOptions"
4
4
  :chips="spec.multiple" :multiple="spec.multiple" :readonly="spec.readOnly" :clearable="!spec.readOnly"
5
5
  :placeholder="spec.placeholder" :rules="rules" persistent-hint :append-icon="append.icon" validate-on="blur"
6
6
  item-title='text' :variant="variant" :closable-chips="spec.multiple" :density="density" persistent-placeholder
@@ -27,7 +27,7 @@
27
27
  <template v-slot:append-item v-if="spec.footer">
28
28
  <common-responsive :spec="spec.footer" />
29
29
  </template>
30
- </v-autocomplete>
30
+ </component>
31
31
 
32
32
  <input v-for="(item, index) in values" :key="index" type="hidden" :disabled="inputDisabled" :name="fieldName"
33
33
  :value="item" />
@@ -54,6 +54,9 @@ export default {
54
54
  };
55
55
  },
56
56
  computed: {
57
+ compName() {
58
+ return this.spec.searchable ? 'v-autocomplete' : 'v-select';
59
+ },
57
60
  normalizedOptions() {
58
61
  return this.spec.options.map(i => {
59
62
  switch (i.type) {
@@ -123,3 +126,10 @@ export default {
123
126
  }
124
127
  };
125
128
  </script>
129
+
130
+ <style lang="scss" scoped>
131
+ // This is to ensure that the text does not get clipped by the clear icon.
132
+ ::v-deep .v-autocomplete__selection {
133
+ display: block;
134
+ }
135
+ </style>
@@ -20,20 +20,29 @@
20
20
  <template v-for="(file, index) in Object.entries(files)" :key="`added-${index}`">
21
21
  <div :class="`file-container ${file[1].status == 'failed' ? 'opacity-50' : ''}`">
22
22
  <div class="file">
23
- <div @click.stop="handleRemoveFile(file[0])" class="close-btn">
24
- <v-icon size="12" color="white">close</v-icon>
25
- </div>
26
23
  <div class="status">
27
- <v-icon class="icon">{{ file[1].isImage() ? 'photo_library' : 'description' }}</v-icon>
24
+ <div class="image-icon">
25
+ <img v-if="file[1].isImage()" :src="pic" alt="img-icon" />
26
+ <img v-else-if="file[1].type.startsWith('application/pdf')" :src="pdf" alt="img-icon" />
27
+ <img v-else :src="doc" alt="img-icon" />
28
+ </div>
28
29
  <div class="progress">
29
- <div class="label"><a :href="file[1].url" target="_blank">{{ `${file[1].name}` }}</a><span
30
- v-if="file[1].message" style="font-size: 0.75em">{{
31
- file[1].message }}</span></div>
32
- <div class="background" v-show="file[1].progress.value > 0">
33
- <div class="value" :style="{ width: `${file[1].progress.value}%` }"></div>
30
+ <div class="label"><a :href="file[1].url" target="_blank">{{ `${file[1].name}` }}</a>
31
+ <span v-if="file[1].message" :class="file[1].message != 'Completed' ? 'text-error' : ''">
32
+ {{ file[1].message }}
33
+ </span>
34
34
  </div>
35
35
  </div>
36
+ <div @click.stop="handleRemoveFile(file[0])" class="close-btn">
37
+ <v-icon size="24" color="black">close</v-icon>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ <div class="percentage-wrapper" v-show="file[1].progress.value > 0 && file[1].progress.value < 100">
42
+ <div class="background">
43
+ <div class="value" :style="{ width: `${file[1].progress.value}%` }"></div>
36
44
  </div>
45
+ <div class="percentage">{{ parseInt(file[1].progress.value) }}%</div>
37
46
  </div>
38
47
  </div>
39
48
  </template>
@@ -73,12 +82,14 @@
73
82
  .file-container {
74
83
  display: flex;
75
84
  align-items: center;
85
+ flex-direction: column;
76
86
  border-radius: 6px;
77
87
  background-color: #f3f4f6;
78
88
  width: 100%;
79
89
  padding: 16px;
80
90
  margin-bottom: 16px;
81
91
  min-height: 72px;
92
+ gap: 8px;
82
93
  }
83
94
 
84
95
  .file {
@@ -89,8 +100,24 @@
89
100
  .status {
90
101
  display: flex;
91
102
  width: 100%;
103
+ gap: 8px;
92
104
  align-items: center;
93
105
 
106
+ .image-icon {
107
+ width: 40px;
108
+ height: 40px;
109
+ background-color: #fff;
110
+ border-radius: 5px;
111
+ display: flex;
112
+ justify-content: center;
113
+ align-items: center;
114
+
115
+ img {
116
+ width: 20px;
117
+ height: 20px;
118
+ }
119
+ }
120
+
94
121
  .icon {
95
122
  margin-right: 8px;
96
123
  }
@@ -104,14 +131,13 @@
104
131
  .label {
105
132
  /* mb-1 */
106
133
  display: flex;
107
- align-items: center;
108
- justify-content: space-between;
109
- flex-wrap: nowrap;
134
+ flex-direction: column;
110
135
  margin-bottom: 4px;
111
136
  }
112
137
 
113
138
  .label a {
114
139
  color: #47495F;
140
+ font-weight: 600;
115
141
  }
116
142
 
117
143
  .label a:hover {
@@ -124,34 +150,41 @@
124
150
  text-decoration: none !important;
125
151
  }
126
152
 
127
- .background {
128
- width: 100%;
129
- background-color: #e5e7eb;
130
- border-radius: 9999px;
131
- height: 6px;
132
-
133
- .value {
134
- background-color: #4b5563;
135
- height: inherit;
136
- border-radius: inherit;
137
- }
138
- }
139
153
  }
140
154
  }
141
155
 
142
156
  .close-btn {
143
- border-radius: 9999px;
144
- background-color: #6b7280;
145
- position: absolute;
146
- top: -24px;
147
- right: -24px;
148
- display: flex;
149
- justify-content: center;
150
- align-items: center;
151
- padding: 2px;
152
157
  cursor: pointer;
153
158
  }
154
159
  }
160
+
161
+ .background {
162
+ width: 100%;
163
+ background-color: #e6e6e6;
164
+ border-radius: 9999px;
165
+ height: 8px;
166
+ display: flex;
167
+
168
+ .value {
169
+ background-color: #0A2A9E;
170
+ height: inherit;
171
+ border-radius: inherit;
172
+ }
173
+ }
174
+
175
+ .percentage-wrapper {
176
+ width: 100%;
177
+ display: flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ gap: 8px;
181
+
182
+ .percentage {
183
+ font-size: 14px;
184
+ font-weight: 600;
185
+ margin-bottom: 2px;
186
+ }
187
+ }
155
188
  }
156
189
 
157
190
  .gdrop-file {
@@ -201,6 +234,9 @@ import * as delegateUploader from "../composable/upload_delegator";
201
234
  import { triggerOnChange } from "../composable/form";
202
235
  import { nextTick } from "vue";
203
236
  import { useFilesState, useFileUtils } from "../composable/file";
237
+ import doc from "./selectAsset/doc-1.png"
238
+ import pic from "./selectAsset/pic-1.png"
239
+ import pdf from "./selectAsset/pdf-1.png"
204
240
  const { makeKey, Item, signedIds } = useFileUtils();
205
241
 
206
242
  export default defineComponent({
@@ -336,7 +372,10 @@ export default defineComponent({
336
372
  handleRemoveFile,
337
373
  showUploadedFile,
338
374
  uploadTitle,
339
- props
375
+ props,
376
+ doc,
377
+ pic,
378
+ pdf,
340
379
  };
341
380
  }
342
381
  })
@@ -2,7 +2,21 @@ import { vueApp } from "../../store";
2
2
  import { ValidationFactory } from "../validation";
3
3
 
4
4
  export default {
5
+ computed: {
6
+ menter() {
7
+ return this.spec.onMouseEnter ? 'mouseenter' : null;
8
+ },
9
+ mleave() {
10
+ return this.spec.onMouseLeave ? 'mouseleave' : null;
11
+ }
12
+ },
5
13
  methods: {
14
+ dstart(dragData) {
15
+ return dragData || this.spec.dragData ? 'dragstart' : null;
16
+ },
17
+ handleDragStart(event, dragData) {
18
+ event.dataTransfer.setData('text', JSON.stringify(dragData || this.spec.dragData));
19
+ },
6
20
  $componentName() { // Can be overridden
7
21
  if (this.spec) {
8
22
  return this.spec.view?.replace('-v1', '');
@@ -298,7 +298,12 @@ export default {
298
298
  const vm = realComponent(this);
299
299
  const val = vm.$sanitizeValue(vm.$externalizeValue(Utils.type.isNotNull(newSpec.value) ? newSpec.value : this.fieldModel));
300
300
  newSpec.value = isNeedToBeFixed(val, vm) ? val.toFixed(vm.spec.precision || NUMBER_PRECISION) : val;
301
- vm.fieldModel = newSpec.value;
301
+
302
+ if (newSpec.value !== vm.fieldModel) {
303
+ vm.fieldModel = newSpec.value;
304
+ vm.$executeOnChange();
305
+ }
306
+
302
307
  if (Utils.type.isNotNull(newSpec.displayed)) vm._show = newSpec.displayed;
303
308
  vm._submitWhenNotDisplayed = newSpec.submitWhenNotDisplayed;
304
309
  Object.assign(vm.spec, newSpec);
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <common-responsive ref="delegate" v-if="loadIf" :spec="spec" />
2
+ <common-responsive ref="delegate" :spec="spec" />
3
3
  </template>
4
4
 
5
5
  <script>
@@ -11,7 +11,7 @@
11
11
  <template v-if="importable">
12
12
  <span>{{
13
13
  rows(section).length + section.dataRows.length
14
- }}
14
+ }}
15
15
  rows</span>
16
16
  <input ref="fileInput" style="display: none;" type="file" accept=".csv"
17
17
  @change="loadFile($event, section)" />
@@ -48,7 +48,8 @@
48
48
  <tbody>
49
49
  <!-- <tr v-for="(row, index) in section.rows" :key="`row_${index}`"> -->
50
50
  <template v-for="(row, rowIndex) in section.rows" :key="`row_${rowIndex}`">
51
- <tr :class="row.onClick ? 'clickable' : ''">
51
+ <tr :class="row.onClick ? 'clickable' : ''"
52
+ @[dstart(row.dragData)]="(e) => handleDragStart(e, row.dragData)" :draggable="!!row.dragData">
52
53
  <td v-for="(cell, cellIndex) in row.cellViews" :key="`cell_${cellIndex}`"
53
54
  :colSpan="colSpan(row, cellIndex)" :style="colStyles(row, cellIndex)">
54
55
  <span>
@@ -7,6 +7,7 @@ export default class TreeNode {
7
7
  } else {
8
8
  this.expand = false;
9
9
  this.title = options.title;
10
+ this.subtitle = options.subtitle;
10
11
  this.id = options.id;
11
12
  this.dropData = options.dropData;
12
13
  this.icon = options.icon;
@@ -1,14 +1,18 @@
1
1
  <template>
2
2
  <div class="gtree-row">
3
3
  <div :class="`${node.selected ? 'node selected' : 'node'}`" @dragover="(e) => handleDragOver(e, node)"
4
- @drop="(e) => handleDrop(e, node)" @dragleave="(e) => handleDragLeave(e, node)">
4
+ @drop="(e) => handleDrop(e, node)" @dragleave="(e) => handleDragLeave(e, node)" @click="handleClick(node)">
5
5
  <div class="content">
6
- <v-btn style="z-index: 11" size="24" :icon="node.expand ? 'arrow_drop_down' : 'arrow_right'"
7
- v-if="props.node.rows" @click="node.expand = !node.expand" variant="plain"></v-btn>
6
+ <v-btn style="z-index: 11;" size="24" :icon="node.expand ? 'arrow_drop_down' : 'arrow_right'"
7
+ v-if="props.node.rows" @click="node.expand = !node.expand" variant="plain" @click.stop></v-btn>
8
8
  <div style="width: 24px" v-else></div>
9
- <div class="title" @click="handleClick(node)">
10
- <v-icon v-if="node.icon" size="24" :icon="node.icon.name" :color="node.icon.color"></v-icon>
11
- <span>{{ node.title }}</span>
9
+ <div class="text">
10
+ <v-icon v-if="node.icon" :size="node.icon.size || 24" :icon="node.icon.name"
11
+ :color="node.icon.color"></v-icon>
12
+ <div style="display: flex; flex-direction: column; width: 100%">
13
+ <div class="title">{{ node.title }}</div>
14
+ <div class="subtitle" v-if="node.subtitle">{{ node.subtitle }}</div>
15
+ </div>
12
16
  </div>
13
17
  </div>
14
18
  <div class="overlay"></div>
@@ -25,6 +29,7 @@
25
29
  <style lang="scss">
26
30
  .gtree-row .node {
27
31
  position: relative;
32
+ width: 100%;
28
33
  }
29
34
 
30
35
  .gtree-row .node .overlay {
@@ -34,7 +39,7 @@
34
39
  }
35
40
 
36
41
  .gtree-row .node .content {
37
- padding: 0 8px;
42
+ padding: 8px 16px;
38
43
  width: 100%;
39
44
  display: flex;
40
45
  align-items: center;
@@ -48,11 +53,25 @@
48
53
  opacity: 0.04;
49
54
  }
50
55
 
51
- .gtree-row .node .title {
56
+ .gtree-row .node .content .text {
57
+ width: 100%;
52
58
  display: flex;
53
- gap: 2px;
59
+ gap: 6px;
54
60
  align-items: center;
55
61
  z-index: 10;
62
+ pointer-events: none;
63
+ overflow: hidden;
64
+
65
+ .title,
66
+ .subtitle {
67
+ text-overflow: ellipsis;
68
+ white-space: nowrap;
69
+ }
70
+
71
+ .subtitle {
72
+ font-weight: lighter;
73
+ font-size: smaller;
74
+ }
56
75
  }
57
76
 
58
77
  .gtree-row .child-0-row {
@@ -65,7 +84,6 @@
65
84
  gap: 2px;
66
85
  align-items: center;
67
86
  cursor: pointer;
68
- height: 40px;
69
87
  }
70
88
  </style>
71
89
 
@@ -1,10 +1,10 @@
1
1
  <template>
2
2
  <div :style="$styles()" :class="$classes()" v-if="loadIf">
3
- <template v-for="(section, index) in props.spec.sections" :key="index">
3
+ <template v-for="(section, index) in sections" :key="`${index}-${compKey}`">
4
4
  <panels-responsive :spec="section.header" v-if="section.header"></panels-responsive>
5
5
 
6
6
  <component :is="template(row.template)" v-for="(row, index) in section.rows"
7
- v-bind="{ node: new TreeNode(row), level: 0 }" :key="index">
7
+ v-bind="{ node: treeNode(row), level: 0 }" :key="index">
8
8
  </component>
9
9
 
10
10
  <panels-responsive :spec="section.footer" v-if="section.footer"></panels-responsive>
@@ -24,14 +24,38 @@ function template(name) {
24
24
  }[name];
25
25
  }
26
26
 
27
+ function treeNode(row) {
28
+ return new TreeNode(row);
29
+ }
30
+
27
31
  const props = defineProps(['spec']);
28
32
  const dropBox = ref({ data: {}, files: [] });
29
33
  const selected = computed(() => props.spec.selected);
30
34
  const currTreeNode = ref({});
35
+ const sections = ref(props.spec.sections);
36
+ const compKey = ref(0);
37
+
38
+ watch(props, (val) => {
39
+ sections.value = val.spec.sections;
40
+ compKey.value += 1;
41
+ });
31
42
 
32
43
  watch(dropBox, (val) => {
33
44
  if (!val) return;
34
- Action.execute(props.spec.onDrop, {}, val);
45
+
46
+ if (val.files.length > 0) {
47
+ Action.execute(props.spec.onDropFile, {}, val);
48
+ } else {
49
+ delete val.files;
50
+
51
+ const params = {
52
+ [props.spec.paramNameForFormData || 'formData']: val.data
53
+ };
54
+
55
+ const data = Object.assign({}, props.spec.onDropView, params);
56
+ // TODO: Need to pass the component as the second parameter
57
+ Action.execute(data, {});
58
+ }
35
59
  });
36
60
 
37
61
  provide('dropBox', dropBox);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "4.5.1",
3
+ "version": "4.6.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {