odant-work 1.0.1 → 1.0.2
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/$server/$folder/$class/$structure/$server/handlers/pages/form/license/$handler/license.js +9 -6
- package/$server/$folder/$class/handlers/pages/form/object/$handler/class.js +27 -0
- package/$server/$folder/$class/handlers/pages/form/object/$handler/object.js +352 -0
- package/$server/$folder/$class/handlers/pages/form/objects/$handler/objects.js +39 -2
- package/$server/$folder/handlers/pages/form/$handler/form.js +427 -68
- package/$server/$folder/lib/tools/tools.js +9 -2
- package/MODULES/Register/REGISTER/$register/$folder/$class/$account/handlers/pages/form/postings/$handler/postings.js +388 -103
- package/MODULES/Register/REGISTER/$register/$folder/$class/$register/handlers/pages/form/journal/$handler/journal.js +84 -5
- package/MODULES/Register/REGISTER/$register/$folder/$class/$register/handlers/pages/form/sheet/$handler/sheet.js +84 -5
- package/data/store.json +3 -3
- package/oda/components/packets/packets.js +6 -0
- package/package.json +1 -1
- package/sources/data/api.js +180 -1
- package/sources/data/progress.md +37 -0
- package/sources/modules/license/index.js +7 -5
|
@@ -69,6 +69,104 @@ ODA({is: 'work-form',
|
|
|
69
69
|
from { opacity: 0; }
|
|
70
70
|
to { opacity: 1; }
|
|
71
71
|
}
|
|
72
|
+
.form-tabs-bar {
|
|
73
|
+
@apply --horizontal;
|
|
74
|
+
@apply --no-flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 4px;
|
|
77
|
+
max-width: 62vw;
|
|
78
|
+
overflow-x: auto;
|
|
79
|
+
scrollbar-width: thin;
|
|
80
|
+
}
|
|
81
|
+
.form-tab {
|
|
82
|
+
@apply --horizontal;
|
|
83
|
+
@apply --no-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 5px;
|
|
86
|
+
max-width: 220px;
|
|
87
|
+
height: 24px;
|
|
88
|
+
padding: 2px 8px;
|
|
89
|
+
font-size: 12px;
|
|
90
|
+
font-weight: 500;
|
|
91
|
+
border: 1px solid var(--border-color, #b8b7ae);
|
|
92
|
+
border-radius: 4px;
|
|
93
|
+
background: light-dark(#e9e8df, #262830);
|
|
94
|
+
color: light-dark(#333, #cbd5e1);
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
user-select: none;
|
|
97
|
+
box-sizing: border-box;
|
|
98
|
+
transition: background 0.12s ease, border-color 0.12s ease;
|
|
99
|
+
}
|
|
100
|
+
.form-tab:hover:not(.active) {
|
|
101
|
+
background: light-dark(#f3f5fa, #30333d);
|
|
102
|
+
}
|
|
103
|
+
.form-tab.active {
|
|
104
|
+
background: light-dark(#ffffff, #1b1c20);
|
|
105
|
+
color: light-dark(#111, #f8fafc);
|
|
106
|
+
border-color: light-dark(#4f6bed, #60a5fa);
|
|
107
|
+
font-weight: 700;
|
|
108
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
|
109
|
+
}
|
|
110
|
+
.form-tab .tab-label {
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
text-overflow: ellipsis;
|
|
113
|
+
white-space: nowrap;
|
|
114
|
+
}
|
|
115
|
+
.form-tab .tab-close {
|
|
116
|
+
display: inline-flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
width: 15px;
|
|
120
|
+
height: 15px;
|
|
121
|
+
margin-left: 2px;
|
|
122
|
+
border-radius: 3px;
|
|
123
|
+
font-size: 13px;
|
|
124
|
+
line-height: 1;
|
|
125
|
+
opacity: 0.7;
|
|
126
|
+
}
|
|
127
|
+
.form-tab .tab-close:hover {
|
|
128
|
+
opacity: 1;
|
|
129
|
+
background: rgba(220, 38, 38, 0.16);
|
|
130
|
+
color: #dc2626;
|
|
131
|
+
}
|
|
132
|
+
.obj-footer-actions {
|
|
133
|
+
@apply --horizontal;
|
|
134
|
+
@apply --no-flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
gap: 6px;
|
|
137
|
+
}
|
|
138
|
+
button.obj-footer-btn {
|
|
139
|
+
@apply --horizontal;
|
|
140
|
+
@apply --no-flex;
|
|
141
|
+
@apply --content;
|
|
142
|
+
align-items: center;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
gap: 5px;
|
|
145
|
+
min-height: 24px;
|
|
146
|
+
padding: 3px 12px;
|
|
147
|
+
font-size: 12.5px;
|
|
148
|
+
font-weight: 600;
|
|
149
|
+
border: 1px solid var(--border-color);
|
|
150
|
+
border-radius: 3px;
|
|
151
|
+
cursor: pointer;
|
|
152
|
+
user-select: none;
|
|
153
|
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.06);
|
|
154
|
+
}
|
|
155
|
+
button.obj-footer-btn.primary {
|
|
156
|
+
font-weight: 700;
|
|
157
|
+
min-width: 58px;
|
|
158
|
+
}
|
|
159
|
+
button.obj-footer-btn.icon-only {
|
|
160
|
+
padding: 3px 7px;
|
|
161
|
+
}
|
|
162
|
+
button.obj-footer-btn:hover:not(:disabled) {
|
|
163
|
+
border-color: var(--accent-color);
|
|
164
|
+
background: var(--accent-soft);
|
|
165
|
+
}
|
|
166
|
+
button.obj-footer-btn:disabled {
|
|
167
|
+
opacity: 0.45;
|
|
168
|
+
cursor: default;
|
|
169
|
+
}
|
|
72
170
|
</style>
|
|
73
171
|
<div ~show="!fullScreen" accent-invert slot="header" shadow horizontal flex style="padding: 2px; gap: 4px; align-items: center;">
|
|
74
172
|
<div center flex horizontal style="overflow: hidden; flex-wrap: balance; gap: 4px; margin: 4px;">
|
|
@@ -110,7 +208,7 @@ ODA({is: 'work-form',
|
|
|
110
208
|
</div>
|
|
111
209
|
<div id="tools" raised no-flex horizontal style="min-height: 32px; align-items: center; gap: 8px; border-bottom: 1px solid;">
|
|
112
210
|
<item-node
|
|
113
|
-
~for="
|
|
211
|
+
~for="visibleFormViews"
|
|
114
212
|
:$item="$for.item"
|
|
115
213
|
:hide-label="view?.id !== $for.item.id"
|
|
116
214
|
:icon-size
|
|
@@ -143,10 +241,23 @@ ODA({is: 'work-form',
|
|
|
143
241
|
</div>
|
|
144
242
|
</div>
|
|
145
243
|
<div id="formFooter" slot="footer" ~show="hasFooter" header horizontal no-flex style="justify-items: space-between; align-items: center; padding: 4px 8px; min-height: 34px; gap: 6px; box-sizing: border-box; border-top: 1px solid var(--border-color);">
|
|
146
|
-
<item-tools id="mainTools" :$item :state-version="focusedStateVersion" filter="service" ::tool-items="mainToolItems" @tools-changed="onToolsListChanged('mainToolItems', $event)" show-labels></item-tools>
|
|
147
244
|
<item-tools id="focusedTools" focused-only :target-item="focusedItem" :fallback-item="fallbackFocusedItem" :$item="focusedItem" :state-version="focusedStateVersion" filter="service" ::tool-items="focusedToolItems" @tools-changed="onToolsListChanged('focusedToolItems', $event)" show-labels></item-tools>
|
|
148
245
|
<slot name="footer-left"></slot>
|
|
149
246
|
<div flex></div>
|
|
247
|
+
<div class="obj-footer-actions" ~if="isObjectViewActive">
|
|
248
|
+
<button type="button" class="obj-footer-btn primary" ~if="view_control?.canSave" :disabled="view_control?.busy" @tap="onObjectOk()">OK</button>
|
|
249
|
+
<button type="button" class="obj-footer-btn icon-only" ~if="view_control?.canSave" :disabled="view_control?.busy" title="Сохранить" @tap="onObjectSave()">
|
|
250
|
+
<oda-icon icon="icons:save" icon-size="16"></oda-icon>
|
|
251
|
+
</button>
|
|
252
|
+
<button type="button" class="obj-footer-btn" :disabled="view_control?.busy" @tap="onObjectClose()">Закрыть</button>
|
|
253
|
+
</div>
|
|
254
|
+
<div class="form-tabs-bar" ~if="formTabs?.length > 1">
|
|
255
|
+
<div class="form-tab" ~for="formTabs" ~class="{active: activeTabKey === $for.item.key}" :title="$for.item.label" @tap="activateTab($for.item)">
|
|
256
|
+
<oda-icon ~if="$for.item.icon" :icon="$for.item.icon" icon-size="15"></oda-icon>
|
|
257
|
+
<span class="tab-label">{{$for.item.label}}</span>
|
|
258
|
+
<span class="tab-close" ~if="$for.item.closable" title="Закрыть вкладку" @tap.stop="closeTab($for.item, $event)">×</span>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
150
261
|
<div no-flex horizontal ~show="dialog" style="gap: 6px;">
|
|
151
262
|
<oda-button info-invert icon="icons:check" :icon-size @tap="ok" label="OK"></oda-button>
|
|
152
263
|
<oda-button icon="icons:close" :icon-size @tap="cancel" label="Отмена"></oda-button>
|
|
@@ -293,7 +404,10 @@ ODA({is: 'work-form',
|
|
|
293
404
|
}
|
|
294
405
|
},
|
|
295
406
|
async ready() {
|
|
296
|
-
window.execute
|
|
407
|
+
window.execute = ($item, nav) => {
|
|
408
|
+
if ($item && $item.type !== '$handler') {
|
|
409
|
+
return this.openInForm($item, nav);
|
|
410
|
+
}
|
|
297
411
|
const short = $item?.short || $item?.path || '';
|
|
298
412
|
if (nav && short)
|
|
299
413
|
this._saveNav(short, nav);
|
|
@@ -302,11 +416,11 @@ ODA({is: 'work-form',
|
|
|
302
416
|
return;
|
|
303
417
|
}
|
|
304
418
|
let url = short + '/';
|
|
305
|
-
if ($item
|
|
306
|
-
url += '~/handlers/pages/' + ($item
|
|
419
|
+
if ($item?.type !== '$handler')
|
|
420
|
+
url += '~/handlers/pages/' + ($item?.page || 'form') + '/'
|
|
307
421
|
url = encodeURI(url);
|
|
308
422
|
window.open(url, url)
|
|
309
|
-
}
|
|
423
|
+
};
|
|
310
424
|
// для закрытия вкладки браузера (form без explorer)
|
|
311
425
|
if (window.parent !== window)
|
|
312
426
|
window.close = () => {
|
|
@@ -379,6 +493,12 @@ ODA({is: 'work-form',
|
|
|
379
493
|
return [];
|
|
380
494
|
return this.loadFormViews($item, role);
|
|
381
495
|
},
|
|
496
|
+
get visibleFormViews() {
|
|
497
|
+
const views = this.formViews;
|
|
498
|
+
if (!Array.isArray(views))
|
|
499
|
+
return [];
|
|
500
|
+
return views.filter(v => !(v?.DATA?.hiddenInViewList || v?.hiddenInViewList) || v?.id === this.view?.id);
|
|
501
|
+
},
|
|
382
502
|
async loadFormViews($item, role) {
|
|
383
503
|
try {
|
|
384
504
|
const root = await $item.fetch('handlers', { path: '//form', role });
|
|
@@ -436,14 +556,23 @@ ODA({is: 'work-form',
|
|
|
436
556
|
this._savePath = ($class?.short || this.$item?.short) + '/' + this.localName + (this.$saveKey ? '[' + this.$saveKey + ']' : '');
|
|
437
557
|
|
|
438
558
|
await n.ensureRole?.();
|
|
559
|
+
if (this._switchingTab)
|
|
560
|
+
return;
|
|
439
561
|
const nav = this._takeNav(n.short || n.path);
|
|
440
|
-
if (nav)
|
|
562
|
+
if (nav) {
|
|
563
|
+
if (nav.view === 'objects' && nav.id)
|
|
564
|
+
nav.view = 'object';
|
|
441
565
|
this._pendingNav = nav;
|
|
566
|
+
}
|
|
442
567
|
const view_name = nav?.view || this.host.default_view || n?.form || this.host.view_name;
|
|
443
568
|
this.view ||= await this.resolveView(view_name);
|
|
444
569
|
}
|
|
445
570
|
}
|
|
446
571
|
},
|
|
572
|
+
formTabs: [],
|
|
573
|
+
activeTabKey: '',
|
|
574
|
+
_newObjSeq: 0,
|
|
575
|
+
_switchingTab: false,
|
|
447
576
|
topMainToolItems: [],
|
|
448
577
|
topFocusedToolItems: [],
|
|
449
578
|
mainToolItems: [],
|
|
@@ -451,11 +580,16 @@ ODA({is: 'work-form',
|
|
|
451
580
|
hasSlottedToolbar: false,
|
|
452
581
|
focusedStateVersion: 0,
|
|
453
582
|
fallbackFocusedItem: null,
|
|
583
|
+
get isObjectViewActive() {
|
|
584
|
+
return Boolean(this.view?.id === 'object' || this.view_control?.isObjectView);
|
|
585
|
+
},
|
|
454
586
|
get hasToolbar() {
|
|
587
|
+
if (this.view?.allowTools === false || this.view?.DATA?.allowTools === false)
|
|
588
|
+
return false;
|
|
455
589
|
return Boolean(this.topMainToolItems?.length || this.topFocusedToolItems?.length || this.hasSlottedToolbar);
|
|
456
590
|
},
|
|
457
591
|
get hasFooter() {
|
|
458
|
-
return Boolean(this.dialog || this.
|
|
592
|
+
return Boolean(this.dialog || this.isObjectViewActive || this.focusedToolItems?.length || (this.formTabs?.length > 1));
|
|
459
593
|
},
|
|
460
594
|
_checkToolbarSlots() {
|
|
461
595
|
const names = ['tool-left', 'tool-center', 'tool-right'];
|
|
@@ -471,7 +605,7 @@ ODA({is: 'work-form',
|
|
|
471
605
|
this.async(() => this._wakeBars(), 5);
|
|
472
606
|
},
|
|
473
607
|
_wakeBars() {
|
|
474
|
-
for (const sel of ['#formToolbar', '#topMainTools', '#topFocusedTools', '#formFooter', '#
|
|
608
|
+
for (const sel of ['#formToolbar', '#topMainTools', '#topFocusedTools', '#formFooter', '#focusedTools']) {
|
|
475
609
|
const node = this.$?.(sel);
|
|
476
610
|
if (node) {
|
|
477
611
|
if (node[R]?.states)
|
|
@@ -486,7 +620,7 @@ ODA({is: 'work-form',
|
|
|
486
620
|
this.fallbackFocusedItem = vc.fallbackFocusedItem || null;
|
|
487
621
|
this.focusedItem = vc.$focusedItem !== undefined ? vc.$focusedItem : (vc.focusedItem || null);
|
|
488
622
|
this.focusedStateVersion++;
|
|
489
|
-
for (const [sel, isFocused] of [['#topMainTools', false], ['#topFocusedTools', true], ['#
|
|
623
|
+
for (const [sel, isFocused] of [['#topMainTools', false], ['#topFocusedTools', true], ['#focusedTools', true]]) {
|
|
490
624
|
const t = this.$?.(sel);
|
|
491
625
|
if (t) {
|
|
492
626
|
t.$item = this.$item;
|
|
@@ -622,6 +756,224 @@ ODA({is: 'work-form',
|
|
|
622
756
|
this.host.view_name = handler.id;
|
|
623
757
|
this.view = handler;
|
|
624
758
|
},
|
|
759
|
+
_computeTabKey($item, viewId, nav = null) {
|
|
760
|
+
const short = $item?.short || $item?.path || '';
|
|
761
|
+
if (viewId === 'object') {
|
|
762
|
+
const objKey = nav?.id ? `id:${nav.id}` : nav?.copyFrom ? `copy:${nav.copyFrom}` : `new:${nav?._seq || (++this._newObjSeq)}`;
|
|
763
|
+
if (nav && !nav.id && !nav.copyFrom && !nav._seq)
|
|
764
|
+
nav._seq = Number(objKey.slice(4));
|
|
765
|
+
return `${short}::object::${objKey}`;
|
|
766
|
+
}
|
|
767
|
+
return `${short}::${viewId || ''}`;
|
|
768
|
+
},
|
|
769
|
+
_computeTabLabel($item, viewHandler, nav = null) {
|
|
770
|
+
if (nav?.label)
|
|
771
|
+
return String(nav.label);
|
|
772
|
+
const rawLabel = $item?.DATA?.label || $item?.label || $item?.id || ($item?.short || '').split('/').pop() || 'Форма';
|
|
773
|
+
const code = ($item?.short || $item?.path || '').split('/').pop() || '';
|
|
774
|
+
const isAcc = $item?.type === '$account' || /^\d+(?:\.\d+)?$/.test(code);
|
|
775
|
+
const itemTitle = (isAcc && code && !String(rawLabel).startsWith(code + ' ') && String(rawLabel) !== code)
|
|
776
|
+
? `${code} ${rawLabel}`
|
|
777
|
+
: String(rawLabel);
|
|
778
|
+
return itemTitle;
|
|
779
|
+
},
|
|
780
|
+
_ensureCurrentTab(viewHandler = this.view, nav = null) {
|
|
781
|
+
if (!this.$item || !viewHandler)
|
|
782
|
+
return null;
|
|
783
|
+
const viewId = viewHandler.id || '';
|
|
784
|
+
const list = Array.isArray(this.formTabs) ? [...this.formTabs] : [];
|
|
785
|
+
if (list.length === 0) {
|
|
786
|
+
const key = this._computeTabKey(this.$item, viewId, nav);
|
|
787
|
+
const rootTab = {
|
|
788
|
+
key,
|
|
789
|
+
$item: this.$item,
|
|
790
|
+
viewId,
|
|
791
|
+
view: viewHandler,
|
|
792
|
+
nav: nav || null,
|
|
793
|
+
label: this._computeTabLabel(this.$item, viewHandler, nav),
|
|
794
|
+
icon: viewHandler.DATA?.icon || viewHandler.icon || this.$item.icon || 'icons:dashboard',
|
|
795
|
+
closable: false,
|
|
796
|
+
};
|
|
797
|
+
this.formTabs = [rootTab];
|
|
798
|
+
this.activeTabKey = key;
|
|
799
|
+
return rootTab;
|
|
800
|
+
}
|
|
801
|
+
let cur = list.find(t => t.key === this.activeTabKey);
|
|
802
|
+
if (cur && cur.$item === this.$item && cur.viewId !== 'object' && viewId !== 'object') {
|
|
803
|
+
const newKey = this._computeTabKey(this.$item, viewId, nav);
|
|
804
|
+
if (cur.key !== newKey && !list.some(t => t.key === newKey)) {
|
|
805
|
+
cur.key = newKey;
|
|
806
|
+
this.activeTabKey = newKey;
|
|
807
|
+
}
|
|
808
|
+
cur.viewId = viewId;
|
|
809
|
+
cur.view = viewHandler;
|
|
810
|
+
cur.icon = viewHandler.DATA?.icon || viewHandler.icon || this.$item.icon || cur.icon;
|
|
811
|
+
this.formTabs = [...list];
|
|
812
|
+
}
|
|
813
|
+
return cur || list[0];
|
|
814
|
+
},
|
|
815
|
+
updateActiveTabLabel(newLabel) {
|
|
816
|
+
if (!newLabel || !Array.isArray(this.formTabs))
|
|
817
|
+
return;
|
|
818
|
+
const list = [...this.formTabs];
|
|
819
|
+
const cur = list.find(t => t.key === this.activeTabKey);
|
|
820
|
+
if (cur && cur.label !== newLabel) {
|
|
821
|
+
cur.label = String(newLabel);
|
|
822
|
+
this.formTabs = list;
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
async openInForm(targetItem, rawNav = null) {
|
|
826
|
+
if (!targetItem)
|
|
827
|
+
return;
|
|
828
|
+
await targetItem.ensureRole?.();
|
|
829
|
+
const nav = rawNav && typeof rawNav === 'object' ? { ...rawNav } : {};
|
|
830
|
+
if (nav.view === 'objects' && nav.id) {
|
|
831
|
+
nav.view = 'object';
|
|
832
|
+
}
|
|
833
|
+
// Зафиксируем текущую (начальную) вкладку перед открытием дочерней
|
|
834
|
+
this._ensureCurrentTab(this.view);
|
|
835
|
+
|
|
836
|
+
const prevItem = this.$item;
|
|
837
|
+
this._switchingTab = true;
|
|
838
|
+
this.$item = targetItem;
|
|
839
|
+
this._switchingTab = false;
|
|
840
|
+
|
|
841
|
+
const targetViewName = nav.view || targetItem.form || '';
|
|
842
|
+
const viewHandler = await this.resolveView(targetViewName);
|
|
843
|
+
if (!viewHandler) {
|
|
844
|
+
this._switchingTab = true;
|
|
845
|
+
this.$item = prevItem;
|
|
846
|
+
this._switchingTab = false;
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const viewId = viewHandler.id;
|
|
850
|
+
const key = this._computeTabKey(targetItem, viewId, nav);
|
|
851
|
+
const list = Array.isArray(this.formTabs) ? [...this.formTabs] : [];
|
|
852
|
+
let tab = list.find(t => t.key === key);
|
|
853
|
+
if (!tab) {
|
|
854
|
+
tab = {
|
|
855
|
+
key,
|
|
856
|
+
$item: targetItem,
|
|
857
|
+
viewId,
|
|
858
|
+
view: viewHandler,
|
|
859
|
+
nav,
|
|
860
|
+
label: this._computeTabLabel(targetItem, viewHandler, nav),
|
|
861
|
+
icon: viewHandler.DATA?.icon || viewHandler.icon || targetItem.icon || 'icons:dashboard',
|
|
862
|
+
closable: list.length > 0,
|
|
863
|
+
};
|
|
864
|
+
list.push(tab);
|
|
865
|
+
this.formTabs = list;
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
tab.nav = nav;
|
|
869
|
+
tab.view = viewHandler;
|
|
870
|
+
if (nav.label)
|
|
871
|
+
tab.label = String(nav.label);
|
|
872
|
+
this.formTabs = list;
|
|
873
|
+
}
|
|
874
|
+
await this.activateTab(tab, nav);
|
|
875
|
+
},
|
|
876
|
+
async activateTab(tab, explicitNav = null) {
|
|
877
|
+
if (!tab || !tab.$item)
|
|
878
|
+
return;
|
|
879
|
+
this.activeTabKey = tab.key;
|
|
880
|
+
if (this.$item !== tab.$item) {
|
|
881
|
+
this._switchingTab = true;
|
|
882
|
+
this.$item = tab.$item;
|
|
883
|
+
this._switchingTab = false;
|
|
884
|
+
}
|
|
885
|
+
const viewHandler = tab.view || await this.resolveView(tab.viewId);
|
|
886
|
+
if (!viewHandler)
|
|
887
|
+
return;
|
|
888
|
+
tab.view = viewHandler;
|
|
889
|
+
if (explicitNav)
|
|
890
|
+
this._pendingNav = explicitNav;
|
|
891
|
+
if (this.view === viewHandler) {
|
|
892
|
+
await this._mountActiveView(viewHandler);
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
this.view = viewHandler;
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
async closeTab(tab, e) {
|
|
899
|
+
e?.stopPropagation?.();
|
|
900
|
+
const list = Array.isArray(this.formTabs) ? [...this.formTabs] : [];
|
|
901
|
+
const idx = list.findIndex(t => t.key === tab?.key);
|
|
902
|
+
if (idx <= 0)
|
|
903
|
+
return;
|
|
904
|
+
const targetTab = list[idx];
|
|
905
|
+
const ctrl = targetTab.control || this.controls[targetTab.key];
|
|
906
|
+
if (ctrl && typeof ctrl.isDirty === 'function' && ctrl.isDirty()) {
|
|
907
|
+
if (typeof ctrl.confirmClose === 'function') {
|
|
908
|
+
const canClose = await ctrl.confirmClose();
|
|
909
|
+
if (!canClose)
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
if (ctrl && this.contains(ctrl))
|
|
914
|
+
this.removeChild(ctrl);
|
|
915
|
+
delete this.controls[targetTab.key];
|
|
916
|
+
list.splice(idx, 1);
|
|
917
|
+
this.formTabs = list;
|
|
918
|
+
if (this.activeTabKey === targetTab.key && list.length) {
|
|
919
|
+
const nextTab = list[Math.max(0, idx - 1)];
|
|
920
|
+
await this.activateTab(nextTab);
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
async closeActiveTab(reloadPrevious = false) {
|
|
924
|
+
const list = Array.isArray(this.formTabs) ? [...this.formTabs] : [];
|
|
925
|
+
const idx = list.findIndex(t => t.key === this.activeTabKey);
|
|
926
|
+
if (idx <= 0)
|
|
927
|
+
return;
|
|
928
|
+
const targetTab = list[idx];
|
|
929
|
+
const ctrl = targetTab.control || this.controls[targetTab.key];
|
|
930
|
+
if (ctrl && this.contains(ctrl))
|
|
931
|
+
this.removeChild(ctrl);
|
|
932
|
+
delete this.controls[targetTab.key];
|
|
933
|
+
list.splice(idx, 1);
|
|
934
|
+
this.formTabs = list;
|
|
935
|
+
const nextTab = list[Math.max(0, idx - 1)];
|
|
936
|
+
if (nextTab) {
|
|
937
|
+
await this.activateTab(nextTab);
|
|
938
|
+
if (reloadPrevious && nextTab.control && typeof nextTab.control.reload === 'function') {
|
|
939
|
+
await nextTab.control.reload();
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
},
|
|
943
|
+
async onObjectOk() {
|
|
944
|
+
const vc = this.view_control;
|
|
945
|
+
if (!vc)
|
|
946
|
+
return;
|
|
947
|
+
if (typeof vc.saveAndClose === 'function') {
|
|
948
|
+
await vc.saveAndClose();
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
if (typeof vc.save === 'function') {
|
|
952
|
+
const ok = await vc.save();
|
|
953
|
+
if (!ok)
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
await this.closeActiveTab(true);
|
|
957
|
+
},
|
|
958
|
+
async onObjectSave() {
|
|
959
|
+
const vc = this.view_control;
|
|
960
|
+
if (!vc)
|
|
961
|
+
return;
|
|
962
|
+
if (typeof vc.saveAndStay === 'function') {
|
|
963
|
+
await vc.saveAndStay();
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
await vc.save?.();
|
|
967
|
+
},
|
|
968
|
+
async onObjectClose() {
|
|
969
|
+
const list = Array.isArray(this.formTabs) ? this.formTabs : [];
|
|
970
|
+
const cur = list.find(t => t.key === this.activeTabKey);
|
|
971
|
+
if (cur && cur.closable) {
|
|
972
|
+
await this.closeTab(cur);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
await this.close();
|
|
976
|
+
},
|
|
625
977
|
ok(e) {
|
|
626
978
|
alert('ok')
|
|
627
979
|
},
|
|
@@ -629,72 +981,79 @@ ODA({is: 'work-form',
|
|
|
629
981
|
alert('cancel')
|
|
630
982
|
},
|
|
631
983
|
controls: {},
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
984
|
+
async _mountActiveView(n) {
|
|
985
|
+
if (!n) return;
|
|
986
|
+
const nav = this._pendingNav || this._takeNav(this.$item?.short || this.$item?.path);
|
|
987
|
+
const curTab = this._ensureCurrentTab(n, nav);
|
|
988
|
+
const cacheKey = curTab?.key || `${this.$item?.short || ''}::${n.id}`;
|
|
989
|
+
let el = curTab?.control || this.controls[cacheKey];
|
|
990
|
+
for (let id in this.controls) {
|
|
991
|
+
let old = this.controls[id];
|
|
992
|
+
if (old !== el && this.contains(old))
|
|
993
|
+
this.removeChild(old);
|
|
994
|
+
}
|
|
995
|
+
if (!el) {
|
|
996
|
+
const ext = String(this.$item?.ext || '').toLowerCase();
|
|
997
|
+
const is = (n.id === 'file' && ext) ? `item-file-${ext}` : `item-${n.id}`;
|
|
998
|
+
await n.importView(is);
|
|
999
|
+
if (this.view !== n)
|
|
1000
|
+
return;
|
|
1001
|
+
if (!customElements.get(is)) {
|
|
1002
|
+
console.error(`[work-form] вид '${n.id}' не зарегистрирован как <${is}>`);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
el = curTab?.control || this.controls[cacheKey];
|
|
1006
|
+
if (!el) {
|
|
1007
|
+
el = ODA.createComponent(is, { $item: this.$item, $context: this.$item, slot: 'main', $handler: n });
|
|
1008
|
+
el.$form = this;
|
|
1009
|
+
this.controls[cacheKey] = el;
|
|
1010
|
+
this.controls[n.id] = el;
|
|
1011
|
+
if (curTab)
|
|
1012
|
+
curTab.control = el;
|
|
1013
|
+
}
|
|
636
1014
|
for (let id in this.controls) {
|
|
637
1015
|
let old = this.controls[id];
|
|
638
1016
|
if (old !== el && this.contains(old))
|
|
639
1017
|
this.removeChild(old);
|
|
640
1018
|
}
|
|
641
|
-
if (!el)
|
|
642
|
-
const ext = String(this.$item?.ext || '').toLowerCase();
|
|
643
|
-
const is = (n.id === 'file' && ext) ? `item-file-${ext}` : `item-${n.id}`;
|
|
644
|
-
await n.importView(is);
|
|
645
|
-
if (this.view !== n)
|
|
646
|
-
return;
|
|
647
|
-
if (!customElements.get(is)) {
|
|
648
|
-
// Вид не зарегистрирован под нужным тегом — создавать элемент
|
|
649
|
-
// бессмысленно: он останется пустым (как на скриншоте).
|
|
650
|
-
console.error(`[work-form] вид '${n.id}' не зарегистрирован как <${is}>`);
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
// Сеттер асинхронный: пока грузился модуль, параллельный вызов (роль, повторный $item)
|
|
654
|
-
// мог уже создать это представление — второй экземпляр не нужен.
|
|
655
|
-
el = this.controls[n.id];
|
|
656
|
-
if (!el) {
|
|
657
|
-
el = ODA.createComponent(is, { $item: this.$item, $context: this.$item, slot: 'main', $handler: n });
|
|
658
|
-
el.$form = this;
|
|
659
|
-
this.controls[n.id] = el;
|
|
660
|
-
}
|
|
661
|
-
for (let id in this.controls) {
|
|
662
|
-
let old = this.controls[id];
|
|
663
|
-
if (old !== el && this.contains(old))
|
|
664
|
-
this.removeChild(old);
|
|
665
|
-
}
|
|
666
|
-
if (!el.isConnected)
|
|
667
|
-
this.appendChild(el);
|
|
668
|
-
}
|
|
669
|
-
else if (!el.isConnected)
|
|
1019
|
+
if (!el.isConnected)
|
|
670
1020
|
this.appendChild(el);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
this
|
|
1021
|
+
}
|
|
1022
|
+
else if (!el.isConnected)
|
|
1023
|
+
this.appendChild(el);
|
|
1024
|
+
el.$form = this;
|
|
1025
|
+
if (curTab)
|
|
1026
|
+
curTab.control = el;
|
|
1027
|
+
for (const oldSlotEl of [
|
|
1028
|
+
...this.querySelectorAll('[slot="tool-left"], [slot="tool-center"], [slot="tool-right"], [slot="footer-left"]'),
|
|
1029
|
+
...(this.$?.('#formToolbar')?.querySelectorAll?.('[data-toolbar-widget]') || []),
|
|
1030
|
+
]) {
|
|
1031
|
+
if (oldSlotEl._ownerView && oldSlotEl._ownerView !== el)
|
|
1032
|
+
oldSlotEl.remove();
|
|
1033
|
+
}
|
|
1034
|
+
this.view_control = el;
|
|
1035
|
+
if (n.allowTools !== false && n.DATA?.allowTools !== false)
|
|
680
1036
|
el.mountToolbarSlots?.(this);
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1037
|
+
this._checkToolbarSlots();
|
|
1038
|
+
this.syncFromView(el);
|
|
1039
|
+
this.host.view_name = n.id;
|
|
1040
|
+
if (nav && typeof el?.applyNav === 'function') {
|
|
1041
|
+
this._pendingNav = null;
|
|
1042
|
+
Promise.resolve(el.applyNav(nav)).catch(err => console.error(err));
|
|
1043
|
+
}
|
|
1044
|
+
try {
|
|
1045
|
+
const exp = window.top?.explorer || window.parent?.explorer;
|
|
1046
|
+
const cons = exp?.$?.('#consultant') || exp?.$?.('item-consultant');
|
|
1047
|
+
if (cons) {
|
|
1048
|
+
const ctx = this.buildConsultantContext();
|
|
1049
|
+
cons.activeFormContext = ctx;
|
|
1050
|
+
cons.activeFormSummary = ctx;
|
|
688
1051
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
cons.activeFormContext = ctx;
|
|
695
|
-
cons.activeFormSummary = ctx;
|
|
696
|
-
}
|
|
697
|
-
} catch { /* ignore */ }
|
|
1052
|
+
} catch { /* ignore */ }
|
|
1053
|
+
},
|
|
1054
|
+
view: {
|
|
1055
|
+
async set(n) {
|
|
1056
|
+
await this._mountActiveView(n);
|
|
698
1057
|
}
|
|
699
1058
|
},
|
|
700
1059
|
$listeners: {
|
|
@@ -159,6 +159,10 @@ export default {
|
|
|
159
159
|
const vc = host?.view_control;
|
|
160
160
|
const filter = this.filter;
|
|
161
161
|
const focusedOnly = this.focusedOnly;
|
|
162
|
+
if (filter === 'button' && (host?.view?.allowTools === false || host?.view?.DATA?.allowTools === false)) {
|
|
163
|
+
this._setTools([]);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
162
166
|
const rawTarget = this.targetItem;
|
|
163
167
|
const isMulti = Array.isArray(rawTarget) && rawTarget.length > 1;
|
|
164
168
|
const singleTarget = Array.isArray(rawTarget) ? (rawTarget[0] || null) : rawTarget;
|
|
@@ -276,7 +280,7 @@ export default {
|
|
|
276
280
|
tool.$handler.execute(e);
|
|
277
281
|
return;
|
|
278
282
|
}
|
|
279
|
-
if (tool.type === '$handler' && target && window.execute) {
|
|
283
|
+
if (tool.type === '$handler' && target && (typeof host?.openInForm === 'function' || window.execute)) {
|
|
280
284
|
const nav = typeof vc?.buildDrilldownNav === 'function'
|
|
281
285
|
? vc.buildDrilldownNav(tool.id, target)
|
|
282
286
|
: {
|
|
@@ -287,7 +291,10 @@ export default {
|
|
|
287
291
|
packetsKey: vc?.packetsKey || '',
|
|
288
292
|
unit: vc?.unit || '',
|
|
289
293
|
};
|
|
290
|
-
|
|
294
|
+
if (typeof host?.openInForm === 'function')
|
|
295
|
+
host.openInForm(target, nav);
|
|
296
|
+
else
|
|
297
|
+
window.execute(target, nav);
|
|
291
298
|
return;
|
|
292
299
|
}
|
|
293
300
|
if (typeof tool.execute === 'function')
|