@itfin/components 1.4.35 → 1.4.40
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/package.json +1 -1
- package/src/components/app/App.vue +3 -6
- package/src/components/checkbox/Checkbox.vue +1 -1
- package/src/components/checkbox/RadioBox.vue +6 -13
- package/src/components/filter/FilterBadge.vue +5 -0
- package/src/components/filter/FilterPanel.vue +54 -16
- package/src/components/icon/components/nomi-arrow-right.vue +4 -0
- package/src/components/icon/components/nomi-cash-repeat.vue +6 -0
- package/src/components/icon/components/nomi-chavron-up.vue +4 -0
- package/src/components/icon/components/nomi-chavron_down.vue +4 -0
- package/src/components/icon/components/nomi-chavron_up.vue +4 -0
- package/src/components/icon/components/nomi-chevron-up.vue +4 -0
- package/src/components/icon/components/nomi-history.vue +7 -0
- package/src/components/icon/components/nomi-pen-alt.vue +4 -0
- package/src/components/panels/PanelItemEdit.vue +6 -8
- package/src/components/panels/PanelList.vue +33 -102
- package/src/components/panels/helpers.ts +11 -29
- package/src/components/table/Table2.vue +65 -61
- package/src/components/table/TableBody.vue +6 -0
- package/src/components/table/TableGroup.vue +14 -4
- package/src/components/table/TableHeader.vue +12 -7
- package/src/components/table/TableRowToggle.vue +9 -1
- package/src/components/table/TableRows.vue +55 -43
- package/src/components/table/table2.scss +15 -25
- package/src/components/view/View.vue +15 -8
package/package.json
CHANGED
|
@@ -74,12 +74,9 @@ class itfApp extends Vue {
|
|
|
74
74
|
try {
|
|
75
75
|
await func();
|
|
76
76
|
} catch (err) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (errFunc) {
|
|
81
|
-
errFunc(err);
|
|
82
|
-
}
|
|
77
|
+
this.showError(err.message);
|
|
78
|
+
if (errFunc) {
|
|
79
|
+
errFunc(err);
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
82
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<div class="itf-checkbox form-check" :class="{ 'form-switch': this.switch, 'itf-checkbox__large': large, 'itf-checkbox__medium': medium }">
|
|
4
4
|
<input class="form-check-input" ref="input" :id="id" type="checkbox" name="checkbox" v-model="isChecked" :disabled="isDisabled" />
|
|
5
|
-
<label :for="id" class="form-check-label
|
|
5
|
+
<label :for="id" class="form-check-label">
|
|
6
6
|
<slot name="label">
|
|
7
7
|
{{label}}
|
|
8
8
|
<slot name="icon"></slot>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="itf-radio-box form-check card" :class="{ '
|
|
2
|
+
<div class="itf-radio-box form-check card" :class="{ 'itf-radio__large': large, 'itf-radio__medium': medium, 'active': isChecked, 'right': right, 'left': !right }">
|
|
3
3
|
<input class="form-check-input" :id="id" type="radio" :name="radioName" v-model="isChecked" :value="true" :disabled="disabled" />
|
|
4
4
|
<label :for="id" slot="label" class="form-check-label card-body">
|
|
5
5
|
|
|
@@ -18,17 +18,6 @@
|
|
|
18
18
|
position: relative;
|
|
19
19
|
cursor: pointer;
|
|
20
20
|
|
|
21
|
-
&:not(.disabled) {
|
|
22
|
-
cursor: pointer;
|
|
23
|
-
|
|
24
|
-
.form-check-label {
|
|
25
|
-
cursor: pointer;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.form-check-label {
|
|
30
|
-
cursor: not-allowed;
|
|
31
|
-
}
|
|
32
21
|
&.left {
|
|
33
22
|
padding: 0 0 0 2.5rem;
|
|
34
23
|
|
|
@@ -49,9 +38,13 @@
|
|
|
49
38
|
&.active {
|
|
50
39
|
background-color: rgba(var(--bs-primary-rgb),.1) !important;
|
|
51
40
|
}
|
|
52
|
-
&:hover
|
|
41
|
+
&:hover {
|
|
53
42
|
background-color: rgba(0,0,0,.05);
|
|
54
43
|
}
|
|
44
|
+
|
|
45
|
+
.form-check-label {
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
}
|
|
55
48
|
}
|
|
56
49
|
</style>
|
|
57
50
|
<script>
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
<itf-date-picker-inline
|
|
41
41
|
style="margin: -.5rem"
|
|
42
42
|
:value="value.value"
|
|
43
|
+
:only-calendar="options.calendarOptions && options.calendarOptions.onlyCalendar"
|
|
44
|
+
:max-date="options.calendarOptions && options.calendarOptions.maxDate"
|
|
45
|
+
:min-date="options.calendarOptions && options.calendarOptions.minDate"
|
|
46
|
+
:start-view="options.calendarOptions && options.calendarOptions.startView"
|
|
47
|
+
:min-view="options.calendarOptions && options.calendarOptions.minView"
|
|
43
48
|
@input="onFilterChange({ value: $event })"
|
|
44
49
|
/>
|
|
45
50
|
</template>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="itf-filter-panel d-flex flex-column
|
|
2
|
+
<div class="itf-filter-panel d-flex flex-column align-items-start" :class="{'gap-3': !filtersOnly}">
|
|
3
3
|
<div v-if="!filtersOnly" class="d-flex gap-2 justify-content-between w-100">
|
|
4
4
|
<slot name="search">
|
|
5
5
|
<div>
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
class="itf-filter-panel__badge"
|
|
36
36
|
:ref="'item-' + n"
|
|
37
37
|
v-model="filter[facet.name]"
|
|
38
|
-
:is-default="filter[facet.name].isDefault"
|
|
39
|
-
:text="filter[facet.name].label"
|
|
38
|
+
:is-default="filter[facet.name] && filter[facet.name].isDefault"
|
|
39
|
+
:text="filter[facet.name] && filter[facet.name].label"
|
|
40
40
|
:type="facet.type"
|
|
41
41
|
:icon="facet.icon"
|
|
42
42
|
:options="facet.options"
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
<slot name="after-filters"></slot>
|
|
54
54
|
</div>
|
|
55
|
-
<div v-if="loading">
|
|
55
|
+
<div v-if="loading && !visibleFilters.length">
|
|
56
56
|
<span class="itf-spinner"></span>
|
|
57
57
|
{{$t('loading')}}
|
|
58
58
|
</div>
|
|
@@ -177,6 +177,8 @@ class FilterPanel extends Vue {
|
|
|
177
177
|
(entries) => {
|
|
178
178
|
entries.forEach(entry => {
|
|
179
179
|
const index = parseInt(entry.target.dataset.index);
|
|
180
|
+
const filter = this.filters[index];
|
|
181
|
+
const value = this.filter[filter.name];
|
|
180
182
|
if (entry.isIntersecting) {
|
|
181
183
|
this.visibleItems.add(index); // Додаємо, якщо елемент у полі зору
|
|
182
184
|
} else {
|
|
@@ -206,7 +208,7 @@ class FilterPanel extends Vue {
|
|
|
206
208
|
|
|
207
209
|
get visibleFilters() {
|
|
208
210
|
if (this.mini) {
|
|
209
|
-
return sortBy(this.filters, (f) => this.filter[f.name].isDefault).filter(f => !f.options?.hidden)
|
|
211
|
+
return sortBy(this.filters, (f) => this.filter[f.name].isDefault).filter(f => !f.options?.hidden);
|
|
210
212
|
}
|
|
211
213
|
return this.filters.filter(f => !f.options?.hidden);
|
|
212
214
|
}
|
|
@@ -233,15 +235,7 @@ class FilterPanel extends Vue {
|
|
|
233
235
|
|
|
234
236
|
this.filters = this.staticFilters ?? [];
|
|
235
237
|
if (this.endpoint) {
|
|
236
|
-
this.
|
|
237
|
-
await this.$try(async () => {
|
|
238
|
-
const payload = this.panel ? this.panel.getPayload() : {};
|
|
239
|
-
const {filters, tableSchema} = await this.$axios.$get(this.endpoint, { params: payload });
|
|
240
|
-
this.filters = filters;
|
|
241
|
-
this.$emit('set-table-schema', tableSchema);
|
|
242
|
-
this.loadFiltersValue();
|
|
243
|
-
});
|
|
244
|
-
this.loading = false;
|
|
238
|
+
this.loadData();
|
|
245
239
|
} else {
|
|
246
240
|
this.loadFiltersValue();
|
|
247
241
|
}
|
|
@@ -250,6 +244,23 @@ class FilterPanel extends Vue {
|
|
|
250
244
|
}
|
|
251
245
|
}
|
|
252
246
|
|
|
247
|
+
async loadData() {
|
|
248
|
+
this.loading = true;
|
|
249
|
+
await this.$try(async () => {
|
|
250
|
+
const payload = this.panel ? this.panel.getPayload() : {};
|
|
251
|
+
const {filters, tableSchema} = await this.$axios.$get(this.endpoint, {
|
|
252
|
+
preventRaceCondition: true,
|
|
253
|
+
params: payload
|
|
254
|
+
});
|
|
255
|
+
this.filters = filters;
|
|
256
|
+
this.loading = false;
|
|
257
|
+
this.$emit('set-table-schema', tableSchema);
|
|
258
|
+
this.loadFiltersValue();
|
|
259
|
+
}, () => {
|
|
260
|
+
this.loading = false;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
253
264
|
toggleFilters() {
|
|
254
265
|
this.showFilters = !this.showFilters;
|
|
255
266
|
if (this.stateName) {
|
|
@@ -269,7 +280,7 @@ class FilterPanel extends Vue {
|
|
|
269
280
|
filterValue.to = payload.to;
|
|
270
281
|
} else {
|
|
271
282
|
filter[item.name] = payload[item.name] ? this.formatValue(item, { value: payload[item.name] }) : { isDefault: true, ...item.options.defaultValue };
|
|
272
|
-
filterValue[item.name] = payload[item.name]
|
|
283
|
+
filterValue[item.name] = payload[item.name];
|
|
273
284
|
}
|
|
274
285
|
}
|
|
275
286
|
}
|
|
@@ -282,6 +293,7 @@ class FilterPanel extends Vue {
|
|
|
282
293
|
this.filter = filter;
|
|
283
294
|
this.filterValue = filterValue;
|
|
284
295
|
this.$emit('input', this.filterValue);
|
|
296
|
+
this.$emit('loaded', this.filterValue);
|
|
285
297
|
this.initObserver();
|
|
286
298
|
}
|
|
287
299
|
}
|
|
@@ -318,6 +330,7 @@ class FilterPanel extends Vue {
|
|
|
318
330
|
this.panel.setPayload({ ...payload, ...this.filterValue });
|
|
319
331
|
}
|
|
320
332
|
this.$emit('input', this.filterValue);
|
|
333
|
+
this.$emit('change', this.filterValue);
|
|
321
334
|
}
|
|
322
335
|
|
|
323
336
|
get daysList() {
|
|
@@ -363,7 +376,28 @@ class FilterPanel extends Vue {
|
|
|
363
376
|
}
|
|
364
377
|
} else if (facet.type === 'date') {
|
|
365
378
|
const date = DateTime.fromISO(value.value);
|
|
366
|
-
|
|
379
|
+
const effectiveDate = (date.isValid ? date : DateTime.fromISO(facet.options.defaultValue.value ?? DateTime.now().toISO()));
|
|
380
|
+
// Якщо календарь в режимі вибору місяця (startView/minView === 'month'),
|
|
381
|
+
// відображаємо весь місяць, наприклад: "01 - 31 Mar 2026"
|
|
382
|
+
if (
|
|
383
|
+
facet.options?.calendarOptions &&
|
|
384
|
+
facet.options.calendarOptions.startView === 'months' &&
|
|
385
|
+
facet.options.calendarOptions.minView === 'months'
|
|
386
|
+
) {
|
|
387
|
+
const startOfMonth = effectiveDate.startOf('month');
|
|
388
|
+
const endOfMonth = effectiveDate.endOf('month');
|
|
389
|
+
const startLabel = startOfMonth.toFormat('dd');
|
|
390
|
+
const endLabel = endOfMonth.toFormat('dd MMM yyyy');
|
|
391
|
+
value.label = `${startLabel} - ${endLabel}`;
|
|
392
|
+
} else {
|
|
393
|
+
// Стандартний сценарій: одиночна дата
|
|
394
|
+
value.label = effectiveDate.toFormat('dd MMM yyyy');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
value.isDefault = facet.options.defaultValue ? value.value === facet.options.defaultValue.value : false;
|
|
398
|
+
} else if (facet.type === 'month') {
|
|
399
|
+
const date = DateTime.fromISO(value.value);
|
|
400
|
+
value.label = capitalizeFirstLetter((date.isValid ? date : DateTime.fromISO(facet.options.defaultValue.value)).toFormat('LLLL yyyy'));
|
|
367
401
|
value.isDefault = facet.options.defaultValue ? value.value === facet.options.defaultValue.value : false;
|
|
368
402
|
} else if (facet.type === 'facets-list') {
|
|
369
403
|
const firstItem = facet.options.items.find(item => item.value === (Array.isArray(value.value) ? value.value[0] : value.value));
|
|
@@ -402,6 +436,10 @@ class FilterPanel extends Vue {
|
|
|
402
436
|
}
|
|
403
437
|
value.hidden = facet.options?.hidden ?? false;
|
|
404
438
|
return value;
|
|
439
|
+
|
|
440
|
+
function capitalizeFirstLetter(string) {
|
|
441
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
442
|
+
}
|
|
405
443
|
}
|
|
406
444
|
}
|
|
407
445
|
</script>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<template><svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M15 5C16.8856 5 17.8283 5.00015 18.4141 5.58594C18.9998 6.17172 19 7.11438 19 9V11C19 11.389 18.9973 11.7379 18.9922 12.0518C18.4552 11.8439 17.823 11.9563 17.3896 12.3896C17.2259 12.5534 17.1091 12.7457 17.0371 12.9502H15.2002C13.8757 12.9502 12.7541 13.8093 12.3564 15H9C7.11438 15 6.17172 14.9998 5.58594 14.4141C5.00015 13.8283 5 12.8856 5 11V9C5 7.11438 5.00015 6.17172 5.58594 5.58594C6.17172 5.00015 7.11438 5 9 5H15ZM12 8C10.8954 8 10 8.89543 10 10C10 11.1046 10.8954 12 12 12C13.1046 12 14 11.1046 14 10C14 8.89543 13.1046 8 12 8ZM8 9C7.44772 9 7 9.44772 7 10C7 10.5523 7.44772 11 8 11C8.55228 11 9 10.5523 9 10C9 9.44772 8.55228 9 8 9ZM16 9C15.4477 9 15 9.44772 15 10C15 10.5523 15.4477 11 16 11C16.5523 11 17 10.5523 17 10C17 9.44772 16.5523 9 16 9Z" fill="currentColor"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.1318 13.1318C18.3076 12.9561 18.5928 12.9561 18.7686 13.1318L19.7686 14.1318C19.9443 14.3075 19.9443 14.5928 19.7686 14.7685L18.7686 15.7685C18.5928 15.9442 18.3076 15.9442 18.1318 15.7685C17.9561 15.5928 17.9561 15.3075 18.1318 15.1318L18.3633 14.9004H15.2002C14.6203 14.9004 14.1504 15.3703 14.1504 15.9502V16.9502C14.1504 17.1987 13.9487 17.4004 13.7002 17.4004C13.4517 17.4004 13.25 17.1987 13.25 16.9502V15.9502C13.25 14.8732 14.1232 14 15.2002 14H18.3633L18.1318 13.7685C17.9561 13.5928 17.9561 13.3075 18.1318 13.1318Z" fill="currentColor"/>
|
|
4
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2002 16.4999C19.4487 16.5 19.6504 16.7016 19.6504 16.9501V17.9501C19.6504 19.0271 18.7771 19.9003 17.7002 19.9003H14.5371L14.7685 20.1318C14.9442 20.3075 14.9442 20.5928 14.7685 20.7685C14.5928 20.9442 14.3075 20.9442 14.1318 20.7685L13.1318 19.7685C12.9561 19.5928 12.9561 19.3075 13.1318 19.1318L14.1318 18.1318C14.3075 17.9561 14.5928 17.956 14.7685 18.1318C14.9442 18.3075 14.9442 18.5928 14.7685 18.7685L14.5371 18.9999H17.7002C18.28 18.9999 18.75 18.53 18.75 17.9501V16.9501C18.75 16.7016 18.9516 16.4999 19.2002 16.4999Z" fill="currentColor"/>
|
|
5
|
+
</svg>
|
|
6
|
+
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<template><svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M12.3535 7.47363V11.6419" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
|
+
<path d="M15.966 13.7257L12.3535 11.6416" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
<path d="M7.47353 9.18446H4V5.71094" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
<path d="M6.9525 17.0431C8.02097 18.1124 9.38259 18.8409 10.8651 19.1362C12.3476 19.4316 13.8845 19.2806 15.2812 18.7024C16.6779 18.1242 17.8717 17.1448 18.7117 15.8879C19.5517 14.6311 20 13.1534 20 11.6418C20 10.1301 19.5517 8.6524 18.7117 7.39558C17.8717 6.13876 16.6779 5.1593 15.2812 4.5811C13.8845 4.0029 12.3476 3.85193 10.8651 4.14729C9.38259 4.44266 8.02097 5.17109 6.9525 6.24043L4 9.18424" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
6
|
+
</svg>
|
|
7
|
+
</template>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<template><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M6 11.9316V13.6002C6 13.8211 6.17909 14.0002 6.4 14.0002H8.06863C8.2808 14.0002 8.48429 13.9159 8.63432 13.7659L14.2343 8.16588C14.5467 7.85346 14.5467 7.34693 14.2343 7.03451L12.9657 5.76588C12.6533 5.45346 12.1467 5.45346 11.8343 5.76588L6.23431 11.3659C6.08429 11.5159 6 11.7194 6 11.9316Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
4
|
+
</template>
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-loading="loading" class="px-3 pt-2 h-100
|
|
2
|
+
<div v-loading="loading" class="px-3 pt-2 h-100">
|
|
3
3
|
<itf-form
|
|
4
4
|
ref="editForm"
|
|
5
|
-
class="d-flex flex-column justify-content-between
|
|
5
|
+
class="d-flex flex-column justify-content-between h-100"
|
|
6
6
|
@keydown.native.shift.enter.stop.prevent="onSaveClick"
|
|
7
7
|
@keydown.native.esc.stop.prevent="$emit('cancel')"
|
|
8
8
|
>
|
|
9
9
|
<slot></slot>
|
|
10
10
|
<div class="py-3 justify-content-end d-flex align-items-center sticky-container">
|
|
11
11
|
<div v-if="!hideFooter">
|
|
12
|
-
<itf-button v-tooltip.delay="'Hot key: Esc'" secondary
|
|
13
|
-
<span>{{
|
|
12
|
+
<itf-button v-tooltip.delay="'Hot key: Esc'" secondary :loading="loading" :disabled="loading" @click="$emit('cancel')">
|
|
13
|
+
<span>{{ $t('components.modal.cancel') }}</span>
|
|
14
14
|
</itf-button>
|
|
15
|
-
<itf-button v-tooltip.delay="'Hot key: Shift + Enter'" primary
|
|
16
|
-
<span>{{
|
|
15
|
+
<itf-button v-tooltip.delay="'Hot key: Shift + Enter'" primary :loading="loading" :disabled="loading" @click="onSaveClick">
|
|
16
|
+
<span>{{ $t('components.modal.save') }}</span>
|
|
17
17
|
</itf-button>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
@@ -51,8 +51,6 @@ import itfButton from '../button/Button.vue';
|
|
|
51
51
|
export default class PanelItemEdit extends Vue {
|
|
52
52
|
@Prop(Boolean) loading;
|
|
53
53
|
@Prop(Boolean) hideFooter;
|
|
54
|
-
@Prop({ type: String, default: function() { return this.$t('components.modal.save') } }) saveBtnText;
|
|
55
|
-
@Prop({ type: String, default: function() { return this.$t('components.modal.cancel') } }) cancelBtnText;
|
|
56
54
|
|
|
57
55
|
onSaveClick() {
|
|
58
56
|
if (this.$refs.editForm && !this.$refs.editForm.doValidation()) {
|
|
@@ -17,20 +17,14 @@
|
|
|
17
17
|
:icon="panel.icon"
|
|
18
18
|
:payload="panel.payload"
|
|
19
19
|
:expandable="panelsStack.length > 1"
|
|
20
|
-
:isFullSize="isFullSize"
|
|
21
20
|
:collapsed="panel.isCollapsed"
|
|
22
21
|
:closeable="panel.isCloseable"
|
|
23
22
|
:animate="panel.isAnimate"
|
|
24
23
|
@open="openPanel($event[0], $event[1], n + 1)"
|
|
25
24
|
@expand="expandPanel(panel)"
|
|
26
25
|
@fullsize="fullsizePanel(panel)"
|
|
27
|
-
@collapse="collapsePanel(panel)"
|
|
28
26
|
@close="closePanel(panel)"
|
|
29
|
-
@open-menu="$emit('open-menu', panel.type, panel.payload)"
|
|
30
27
|
>
|
|
31
|
-
<template #before-header>
|
|
32
|
-
<slot name="before-header" :panel="panel" :index="n" :payload="panel.payload"></slot>
|
|
33
|
-
</template>
|
|
34
28
|
<slot
|
|
35
29
|
:name="panel.type"
|
|
36
30
|
:panel="panel"
|
|
@@ -40,9 +34,9 @@
|
|
|
40
34
|
:close="() => closePanel(panel)"
|
|
41
35
|
:expand="() => expandPanel(panel)"
|
|
42
36
|
:fullsize="() => fullsizePanel(panel)">
|
|
43
|
-
<component
|
|
37
|
+
<component :is="panels[panel.type].default || panels[panel.type]" :panel="panel" :payload="panel.payload" />
|
|
44
38
|
</slot>
|
|
45
|
-
<template v-if="$scopedSlots[`${panel.type}.title`] || panel.
|
|
39
|
+
<template v-if="$scopedSlots[`${panel.type}.title`] || panels[panel.type].title" #title>
|
|
46
40
|
<slot
|
|
47
41
|
:name="`${panel.type}.title`"
|
|
48
42
|
:panel="panel"
|
|
@@ -52,10 +46,10 @@
|
|
|
52
46
|
:close="() => closePanel(panel)"
|
|
53
47
|
:expand="() => expandPanel(panel)"
|
|
54
48
|
:fullsize="() => fullsizePanel(panel)">
|
|
55
|
-
<component v-if="panel.
|
|
49
|
+
<component v-if="panels[panel.type].title" :is="panels[panel.type].title" :panel="panel" :payload="panel.payload" />
|
|
56
50
|
</slot>
|
|
57
51
|
</template>
|
|
58
|
-
<template v-if="$scopedSlots[`${panel.type}.buttons`] || panel.
|
|
52
|
+
<template v-if="$scopedSlots[`${panel.type}.buttons`] || panels[panel.type].buttons" #buttons>
|
|
59
53
|
<slot
|
|
60
54
|
:name="`${panel.type}.buttons`"
|
|
61
55
|
:panel="panel"
|
|
@@ -65,10 +59,10 @@
|
|
|
65
59
|
:close="() => closePanel(panel)"
|
|
66
60
|
:expand="() => expandPanel(panel)"
|
|
67
61
|
:fullsize="() => fullsizePanel(panel)">
|
|
68
|
-
<component v-if="panel.
|
|
62
|
+
<component v-if="panels[panel.type].buttons" :is="panels[panel.type].buttons" :panel="panel" :payload="panel.payload" />
|
|
69
63
|
</slot>
|
|
70
64
|
</template>
|
|
71
|
-
<template v-if="$scopedSlots[`${panel.type}.header`] || panel.
|
|
65
|
+
<template v-if="$scopedSlots[`${panel.type}.header`] || panels[panel.type].header" #header>
|
|
72
66
|
<slot
|
|
73
67
|
:name="`${panel.type}.header`"
|
|
74
68
|
:panel="panel"
|
|
@@ -78,7 +72,7 @@
|
|
|
78
72
|
:close="() => closePanel(panel)"
|
|
79
73
|
:expand="() => expandPanel(panel)"
|
|
80
74
|
:fullsize="() => fullsizePanel(panel)">
|
|
81
|
-
<component v-if="panel.
|
|
75
|
+
<component v-if="panels[panel.type].header" :is="panels[panel.type].header" :panel="panel" :payload="panel.payload" />
|
|
82
76
|
</slot>
|
|
83
77
|
</template>
|
|
84
78
|
</panel>
|
|
@@ -154,6 +148,7 @@ $double-an-time: $an-time * 2;
|
|
|
154
148
|
//transition: opacity $an-time linear;
|
|
155
149
|
}
|
|
156
150
|
}
|
|
151
|
+
|
|
157
152
|
//.slide-enter-active > div {
|
|
158
153
|
// opacity: 0;
|
|
159
154
|
//}
|
|
@@ -164,10 +159,8 @@ $double-an-time: $an-time * 2;
|
|
|
164
159
|
</style>
|
|
165
160
|
<script lang="ts">
|
|
166
161
|
import { Vue, Component, Prop } from 'vue-property-decorator';
|
|
167
|
-
import
|
|
168
|
-
import
|
|
169
|
-
import {hashToStack, stackToHash} from "@itfin/components/src/components/panels/helpers";
|
|
170
|
-
import {emitGlobalEvent, setRootPanelList} from "@itfin/components/src/components/panels";
|
|
162
|
+
import Panel from './Panel';
|
|
163
|
+
import {hashToStack, stackToHash} from "./helpers";
|
|
171
164
|
|
|
172
165
|
interface VisualOptions {
|
|
173
166
|
title: string;
|
|
@@ -202,7 +195,6 @@ export interface IPanel {
|
|
|
202
195
|
|
|
203
196
|
@Component({
|
|
204
197
|
components: {
|
|
205
|
-
itfIcon,
|
|
206
198
|
Panel
|
|
207
199
|
},
|
|
208
200
|
directives: {
|
|
@@ -216,15 +208,12 @@ export interface IPanel {
|
|
|
216
208
|
export default class PanelList extends Vue {
|
|
217
209
|
@Prop() firstPanel: IPanel;
|
|
218
210
|
@Prop() panels: Record<string, Component>;
|
|
219
|
-
@Prop({ default: () => {} }) searchPanel: (type: string) => boolean;
|
|
220
|
-
@Prop({ type: String, default: 'path' }) routeType: string;
|
|
221
211
|
|
|
222
212
|
panelsStack:IPanel[] = [];
|
|
223
213
|
|
|
224
214
|
nextId:number = 0;
|
|
225
215
|
|
|
226
216
|
created() {
|
|
227
|
-
setRootPanelList(this);
|
|
228
217
|
if (this.firstPanel) {
|
|
229
218
|
this.internalOpenPanel(this.firstPanel.type, this.firstPanel.payload);
|
|
230
219
|
}
|
|
@@ -278,30 +267,18 @@ export default class PanelList extends Vue {
|
|
|
278
267
|
this.panelsStack = newStack;
|
|
279
268
|
}
|
|
280
269
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
panel = await this.searchPanel(type, this.panels);
|
|
285
|
-
if (!panel) {
|
|
286
|
-
console.error(`Panel type "${type}" not found`);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
panel.type = type;
|
|
270
|
+
internalOpenPanel(type: string, payload: any = {}, openIndex?: number, noEvents = false) {
|
|
271
|
+
if (!this.panels[type]) {
|
|
272
|
+
return;
|
|
290
273
|
}
|
|
291
|
-
if (typeof
|
|
274
|
+
if (typeof this.panels[type].caption !== 'function') {
|
|
292
275
|
throw new Error('Panel component must have a "caption" function');
|
|
293
276
|
}
|
|
294
277
|
const newPanel:any = {
|
|
295
278
|
id: this.nextId++,
|
|
296
|
-
nocard:
|
|
297
|
-
title:
|
|
298
|
-
icon:
|
|
299
|
-
components: {
|
|
300
|
-
default: panel.default ?? undefined,
|
|
301
|
-
buttons: panel.buttons ?? undefined,
|
|
302
|
-
header: panel.header ?? undefined,
|
|
303
|
-
title: panel.title ?? undefined,
|
|
304
|
-
},
|
|
279
|
+
nocard: this.panels[type].nocard,
|
|
280
|
+
title: this.panels[type].caption(this.$t.bind(this), payload),
|
|
281
|
+
icon: this.panels[type].icon ? this.panels[type].icon(this.$t.bind(this), payload) : null,
|
|
305
282
|
type,
|
|
306
283
|
payload,
|
|
307
284
|
isCollapsed: false,
|
|
@@ -312,7 +289,7 @@ export default class PanelList extends Vue {
|
|
|
312
289
|
newPanel.isCloseable = false;
|
|
313
290
|
}
|
|
314
291
|
let newStack = [...this.panelsStack];
|
|
315
|
-
if (
|
|
292
|
+
if (this.panels[type].permanentExpanded && newStack.length) {
|
|
316
293
|
for (const panel of newStack) {
|
|
317
294
|
panel.isCollapsed = true;
|
|
318
295
|
}
|
|
@@ -322,32 +299,25 @@ export default class PanelList extends Vue {
|
|
|
322
299
|
isAnimation = newStack.length === openIndex;
|
|
323
300
|
newStack = newStack.slice(0, openIndex);
|
|
324
301
|
}
|
|
325
|
-
if (newStack.length > 0 && !newStack.find(p => !p.isCollapsed)) {
|
|
326
|
-
// якщо немає відкритих панелей, то перша панель має бути розгорнута
|
|
327
|
-
newStack[0].isCollapsed = false;
|
|
328
|
-
}
|
|
329
302
|
this.panelsStack = newStack;
|
|
330
303
|
return new Promise(res => {
|
|
331
304
|
this.$nextTick(() => { // щоб панелі змінювались при редагуванні
|
|
332
305
|
const n = newStack.length;
|
|
333
306
|
newPanel.isAnimate = isAnimation;
|
|
334
|
-
newPanel.permanentExpanded = !!
|
|
307
|
+
newPanel.permanentExpanded = !!this.panels[type].permanentExpanded;
|
|
335
308
|
newPanel.emit = (event, ...args) => this.emitEvent(event, ...args);
|
|
336
|
-
newPanel.open = (type, payload
|
|
309
|
+
newPanel.open = (type, payload) => this.openPanel(type, payload, n + 1);
|
|
337
310
|
newPanel.close = () => this.closePanel(newPanel);
|
|
338
311
|
newPanel.expand = () => this.expandPanel(newPanel);
|
|
339
312
|
newPanel.getTitle = () => newPanel.title;
|
|
340
313
|
newPanel.getIcon = () => newPanel.icon;
|
|
341
|
-
newPanel.setTitle = (title: string) => { newPanel.title = title;
|
|
314
|
+
newPanel.setTitle = (title: string) => { newPanel.title = title; };
|
|
342
315
|
newPanel.setIcon = (icon: string) => { newPanel.icon = icon; };
|
|
343
|
-
newPanel.on = (eventName
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (!newPanel.__events[evName]) {
|
|
347
|
-
newPanel.__events[evName] = [];
|
|
348
|
-
}
|
|
349
|
-
newPanel.__events[evName].push(func);
|
|
316
|
+
newPanel.on = (eventName, func: (event: string, ...args: any[]) => any) => {
|
|
317
|
+
if (!newPanel.__events[eventName]) {
|
|
318
|
+
newPanel.__events[eventName] = [];
|
|
350
319
|
}
|
|
320
|
+
newPanel.__events[eventName].push(func);
|
|
351
321
|
};
|
|
352
322
|
newPanel.off = (eventName, func: (event: string, ...args: any[]) => any) => {
|
|
353
323
|
if (newPanel.__events[eventName]) {
|
|
@@ -367,7 +337,9 @@ export default class PanelList extends Vue {
|
|
|
367
337
|
newPanel.getPayload = () => newPanel.payload;
|
|
368
338
|
newPanel.setPayload = (value: any) => {
|
|
369
339
|
newPanel.payload = value;
|
|
370
|
-
this.
|
|
340
|
+
newPanel.title = this.panels[type].caption(this.$t.bind(this), value);
|
|
341
|
+
newPanel.icon = this.panels[type].icon ? this.panels[type].icon(this.$t.bind(this), payload) : null,
|
|
342
|
+
this.setPanelHash()
|
|
371
343
|
}
|
|
372
344
|
newStack.push(newPanel);
|
|
373
345
|
this.panelsStack = newStack;
|
|
@@ -381,19 +353,12 @@ export default class PanelList extends Vue {
|
|
|
381
353
|
});
|
|
382
354
|
}
|
|
383
355
|
|
|
384
|
-
updateTitle() {
|
|
385
|
-
const titles = this.panelsStack.map(p => p.getTitle()).filter(Boolean).reverse();
|
|
386
|
-
this.$root.$options.head.titleChunk = titles.join(' / ');
|
|
387
|
-
this.$meta().refresh();
|
|
388
|
-
}
|
|
389
|
-
|
|
390
356
|
async openPanel(type: string, payload: any, openIndex?: number) {
|
|
391
357
|
await this.internalOpenPanel(type, payload, openIndex);
|
|
392
|
-
this.setPanelHash()
|
|
358
|
+
this.setPanelHash()
|
|
393
359
|
}
|
|
394
360
|
|
|
395
361
|
emitEvent(event: string, ...args: any[]) {
|
|
396
|
-
emitGlobalEvent(event, ...args);
|
|
397
362
|
for (const panel of this.panelsStack) {
|
|
398
363
|
if (panel.__events[event]) {
|
|
399
364
|
for (const func of panel.__events[event]) {
|
|
@@ -423,40 +388,12 @@ export default class PanelList extends Vue {
|
|
|
423
388
|
fullsizePanel(panel: IPanel) {
|
|
424
389
|
const newStack = [...this.panelsStack];
|
|
425
390
|
for (const p of newStack) {
|
|
426
|
-
p.isLastOpened = !p.isCollapsed && p !== panel;
|
|
427
391
|
p.isCollapsed = p !== panel;
|
|
428
392
|
}
|
|
429
393
|
this.panelsStack = newStack;
|
|
430
394
|
this.setPanelHash();
|
|
431
395
|
}
|
|
432
396
|
|
|
433
|
-
get isFullSize() {
|
|
434
|
-
return this.panelsStack.filter(p => !p.isCollapsed).length === 1;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
expandPanel(panel: IPanel) {
|
|
438
|
-
const newStack = [...this.panelsStack];
|
|
439
|
-
const index = newStack.findIndex(p => p.id === panel.id);
|
|
440
|
-
newStack[index].isCollapsed = false;
|
|
441
|
-
this.panelsStack = newStack;
|
|
442
|
-
this.ensureOnlyTwoOpenPanels(panel.id);
|
|
443
|
-
this.setPanelHash();
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
collapsePanel(panel: IPanel) {
|
|
447
|
-
const newStack = [...this.panelsStack];
|
|
448
|
-
const currenctIndex = newStack.findIndex(p => p.id === panel.id);
|
|
449
|
-
const lastOpenedIndex = newStack.findIndex(p => p.isLastOpened);
|
|
450
|
-
if (lastOpenedIndex !== -1) { // якщо зебрежена остання відкрита панель
|
|
451
|
-
newStack[lastOpenedIndex].isCollapsed = false
|
|
452
|
-
} else if (newStack[currenctIndex-1]) { // якщо після оновлення сторінки відсутнє значення "остання відкрита", то відкриваємо ту, що зліва
|
|
453
|
-
newStack[currenctIndex-1].isCollapsed = false;
|
|
454
|
-
}
|
|
455
|
-
this.panelsStack = newStack;
|
|
456
|
-
this.ensureOnlyTwoOpenPanels(panel.id);
|
|
457
|
-
this.setPanelHash();
|
|
458
|
-
}
|
|
459
|
-
|
|
460
397
|
getPanels(type) {
|
|
461
398
|
return this.panelsStack.filter(panel => panel.type === type);
|
|
462
399
|
}
|
|
@@ -466,19 +403,14 @@ export default class PanelList extends Vue {
|
|
|
466
403
|
}
|
|
467
404
|
|
|
468
405
|
setPanelHash() {
|
|
469
|
-
const hash = stackToHash(this.panelsStack
|
|
470
|
-
|
|
471
|
-
this.$router.push({ path: `/${hash}` });
|
|
472
|
-
} else {
|
|
473
|
-
this.$router.push({ hash });
|
|
474
|
-
}
|
|
475
|
-
this.updateTitle();
|
|
406
|
+
const hash = stackToHash(this.panelsStack).replace(/^#/, '');
|
|
407
|
+
this.$router.push({ hash });
|
|
476
408
|
}
|
|
477
409
|
|
|
478
410
|
async parsePanelHash() {
|
|
479
|
-
const hash =
|
|
411
|
+
const {hash} = location;
|
|
480
412
|
if (hash) {
|
|
481
|
-
const panels = hashToStack(hash
|
|
413
|
+
const panels = hashToStack(hash);
|
|
482
414
|
const newStack = [];
|
|
483
415
|
this.panelsStack = [];
|
|
484
416
|
for (const panelIndex in panels) {
|
|
@@ -499,7 +431,6 @@ export default class PanelList extends Vue {
|
|
|
499
431
|
}
|
|
500
432
|
this.panelsStack = newStack;
|
|
501
433
|
this.emitEvent('panels.changed', this.panelsStack);
|
|
502
|
-
this.updateTitle();
|
|
503
434
|
}
|
|
504
435
|
}
|
|
505
436
|
|