web-mojo 2.1.550 → 2.1.627
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/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +19 -10
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.cjs.js.map +1 -1
- package/dist/auth.es.js +3 -3
- package/dist/auth.es.js.map +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +3 -3
- package/dist/chunks/ChatView-BjbXRdAB.js +2 -0
- package/dist/chunks/ChatView-BjbXRdAB.js.map +1 -0
- package/dist/chunks/{ChatView-C3oW0hvN.js → ChatView-swFqHyZi.js} +72 -16
- package/dist/chunks/ChatView-swFqHyZi.js.map +1 -0
- package/dist/chunks/{ContextMenu-B6VXD0nZ.js → ContextMenu-CyfbvpND.js} +10 -3
- package/dist/chunks/{ContextMenu-B6VXD0nZ.js.map → ContextMenu-CyfbvpND.js.map} +1 -1
- package/dist/chunks/{ContextMenu-BTEcH8VJ.js → ContextMenu-DeL1AJ2K.js} +3 -3
- package/dist/chunks/{ContextMenu-BTEcH8VJ.js.map → ContextMenu-DeL1AJ2K.js.map} +1 -1
- package/dist/chunks/{DataView-CCrESxij.js → DataView-gB9r-zaX.js} +2 -2
- package/dist/chunks/{DataView-CCrESxij.js.map → DataView-gB9r-zaX.js.map} +1 -1
- package/dist/chunks/{DataView-DX2DcGKA.js → DataView-jx8l28w_.js} +2 -2
- package/dist/chunks/{DataView-DX2DcGKA.js.map → DataView-jx8l28w_.js.map} +1 -1
- package/dist/chunks/{Dialog-CY3xpb40.js → Dialog-Cj0Qxc7O.js} +5 -5
- package/dist/chunks/{Dialog-CY3xpb40.js.map → Dialog-Cj0Qxc7O.js.map} +1 -1
- package/dist/chunks/{Dialog-DJkVf-r4.js → Dialog-JhRBUdiM.js} +2 -2
- package/dist/chunks/{Dialog-DJkVf-r4.js.map → Dialog-JhRBUdiM.js.map} +1 -1
- package/dist/chunks/{FormView-Czl1459d.js → FormView-CpChFpxv.js} +2 -2
- package/dist/chunks/{FormView-Czl1459d.js.map → FormView-CpChFpxv.js.map} +1 -1
- package/dist/chunks/{FormView-BFa00Ukw.js → FormView-bFKcq_xg.js} +2 -2
- package/dist/chunks/{FormView-BFa00Ukw.js.map → FormView-bFKcq_xg.js.map} +1 -1
- package/dist/chunks/{MetricsChart-CoKYs18I.js → MetricsChart-DS6rz7HC.js} +3 -3
- package/dist/chunks/{MetricsChart-CoKYs18I.js.map → MetricsChart-DS6rz7HC.js.map} +1 -1
- package/dist/chunks/{MetricsChart-Kx9ZS9n8.js → MetricsChart-Dv49hXjf.js} +2 -2
- package/dist/chunks/{MetricsChart-Kx9ZS9n8.js.map → MetricsChart-Dv49hXjf.js.map} +1 -1
- package/dist/chunks/{PDFViewer-CObvQFg-.js → PDFViewer-CcHiuM7c.js} +3 -3
- package/dist/chunks/{PDFViewer-CObvQFg-.js.map → PDFViewer-CcHiuM7c.js.map} +1 -1
- package/dist/chunks/{PDFViewer-CPRZHTMD.js → PDFViewer-DnsHONcG.js} +2 -2
- package/dist/chunks/{PDFViewer-CPRZHTMD.js.map → PDFViewer-DnsHONcG.js.map} +1 -1
- package/dist/chunks/{Page-oNlGYNf5.js → Page-DdYMaASr.js} +2 -2
- package/dist/chunks/{Page-oNlGYNf5.js.map → Page-DdYMaASr.js.map} +1 -1
- package/dist/chunks/{Page-csKPSoVL.js → Page-DmOy8qUF.js} +2 -2
- package/dist/chunks/{Page-csKPSoVL.js.map → Page-DmOy8qUF.js.map} +1 -1
- package/dist/chunks/TopNav-Bat8EzdF.js +1039 -0
- package/dist/chunks/TopNav-Bat8EzdF.js.map +1 -0
- package/dist/chunks/TopNav-CN_2e_48.js +2 -0
- package/dist/chunks/TopNav-CN_2e_48.js.map +1 -0
- package/dist/chunks/{WebApp-6ncOIVXa.js → WebApp-DeoEYrl6.js} +21 -14
- package/dist/chunks/{WebApp-6ncOIVXa.js.map → WebApp-DeoEYrl6.js.map} +1 -1
- package/dist/chunks/{WebApp-Z3SCGNp2.js → WebApp-DpGABopL.js} +2 -2
- package/dist/chunks/{WebApp-Z3SCGNp2.js.map → WebApp-DpGABopL.js.map} +1 -1
- package/dist/css/web-mojo.css +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +5 -5
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +237 -449
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +4 -4
- package/dist/portal.css +222 -0
- package/dist/table.css +0 -13
- package/package.json +1 -1
- package/dist/chunks/ChatView-C3oW0hvN.js.map +0 -1
- package/dist/chunks/ChatView-DXUzOG8o.js +0 -2
- package/dist/chunks/ChatView-DXUzOG8o.js.map +0 -1
- package/dist/chunks/TopNav-CWXnES46.js +0 -2
- package/dist/chunks/TopNav-CWXnES46.js.map +0 -1
- package/dist/chunks/TopNav-Szt3ULMS.js +0 -381
- package/dist/chunks/TopNav-Szt3ULMS.js.map +0 -1
|
@@ -0,0 +1,1039 @@
|
|
|
1
|
+
import { V as View } from "./WebApp-DeoEYrl6.js";
|
|
2
|
+
import Dialog from "./Dialog-Cj0Qxc7O.js";
|
|
3
|
+
import { G as GroupList } from "./ContextMenu-CyfbvpND.js";
|
|
4
|
+
class ResultsView extends View {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
super({
|
|
7
|
+
className: "search-results-view flex-grow-1 overflow-auto d-flex flex-column",
|
|
8
|
+
template: `
|
|
9
|
+
<div class="flex-grow-1 overflow-auto">
|
|
10
|
+
{{#data.loading}}
|
|
11
|
+
<div class="text-center p-4">
|
|
12
|
+
<div class="spinner-border spinner-border-sm text-muted" role="status">
|
|
13
|
+
<span class="visually-hidden">Loading...</span>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="mt-2 small text-muted">{{data.loadingText}}</div>
|
|
16
|
+
</div>
|
|
17
|
+
{{/data.loading}}
|
|
18
|
+
|
|
19
|
+
{{^data.loading}}
|
|
20
|
+
{{#data.items}}
|
|
21
|
+
<div class="simple-search-item position-relative"
|
|
22
|
+
data-action="select-item"
|
|
23
|
+
data-item-index="{{index}}">
|
|
24
|
+
{{{itemContent}}}
|
|
25
|
+
<i class="bi bi-chevron-right position-absolute end-0 top-50 translate-middle-y me-3 text-muted"></i>
|
|
26
|
+
</div>
|
|
27
|
+
{{/data.items}}
|
|
28
|
+
|
|
29
|
+
{{#data.showNoResults}}
|
|
30
|
+
<div class="text-center p-4">
|
|
31
|
+
<i class="bi bi-search text-muted mb-2" style="font-size: 1.5rem;"></i>
|
|
32
|
+
<div class="text-muted small">{{data.noResultsText}}</div>
|
|
33
|
+
<button type="button"
|
|
34
|
+
class="btn btn-link btn-sm mt-2 p-0"
|
|
35
|
+
data-action="clear-search">
|
|
36
|
+
Clear search
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
{{/data.showNoResults}}
|
|
40
|
+
|
|
41
|
+
{{#data.showEmpty}}
|
|
42
|
+
<div class="text-center p-4">
|
|
43
|
+
<i class="{{data.emptyIcon}} text-muted mb-2" style="font-size: 2rem;"></i>
|
|
44
|
+
<div class="text-muted small mb-2">{{data.emptyText}}</div>
|
|
45
|
+
{{#data.emptySubtext}}
|
|
46
|
+
<div class="text-muted" style="font-size: 0.75rem;">
|
|
47
|
+
{{data.emptySubtext}}
|
|
48
|
+
</div>
|
|
49
|
+
{{/data.emptySubtext}}
|
|
50
|
+
</div>
|
|
51
|
+
{{/data.showEmpty}}
|
|
52
|
+
{{/data.loading}}
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{{#data.showResultsCount}}
|
|
56
|
+
<div class="border-top bg-light p-2 text-center">
|
|
57
|
+
<small class="text-muted">
|
|
58
|
+
{{data.filteredCount}} of {{data.totalCount}}
|
|
59
|
+
</small>
|
|
60
|
+
</div>
|
|
61
|
+
{{/data.showResultsCount}}
|
|
62
|
+
`,
|
|
63
|
+
...options
|
|
64
|
+
});
|
|
65
|
+
this.parentView = options.parentView;
|
|
66
|
+
}
|
|
67
|
+
async handleActionSelectItem(event, element) {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
const itemIndex = parseInt(element.getAttribute("data-item-index"));
|
|
70
|
+
if (this.parentView) {
|
|
71
|
+
this.parentView.handleItemSelection(itemIndex);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async handleActionClearSearch(event, _element) {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
if (this.parentView) {
|
|
77
|
+
this.parentView.clearSearch();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
class SimpleSearchView extends View {
|
|
82
|
+
constructor(options = {}) {
|
|
83
|
+
super({
|
|
84
|
+
className: "simple-search-view h-100 d-flex flex-column",
|
|
85
|
+
template: `
|
|
86
|
+
<div class="p-3 border-bottom bg-light">
|
|
87
|
+
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
88
|
+
<h6 class="text-muted fw-semibold mb-0">
|
|
89
|
+
{{#data.headerIcon}}<i class="{{data.headerIcon}} me-2"></i>{{/data.headerIcon}}
|
|
90
|
+
{{{data.headerText}}}
|
|
91
|
+
</h6>
|
|
92
|
+
{{#data.showExitButton}}
|
|
93
|
+
<button class="btn btn-link p-0 text-muted simple-search-exit-btn"
|
|
94
|
+
type="button"
|
|
95
|
+
data-action="exit-view"
|
|
96
|
+
title="Exit"
|
|
97
|
+
aria-label="Exit view">
|
|
98
|
+
<i class="bi bi-x-lg" aria-hidden="true"></i>
|
|
99
|
+
</button>
|
|
100
|
+
{{/data.showExitButton}}
|
|
101
|
+
</div>
|
|
102
|
+
<div class="position-relative">
|
|
103
|
+
<input type="text"
|
|
104
|
+
class="form-control form-control-sm pe-5"
|
|
105
|
+
placeholder="{{data.searchPlaceholder}}"
|
|
106
|
+
value="{{data.searchValue}}"
|
|
107
|
+
data-filter="live-search"
|
|
108
|
+
data-filter-debounce="{{data.debounceMs}}"
|
|
109
|
+
data-change-action="search-items">
|
|
110
|
+
<button class="btn btn-link p-0 position-absolute top-50 end-0 translate-middle-y me-2 text-muted simple-search-clear-btn"
|
|
111
|
+
type="button"
|
|
112
|
+
data-action="clear-search"
|
|
113
|
+
title="Clear search"
|
|
114
|
+
aria-label="Clear search">
|
|
115
|
+
<i class="bi bi-x-circle-fill" aria-hidden="true"></i>
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div data-container="results"></div>
|
|
121
|
+
|
|
122
|
+
{{#data.showFooter}}
|
|
123
|
+
<div class="p-3 border-top bg-light">
|
|
124
|
+
<small class="text-muted">
|
|
125
|
+
<i class="{{data.footerIcon}} me-1"></i>
|
|
126
|
+
{{{data.footerContent}}}
|
|
127
|
+
</small>
|
|
128
|
+
</div>
|
|
129
|
+
{{/data.showFooter}}
|
|
130
|
+
`,
|
|
131
|
+
...options
|
|
132
|
+
});
|
|
133
|
+
this.Collection = options.Collection;
|
|
134
|
+
this.collection = options.collection;
|
|
135
|
+
this.itemTemplate = options.itemTemplate || this.getDefaultItemTemplate();
|
|
136
|
+
this.searchFields = options.searchFields || ["name"];
|
|
137
|
+
this.collectionParams = { size: 25, ...options.collectionParams };
|
|
138
|
+
this.headerText = options.headerText || "Select Item";
|
|
139
|
+
this.headerIcon = options.headerIcon || "bi bi-list";
|
|
140
|
+
this.searchPlaceholder = options.searchPlaceholder || "Search...";
|
|
141
|
+
this.loadingText = options.loadingText || "Loading items...";
|
|
142
|
+
this.noResultsText = options.noResultsText || "No items match your search";
|
|
143
|
+
this.emptyText = options.emptyText || "No items available";
|
|
144
|
+
this.emptySubtext = options.emptySubtext || null;
|
|
145
|
+
this.emptyIcon = options.emptyIcon || "bi bi-inbox";
|
|
146
|
+
this.footerContent = options.footerContent || null;
|
|
147
|
+
this.footerIcon = options.footerIcon || "bi bi-info-circle";
|
|
148
|
+
this.showExitButton = options.showExitButton || false;
|
|
149
|
+
this.searchValue = "";
|
|
150
|
+
this.filteredItems = [];
|
|
151
|
+
this.loading = false;
|
|
152
|
+
this.hasSearched = false;
|
|
153
|
+
this.searchTimer = null;
|
|
154
|
+
this.debounceMs = options.debounceMs || 800;
|
|
155
|
+
this.resultsView = new ResultsView({
|
|
156
|
+
parentView: this
|
|
157
|
+
});
|
|
158
|
+
if (!this.collection && this.Collection) {
|
|
159
|
+
this.collection = new this.Collection();
|
|
160
|
+
}
|
|
161
|
+
this.addChild(this.resultsView);
|
|
162
|
+
}
|
|
163
|
+
onInit() {
|
|
164
|
+
if (this.collection) {
|
|
165
|
+
this.setupCollection();
|
|
166
|
+
}
|
|
167
|
+
if (this.collection && this.options.autoLoad !== false) {
|
|
168
|
+
this.loadItems();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
setupCollection() {
|
|
172
|
+
Object.assign(this.collection.params, this.collectionParams);
|
|
173
|
+
this.collection.on("fetch:success", () => {
|
|
174
|
+
this.loading = false;
|
|
175
|
+
this.updateFilteredItems();
|
|
176
|
+
});
|
|
177
|
+
this.collection.on("fetch:error", () => {
|
|
178
|
+
this.loading = false;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async loadItems() {
|
|
182
|
+
if (!this.collection) {
|
|
183
|
+
console.warn("SimpleSearchView: No collection provided");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.loading = true;
|
|
187
|
+
this.updateResultsView();
|
|
188
|
+
try {
|
|
189
|
+
await this.collection.fetch();
|
|
190
|
+
this.updateFilteredItems();
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error("Error loading items:", error);
|
|
193
|
+
const app = this.getApp();
|
|
194
|
+
app?.showError?.("Failed to load items. Please try again.");
|
|
195
|
+
} finally {
|
|
196
|
+
this.loading = false;
|
|
197
|
+
this.updateFilteredItems();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
updateFilteredItems() {
|
|
201
|
+
if (!this.collection) {
|
|
202
|
+
this.filteredItems = [];
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const items = this.collection.toJSON();
|
|
206
|
+
if (!this.searchValue || !this.searchValue.trim()) {
|
|
207
|
+
this.filteredItems = items;
|
|
208
|
+
} else {
|
|
209
|
+
const searchTerm = this.searchValue.toLowerCase().trim();
|
|
210
|
+
this.filteredItems = items.filter((item) => {
|
|
211
|
+
return this.searchFields.some((field) => {
|
|
212
|
+
const value = this.getNestedValue(item, field);
|
|
213
|
+
return value && value.toString().toLowerCase().includes(searchTerm);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
this.updateResultsView();
|
|
218
|
+
}
|
|
219
|
+
getNestedValue(obj, path) {
|
|
220
|
+
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
221
|
+
}
|
|
222
|
+
async getViewData() {
|
|
223
|
+
return {
|
|
224
|
+
searchValue: this.searchValue,
|
|
225
|
+
showFooter: !!this.footerContent,
|
|
226
|
+
showExitButton: this.showExitButton,
|
|
227
|
+
debounceMs: this.debounceMs,
|
|
228
|
+
// UI text
|
|
229
|
+
headerText: this.headerText,
|
|
230
|
+
headerIcon: this.headerIcon,
|
|
231
|
+
searchPlaceholder: this.searchPlaceholder,
|
|
232
|
+
footerContent: this.footerContent,
|
|
233
|
+
footerIcon: this.footerIcon
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
updateResultsView() {
|
|
237
|
+
if (!this.resultsView) return;
|
|
238
|
+
const hasItems = this.collection && this.collection.length() > 0;
|
|
239
|
+
const hasFilteredItems = this.filteredItems.length > 0;
|
|
240
|
+
const hasSearchValue = this.searchValue.length > 0;
|
|
241
|
+
const processedItems = this.filteredItems.map((item, index) => {
|
|
242
|
+
return {
|
|
243
|
+
...item,
|
|
244
|
+
index,
|
|
245
|
+
itemContent: this.processItemTemplate(item)
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
this.resultsView.data = {
|
|
249
|
+
loading: this.loading,
|
|
250
|
+
items: processedItems,
|
|
251
|
+
showEmpty: !this.loading && !hasItems,
|
|
252
|
+
showNoResults: !this.loading && hasItems && !hasFilteredItems && hasSearchValue,
|
|
253
|
+
showResultsCount: !this.loading && hasItems,
|
|
254
|
+
filteredCount: this.filteredItems.length,
|
|
255
|
+
totalCount: this.collection?.restEnabled ? this.collection?.meta?.count || 0 : this.collection?.length() || 0,
|
|
256
|
+
// UI text
|
|
257
|
+
loadingText: this.loadingText,
|
|
258
|
+
noResultsText: this.noResultsText,
|
|
259
|
+
emptyText: this.emptyText,
|
|
260
|
+
emptySubtext: this.emptySubtext,
|
|
261
|
+
emptyIcon: this.emptyIcon
|
|
262
|
+
};
|
|
263
|
+
this.resultsView.render();
|
|
264
|
+
}
|
|
265
|
+
processItemTemplate(item) {
|
|
266
|
+
let template = this.itemTemplate;
|
|
267
|
+
template = template.replace(/\{\{(\w+)\}\}/g, (match, prop) => {
|
|
268
|
+
return this.getNestedValue(item, prop) || "";
|
|
269
|
+
});
|
|
270
|
+
return template;
|
|
271
|
+
}
|
|
272
|
+
getDefaultItemTemplate() {
|
|
273
|
+
return `
|
|
274
|
+
<div class="p-3 border-bottom">
|
|
275
|
+
<div class="fw-semibold text-dark">{{name}}</div>
|
|
276
|
+
<small class="text-muted">{{id}}</small>
|
|
277
|
+
</div>
|
|
278
|
+
`;
|
|
279
|
+
}
|
|
280
|
+
async onPassThruActionSearchItems(event, element) {
|
|
281
|
+
const searchValue = element.value || "";
|
|
282
|
+
console.log("search change...");
|
|
283
|
+
this.searchValue = searchValue;
|
|
284
|
+
this.hasSearched = true;
|
|
285
|
+
if (this.searchTimer) {
|
|
286
|
+
clearTimeout(this.searchTimer);
|
|
287
|
+
}
|
|
288
|
+
this.performSearch();
|
|
289
|
+
}
|
|
290
|
+
async performSearch() {
|
|
291
|
+
const searchParams = { ...this.collectionParams };
|
|
292
|
+
if (this.searchValue && this.searchValue.length > 1) {
|
|
293
|
+
searchParams.search = this.searchValue.trim();
|
|
294
|
+
}
|
|
295
|
+
this.collection.setParams(searchParams, true);
|
|
296
|
+
}
|
|
297
|
+
handleItemSelection(itemIndex) {
|
|
298
|
+
if (isNaN(itemIndex) || itemIndex < 0 || itemIndex >= this.filteredItems.length) {
|
|
299
|
+
console.error("Invalid item index:", itemIndex);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const item = this.filteredItems[itemIndex];
|
|
303
|
+
const model = this.collection ? this.collection.get(item.id) : null;
|
|
304
|
+
this.emit("item:selected", {
|
|
305
|
+
item,
|
|
306
|
+
model,
|
|
307
|
+
index: itemIndex
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Set the collection for this search view
|
|
312
|
+
*/
|
|
313
|
+
setCollection(collection) {
|
|
314
|
+
this.collection = collection;
|
|
315
|
+
this.setupCollection();
|
|
316
|
+
return this;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Set the item template
|
|
320
|
+
*/
|
|
321
|
+
setItemTemplate(template) {
|
|
322
|
+
this.itemTemplate = template;
|
|
323
|
+
this.updateResultsView();
|
|
324
|
+
return this;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Set search fields
|
|
328
|
+
*/
|
|
329
|
+
setSearchFields(fields) {
|
|
330
|
+
this.searchFields = Array.isArray(fields) ? fields : [fields];
|
|
331
|
+
return this;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Refresh items list
|
|
335
|
+
*/
|
|
336
|
+
async refresh() {
|
|
337
|
+
await this.loadItems();
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Focus the search input
|
|
341
|
+
*/
|
|
342
|
+
focusSearch() {
|
|
343
|
+
const searchInput = this.element?.querySelector('input[data-action="search-items"]');
|
|
344
|
+
if (searchInput) {
|
|
345
|
+
searchInput.focus();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Handle exit button click - emits event instead of closing
|
|
350
|
+
*/
|
|
351
|
+
async handleActionExitView(event, element) {
|
|
352
|
+
this.emit("exit", { view: this });
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Clear search and reset
|
|
356
|
+
*/
|
|
357
|
+
async handleActionClearSearch(event, element) {
|
|
358
|
+
this.clearSearch();
|
|
359
|
+
}
|
|
360
|
+
clearSearch() {
|
|
361
|
+
this.searchValue = "";
|
|
362
|
+
this.hasSearched = false;
|
|
363
|
+
const searchInput = this.element?.querySelector('input[data-change-action="search-items"]');
|
|
364
|
+
if (searchInput) {
|
|
365
|
+
searchInput.value = "";
|
|
366
|
+
searchInput.focus();
|
|
367
|
+
}
|
|
368
|
+
this.performSearch();
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get the number of available items
|
|
372
|
+
*/
|
|
373
|
+
getItemCount() {
|
|
374
|
+
return this.collection ? this.collection.length() : 0;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Get the number of filtered items
|
|
378
|
+
*/
|
|
379
|
+
getFilteredItemCount() {
|
|
380
|
+
return this.filteredItems.length;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Check if items are loaded
|
|
384
|
+
*/
|
|
385
|
+
hasItems() {
|
|
386
|
+
return this.getItemCount() > 0;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Get current search value
|
|
390
|
+
*/
|
|
391
|
+
getSearchValue() {
|
|
392
|
+
return this.searchValue;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Set search value programmatically
|
|
396
|
+
*/
|
|
397
|
+
setSearchValue(value) {
|
|
398
|
+
this.searchValue = value || "";
|
|
399
|
+
this.hasSearched = !!this.searchValue;
|
|
400
|
+
const searchInput = this.element?.querySelector('input[data-action="search-items"]');
|
|
401
|
+
if (searchInput) {
|
|
402
|
+
searchInput.value = this.searchValue;
|
|
403
|
+
}
|
|
404
|
+
this.performSearch();
|
|
405
|
+
return this;
|
|
406
|
+
}
|
|
407
|
+
async onAfterRender() {
|
|
408
|
+
await super.onAfterRender();
|
|
409
|
+
if (this.resultsView && !this.resultsView.isMounted()) {
|
|
410
|
+
const container = this.element?.querySelector('[data-container="results"]');
|
|
411
|
+
if (container) {
|
|
412
|
+
await this.resultsView.render(true, container);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
this.updateResultsView();
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Cleanup on destroy
|
|
419
|
+
*/
|
|
420
|
+
async onBeforeDestroy() {
|
|
421
|
+
if (this.searchTimer) {
|
|
422
|
+
clearTimeout(this.searchTimer);
|
|
423
|
+
}
|
|
424
|
+
if (this.collection) {
|
|
425
|
+
this.collection.off("update");
|
|
426
|
+
}
|
|
427
|
+
await super.onBeforeDestroy();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
class GroupSelectorButton extends View {
|
|
431
|
+
constructor(options = {}) {
|
|
432
|
+
super({
|
|
433
|
+
tagName: "div",
|
|
434
|
+
className: "nav-item",
|
|
435
|
+
...options
|
|
436
|
+
});
|
|
437
|
+
const app = this.getApp();
|
|
438
|
+
this.Collection = options.Collection || app?.GroupCollection || GroupList;
|
|
439
|
+
this.collection = options.collection || new this.Collection();
|
|
440
|
+
this.currentGroup = options.currentGroup !== void 0 ? options.currentGroup : app?.activeGroup;
|
|
441
|
+
this.buttonClass = options.buttonClass || "btn btn-link nav-link";
|
|
442
|
+
this.buttonIcon = options.buttonIcon || "bi-building";
|
|
443
|
+
this.defaultText = options.defaultText || "Select Group";
|
|
444
|
+
this.itemTemplate = options.itemTemplate;
|
|
445
|
+
this.searchFields = options.searchFields || ["name"];
|
|
446
|
+
this.headerText = options.headerText || "Select Group";
|
|
447
|
+
this.searchPlaceholder = options.searchPlaceholder || "Search groups...";
|
|
448
|
+
this.autoSetActiveGroup = options.autoSetActiveGroup !== false;
|
|
449
|
+
this.onGroupSelected = options.onGroupSelected;
|
|
450
|
+
this.dialog = null;
|
|
451
|
+
if (app?.events) {
|
|
452
|
+
app.events.on("group:changed", (data) => {
|
|
453
|
+
if (data.group !== this.currentGroup) {
|
|
454
|
+
this.setCurrentGroup(data.group);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async getTemplate() {
|
|
460
|
+
return `
|
|
461
|
+
<button class="{{buttonClass}}"
|
|
462
|
+
data-action="show-selector"
|
|
463
|
+
type="button"
|
|
464
|
+
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
|
465
|
+
<i class="{{buttonIcon}} me-1"></i>
|
|
466
|
+
<span class="group-name">{{displayName}}</span>
|
|
467
|
+
</button>
|
|
468
|
+
`;
|
|
469
|
+
}
|
|
470
|
+
async onBeforeRender() {
|
|
471
|
+
await super.onBeforeRender();
|
|
472
|
+
console.log("GroupSelectorButton onBeforeRender - currentGroup:", this.currentGroup?.get?.("name") || this.currentGroup?.name || "none");
|
|
473
|
+
this.buttonClass = this.buttonClass;
|
|
474
|
+
this.buttonIcon = this.buttonIcon;
|
|
475
|
+
this.displayName = this.currentGroup?.get?.("name") || this.currentGroup?.name || this.defaultText;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Show the group selector dialog
|
|
479
|
+
*/
|
|
480
|
+
async onActionShowSelector(event) {
|
|
481
|
+
const searchView = new SimpleSearchView({
|
|
482
|
+
Collection: this.Collection,
|
|
483
|
+
collection: this.collection,
|
|
484
|
+
itemTemplate: this.itemTemplate || this.getDefaultItemTemplate(),
|
|
485
|
+
searchFields: this.searchFields,
|
|
486
|
+
headerText: this.headerText,
|
|
487
|
+
searchPlaceholder: this.searchPlaceholder,
|
|
488
|
+
headerIcon: this.buttonIcon,
|
|
489
|
+
showExitButton: false
|
|
490
|
+
});
|
|
491
|
+
this.dialog = new Dialog({
|
|
492
|
+
title: this.headerText,
|
|
493
|
+
body: searchView,
|
|
494
|
+
size: "md",
|
|
495
|
+
scrollable: true,
|
|
496
|
+
noBodyPadding: true,
|
|
497
|
+
buttons: [],
|
|
498
|
+
closeButton: true
|
|
499
|
+
});
|
|
500
|
+
searchView.on("item:selected", (data) => {
|
|
501
|
+
this.handleGroupSelection(data.model || data.item);
|
|
502
|
+
if (this.dialog) {
|
|
503
|
+
this.dialog.hide();
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
this.dialog.on("hidden", () => {
|
|
507
|
+
this.dialog.destroy();
|
|
508
|
+
this.dialog = null;
|
|
509
|
+
});
|
|
510
|
+
await this.dialog.render(true, document.body);
|
|
511
|
+
this.dialog.show();
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Handle group selection
|
|
516
|
+
*/
|
|
517
|
+
handleGroupSelection(group) {
|
|
518
|
+
this.currentGroup = group;
|
|
519
|
+
this.displayName = group?.get?.("name") || group?.name || this.defaultText;
|
|
520
|
+
this.render();
|
|
521
|
+
const app = this.getApp();
|
|
522
|
+
if (this.autoSetActiveGroup && app?.setActiveGroup) {
|
|
523
|
+
app.setActiveGroup(group);
|
|
524
|
+
}
|
|
525
|
+
if (this.onGroupSelected) {
|
|
526
|
+
this.onGroupSelected({ group });
|
|
527
|
+
}
|
|
528
|
+
this.emit("group-selected", { group });
|
|
529
|
+
if (app?.events) {
|
|
530
|
+
app.events.emit("group:selected", { group });
|
|
531
|
+
app.events.emit("group:changed", { group });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Default item template for groups (matches Sidebar pattern)
|
|
536
|
+
* Note: data-action and data-item-index are added by ResultsView wrapper
|
|
537
|
+
*/
|
|
538
|
+
getDefaultItemTemplate() {
|
|
539
|
+
return `
|
|
540
|
+
<div class="d-flex align-items-center p-3 border-bottom">
|
|
541
|
+
<div class="flex-grow-1">
|
|
542
|
+
<div class="fw-semibold text-dark">{{name}}</div>
|
|
543
|
+
<small class="text-muted">#{{id}} {{kind}}</small>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
`;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Set the current group programmatically
|
|
550
|
+
*/
|
|
551
|
+
setCurrentGroup(group) {
|
|
552
|
+
this.currentGroup = group;
|
|
553
|
+
this.displayName = group?.get?.("name") || group?.name || this.defaultText;
|
|
554
|
+
if (this.mounted) {
|
|
555
|
+
this.render();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Get the current group
|
|
560
|
+
*/
|
|
561
|
+
getCurrentGroup() {
|
|
562
|
+
return this.currentGroup;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
class TopNav extends View {
|
|
566
|
+
constructor(options = {}) {
|
|
567
|
+
const themes = {
|
|
568
|
+
light: "navbar navbar-expand-lg navbar-light topnav-light",
|
|
569
|
+
dark: "navbar navbar-expand-lg navbar-dark topnav-dark",
|
|
570
|
+
clean: "navbar navbar-expand-lg navbar-light topnav-clean",
|
|
571
|
+
gradient: "navbar navbar-expand-lg navbar-dark topnav-gradient"
|
|
572
|
+
};
|
|
573
|
+
const themeName = options.theme || "light";
|
|
574
|
+
let navbarClass = themes[themeName] || themes.light;
|
|
575
|
+
if (options.shadow) {
|
|
576
|
+
navbarClass += ` topnav-shadow-${options.shadow}`;
|
|
577
|
+
}
|
|
578
|
+
super({
|
|
579
|
+
tagName: "nav",
|
|
580
|
+
className: navbarClass,
|
|
581
|
+
style: "position: relative; z-index: 1030;",
|
|
582
|
+
...options
|
|
583
|
+
});
|
|
584
|
+
this.displayMode = options.displayMode || "both";
|
|
585
|
+
this.showPageIcon = options.showPageIcon !== false;
|
|
586
|
+
this.showPageDescription = options.showPageDescription || false;
|
|
587
|
+
this.showBreadcrumbs = options.showBreadcrumbs || false;
|
|
588
|
+
this.groupIcon = options.groupIcon || "bi-building";
|
|
589
|
+
this.currentPage = null;
|
|
590
|
+
this.previousPage = null;
|
|
591
|
+
this.config = {
|
|
592
|
+
brand: options.brand || "MOJO App",
|
|
593
|
+
brandIcon: options.brandIcon || "bi bi-play-circle",
|
|
594
|
+
brandRoute: options.brandRoute || "/",
|
|
595
|
+
navItems: options.navItems || [],
|
|
596
|
+
rightItems: options.rightItems || [],
|
|
597
|
+
showSidebarToggle: options.showSidebarToggle || false,
|
|
598
|
+
sidebarToggleAction: options.sidebarToggleAction || "toggle-sidebar",
|
|
599
|
+
...options
|
|
600
|
+
};
|
|
601
|
+
this.userMenu = options.userMenu || this.findMenuItem("user");
|
|
602
|
+
if (this.userMenu) this.userMenu.id = "user";
|
|
603
|
+
this.loginMenu = options.loginMenu || this.findMenuItem("login");
|
|
604
|
+
this.setupPageListeners();
|
|
605
|
+
this.setupGroupListeners();
|
|
606
|
+
this.groupSelectorButton = null;
|
|
607
|
+
this.currentGroup = null;
|
|
608
|
+
}
|
|
609
|
+
findMenuItem(id) {
|
|
610
|
+
let item = this.config.navItems.find((item2) => item2.id === id);
|
|
611
|
+
if (!item) {
|
|
612
|
+
item = this.config.rightItems.find((item2) => item2.id === id);
|
|
613
|
+
}
|
|
614
|
+
return item || null;
|
|
615
|
+
}
|
|
616
|
+
replaceMenuItem(id, new_menu) {
|
|
617
|
+
const navIndex = this.config.navItems.findIndex((item) => item.id === id);
|
|
618
|
+
if (navIndex !== -1) {
|
|
619
|
+
this.config.navItems[navIndex] = new_menu;
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
const rightIndex = this.config.rightItems.findIndex((item) => item.id === id);
|
|
623
|
+
if (rightIndex !== -1) {
|
|
624
|
+
this.config.rightItems[rightIndex] = new_menu;
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
setBrand(brand, icon = null) {
|
|
630
|
+
this.config.brand = brand;
|
|
631
|
+
this.config.brandIcon = icon || this.config.brandIcon;
|
|
632
|
+
this.render();
|
|
633
|
+
}
|
|
634
|
+
setUser(user) {
|
|
635
|
+
if (!user) {
|
|
636
|
+
this.replaceMenuItem("user", this.loginMenu);
|
|
637
|
+
} else {
|
|
638
|
+
this.userMenu.label = user.get("display_name");
|
|
639
|
+
this.replaceMenuItem("login", this.userMenu);
|
|
640
|
+
}
|
|
641
|
+
this.setModel(user);
|
|
642
|
+
}
|
|
643
|
+
_onModelChange() {
|
|
644
|
+
if (this.model) {
|
|
645
|
+
this.userMenu.label = this.model.get("display_name");
|
|
646
|
+
}
|
|
647
|
+
if (this.isMounted()) {
|
|
648
|
+
this.render();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Get template based on display mode
|
|
653
|
+
*/
|
|
654
|
+
async getTemplate() {
|
|
655
|
+
return `
|
|
656
|
+
<div class="container-fluid">
|
|
657
|
+
{{#data.showSidebarToggle}}
|
|
658
|
+
<button class="topnav-sidebar-toggle me-2" data-action="{{data.sidebarToggleAction}}" aria-label="Toggle Sidebar">
|
|
659
|
+
<i class="bi bi-chevron-right toggle-chevron"></i>
|
|
660
|
+
</button>
|
|
661
|
+
{{/data.showSidebarToggle}}
|
|
662
|
+
|
|
663
|
+
{{#data.showGroupInfo}}
|
|
664
|
+
<div class="navbar-brand d-flex align-items-center">
|
|
665
|
+
{{#data.groupIcon}}<i class="{{data.groupIcon}} me-2"></i>{{/data.groupIcon}}
|
|
666
|
+
<div>
|
|
667
|
+
<span class="topnav-group-name"
|
|
668
|
+
role="button"
|
|
669
|
+
tabindex="0"
|
|
670
|
+
data-action="open-group-selector"
|
|
671
|
+
style="cursor: pointer;">
|
|
672
|
+
{{data.currentGroupName}}
|
|
673
|
+
</span>
|
|
674
|
+
{{#data.showPageTitle}}
|
|
675
|
+
<span class="text-muted mx-2">|</span>
|
|
676
|
+
<span>{{data.currentPageName}}</span>
|
|
677
|
+
{{/data.showPageTitle}}
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
{{/data.showGroupInfo}}
|
|
681
|
+
|
|
682
|
+
{{#data.showPageInfo}}
|
|
683
|
+
<div class="navbar-brand d-flex align-items-center">
|
|
684
|
+
{{#data.currentPageIcon}}<i class="{{data.currentPageIcon}} me-2"></i>{{/data.currentPageIcon}}
|
|
685
|
+
<div>
|
|
686
|
+
<span>{{data.currentPageName}}</span>
|
|
687
|
+
{{#data.currentPageDescription}}
|
|
688
|
+
<small class="d-block" style="font-size: 0.75rem; line-height: 1;">{{data.currentPageDescription}}</small>
|
|
689
|
+
{{/data.currentPageDescription}}
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
{{/data.showPageInfo}}
|
|
693
|
+
|
|
694
|
+
{{#data.showBrand}}
|
|
695
|
+
<a class="navbar-brand" href="{{data.brandRoute}}">
|
|
696
|
+
{{#data.brandIcon}}<i class="{{data.brandIcon}} me-2"></i>{{/data.brandIcon}}
|
|
697
|
+
{{data.brand}}
|
|
698
|
+
</a>
|
|
699
|
+
{{/data.showBrand}}
|
|
700
|
+
|
|
701
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#{{data.navbarId}}">
|
|
702
|
+
<span class="navbar-toggler-icon"></span>
|
|
703
|
+
</button>
|
|
704
|
+
|
|
705
|
+
<div class="collapse navbar-collapse" id="{{data.navbarId}}">
|
|
706
|
+
{{#data.showNavItems}}
|
|
707
|
+
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
|
708
|
+
{{#data.navItems}}
|
|
709
|
+
<li class="nav-item">
|
|
710
|
+
<a class="nav-link {{#active}}active{{/active}}" href="{{route}}">
|
|
711
|
+
{{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
|
|
712
|
+
{{text}}
|
|
713
|
+
</a>
|
|
714
|
+
</li>
|
|
715
|
+
{{/data.navItems}}
|
|
716
|
+
</ul>
|
|
717
|
+
{{/data.showNavItems}}
|
|
718
|
+
|
|
719
|
+
{{#data.hasRightItems}}
|
|
720
|
+
<div class="navbar-nav ms-auto">
|
|
721
|
+
{{#data.rightItems}}
|
|
722
|
+
{{#isGroupSelector}}
|
|
723
|
+
<div data-container="group-selector-{{id}}"></div>
|
|
724
|
+
{{/isGroupSelector}}
|
|
725
|
+
{{^isGroupSelector}}
|
|
726
|
+
{{#isDropdown}}
|
|
727
|
+
<div class="nav-item dropdown">
|
|
728
|
+
<a class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
729
|
+
{{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
|
|
730
|
+
{{label}}
|
|
731
|
+
</a>
|
|
732
|
+
<ul class="dropdown-menu dropdown-menu-end">
|
|
733
|
+
{{#items}}
|
|
734
|
+
{{#divider}}
|
|
735
|
+
<li><hr class="dropdown-divider"></li>
|
|
736
|
+
{{/divider}}
|
|
737
|
+
{{^divider}}
|
|
738
|
+
<li>
|
|
739
|
+
<a class="dropdown-item" role="button" {{#action}}data-action="{{action}}"{{/action}}>
|
|
740
|
+
{{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
|
|
741
|
+
{{label}}
|
|
742
|
+
</a>
|
|
743
|
+
</li>
|
|
744
|
+
{{/divider}}
|
|
745
|
+
{{/items}}
|
|
746
|
+
</ul>
|
|
747
|
+
</div>
|
|
748
|
+
{{/isDropdown}}
|
|
749
|
+
{{^isDropdown}}
|
|
750
|
+
{{#isButton}}
|
|
751
|
+
<button class="{{buttonClass}}" data-action="{{action}}" data-id="{{id}}">
|
|
752
|
+
{{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
|
|
753
|
+
{{label}}
|
|
754
|
+
</button>
|
|
755
|
+
{{/isButton}}
|
|
756
|
+
{{^isButton}}
|
|
757
|
+
<a class="nav-link" href="{{href}}" {{#action}}data-action="{{action}}"{{/action}}>
|
|
758
|
+
{{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
|
|
759
|
+
{{label}}
|
|
760
|
+
</a>
|
|
761
|
+
{{/isButton}}
|
|
762
|
+
{{/isDropdown}}
|
|
763
|
+
{{/isGroupSelector}}
|
|
764
|
+
{{/data.rightItems}}
|
|
765
|
+
</div>
|
|
766
|
+
{{/data.hasRightItems}}
|
|
767
|
+
</div>
|
|
768
|
+
</div>
|
|
769
|
+
`;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Process and normalize data before rendering (like Sidebar)
|
|
773
|
+
*/
|
|
774
|
+
async onBeforeRender() {
|
|
775
|
+
await super.onBeforeRender();
|
|
776
|
+
const app = this.getApp();
|
|
777
|
+
const activeGroup = this.currentGroup || app?.activeGroup;
|
|
778
|
+
const showGroupInfo = this.displayMode === "group" || this.displayMode === "group_page_titles";
|
|
779
|
+
const showPageTitle = this.displayMode === "group_page_titles";
|
|
780
|
+
const showPageInfo = this.displayMode === "page" || this.displayMode === "both";
|
|
781
|
+
const showBrand = !showGroupInfo && !showPageInfo;
|
|
782
|
+
const showNavItems = this.displayMode === "menu" || this.displayMode === "both";
|
|
783
|
+
const navItems = this.filterItemsByPermissions(this.config.navItems || []);
|
|
784
|
+
const rightItems = this.processRightItems(this.config.rightItems || []);
|
|
785
|
+
this.data = {
|
|
786
|
+
// Brand information
|
|
787
|
+
brand: this.config.brand,
|
|
788
|
+
brandIcon: this.config.brandIcon,
|
|
789
|
+
brandRoute: this.config.brandRoute,
|
|
790
|
+
showBrand,
|
|
791
|
+
// Navbar configuration
|
|
792
|
+
navbarId: `navbar-${this.id}`,
|
|
793
|
+
// Navigation items
|
|
794
|
+
navItems,
|
|
795
|
+
showNavItems,
|
|
796
|
+
// Right items
|
|
797
|
+
rightItems,
|
|
798
|
+
hasRightItems: rightItems.length > 0,
|
|
799
|
+
// Group display
|
|
800
|
+
showGroupInfo,
|
|
801
|
+
showPageTitle,
|
|
802
|
+
currentGroupName: activeGroup?.get?.("name") || activeGroup?.name || "Select Group",
|
|
803
|
+
groupIcon: this.groupIcon,
|
|
804
|
+
// Page display
|
|
805
|
+
showPageInfo,
|
|
806
|
+
currentPageName: this.currentPage?.title || this.currentPage?.name || "",
|
|
807
|
+
currentPageIcon: this.currentPage?.icon || this.currentPage?.pageIcon || "",
|
|
808
|
+
currentPageDescription: this.showPageDescription ? this.currentPage?.description : "",
|
|
809
|
+
// Sidebar toggle
|
|
810
|
+
showSidebarToggle: this.config.showSidebarToggle,
|
|
811
|
+
sidebarToggleAction: this.config.sidebarToggleAction,
|
|
812
|
+
// Display mode
|
|
813
|
+
displayMode: this.displayMode
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Process right items configuration
|
|
818
|
+
*/
|
|
819
|
+
processRightItems(rightItems) {
|
|
820
|
+
return this.filterItemsByPermissions(rightItems).map((item) => {
|
|
821
|
+
const processedItem = { ...item };
|
|
822
|
+
if (item.items) {
|
|
823
|
+
processedItem.items = this.filterItemsByPermissions(item.items);
|
|
824
|
+
}
|
|
825
|
+
if (item.type === "group-selector") {
|
|
826
|
+
processedItem.isGroupSelector = true;
|
|
827
|
+
processedItem.isDropdown = false;
|
|
828
|
+
processedItem.isButton = false;
|
|
829
|
+
const groupSelectorOptions = {
|
|
830
|
+
containerId: `group-selector-${item.id || "default"}`
|
|
831
|
+
};
|
|
832
|
+
if (item.Collection !== void 0) groupSelectorOptions.Collection = item.Collection;
|
|
833
|
+
if (item.collection !== void 0) groupSelectorOptions.collection = item.collection;
|
|
834
|
+
if (item.currentGroup !== void 0) groupSelectorOptions.currentGroup = item.currentGroup;
|
|
835
|
+
if (item.buttonClass !== void 0) groupSelectorOptions.buttonClass = item.buttonClass;
|
|
836
|
+
if (item.buttonIcon !== void 0) groupSelectorOptions.buttonIcon = item.buttonIcon;
|
|
837
|
+
if (item.defaultText !== void 0) groupSelectorOptions.defaultText = item.defaultText;
|
|
838
|
+
if (item.itemTemplate !== void 0) groupSelectorOptions.itemTemplate = item.itemTemplate;
|
|
839
|
+
if (item.searchFields !== void 0) groupSelectorOptions.searchFields = item.searchFields;
|
|
840
|
+
if (item.headerText !== void 0) groupSelectorOptions.headerText = item.headerText;
|
|
841
|
+
if (item.searchPlaceholder !== void 0) groupSelectorOptions.searchPlaceholder = item.searchPlaceholder;
|
|
842
|
+
if (item.autoSetActiveGroup !== void 0) groupSelectorOptions.autoSetActiveGroup = item.autoSetActiveGroup;
|
|
843
|
+
if (item.onGroupSelected !== void 0) groupSelectorOptions.onGroupSelected = item.onGroupSelected;
|
|
844
|
+
const groupSelector = new GroupSelectorButton(groupSelectorOptions);
|
|
845
|
+
this.groupSelectorButton = groupSelector;
|
|
846
|
+
this.addChild(groupSelector);
|
|
847
|
+
} else if (processedItem.items && processedItem.items.length > 0) {
|
|
848
|
+
processedItem.isDropdown = true;
|
|
849
|
+
processedItem.isButton = false;
|
|
850
|
+
} else if (item.buttonClass) {
|
|
851
|
+
processedItem.isButton = true;
|
|
852
|
+
processedItem.isDropdown = false;
|
|
853
|
+
} else {
|
|
854
|
+
processedItem.isButton = false;
|
|
855
|
+
processedItem.isDropdown = false;
|
|
856
|
+
}
|
|
857
|
+
if (item.handler) {
|
|
858
|
+
this.rightItemHandlers = this.rightItemHandlers || /* @__PURE__ */ new Map();
|
|
859
|
+
this.rightItemHandlers.set(item.id, item.handler);
|
|
860
|
+
}
|
|
861
|
+
return processedItem;
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Setup listeners for page change events
|
|
866
|
+
*/
|
|
867
|
+
setupPageListeners() {
|
|
868
|
+
this.getApp().events.on(["page:show", "page:hide", "page:denied"], (data) => {
|
|
869
|
+
this.onPageChanged(data);
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Setup listeners for group change events
|
|
874
|
+
*/
|
|
875
|
+
setupGroupListeners() {
|
|
876
|
+
const app = this.getApp();
|
|
877
|
+
if (!app?.events) return;
|
|
878
|
+
app.events.on(["group:changed", "group:loaded"], (data) => {
|
|
879
|
+
if (data?.group) {
|
|
880
|
+
this.currentGroup = data.group;
|
|
881
|
+
}
|
|
882
|
+
if (this.displayMode === "group" || this.displayMode === "group_page_titles") {
|
|
883
|
+
if (this.mounted) {
|
|
884
|
+
this.render();
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Handle page before change event
|
|
891
|
+
* @param {object} data - Event data
|
|
892
|
+
*/
|
|
893
|
+
onPageBeforeChange(data) {
|
|
894
|
+
if (this.displayMode === "page" || this.displayMode === "both") ;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Handle page changed event
|
|
898
|
+
* @param {object} data - Event data with previousPage and currentPage
|
|
899
|
+
*/
|
|
900
|
+
onPageChanged(data) {
|
|
901
|
+
this.previousPage = this.currentPage;
|
|
902
|
+
this.currentPage = data.page;
|
|
903
|
+
if (this.displayMode === "page" || this.displayMode === "both") {
|
|
904
|
+
this.updatePageDisplay();
|
|
905
|
+
}
|
|
906
|
+
if (this.displayMode === "menu" || this.displayMode === "both") {
|
|
907
|
+
if (this.currentPage && this.currentPage.route) {
|
|
908
|
+
this.updateActiveItem(this.currentPage.route);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Update the display to show current page info
|
|
914
|
+
*/
|
|
915
|
+
updatePageDisplay() {
|
|
916
|
+
if (!this.currentPage) return;
|
|
917
|
+
if (this.mounted) {
|
|
918
|
+
this.render();
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
updateActiveItem(currentRoute) {
|
|
922
|
+
const normalizeRoute = (route) => {
|
|
923
|
+
if (!route) return "/";
|
|
924
|
+
return route.startsWith("/") ? route : `/${route}`;
|
|
925
|
+
};
|
|
926
|
+
const normalizedCurrentRoute = normalizeRoute(currentRoute);
|
|
927
|
+
const navItems = this.data.navItems.map((item) => {
|
|
928
|
+
const normalizedItemRoute = normalizeRoute(item.route);
|
|
929
|
+
let isActive = false;
|
|
930
|
+
if (normalizedItemRoute === "/" && normalizedCurrentRoute === "/") {
|
|
931
|
+
isActive = true;
|
|
932
|
+
} else if (normalizedItemRoute !== "/" && normalizedCurrentRoute !== "/") {
|
|
933
|
+
isActive = normalizedCurrentRoute.startsWith(normalizedItemRoute) || normalizedCurrentRoute === normalizedItemRoute;
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
...item,
|
|
937
|
+
active: isActive
|
|
938
|
+
};
|
|
939
|
+
});
|
|
940
|
+
this.updateData({ navItems }, true);
|
|
941
|
+
}
|
|
942
|
+
onPassThruActionProfile() {
|
|
943
|
+
this.getApp().events.emit("portal:action", { action: "profile" });
|
|
944
|
+
}
|
|
945
|
+
onActionSettings() {
|
|
946
|
+
this.getApp().events.emit("portal:action", { action: "settings" });
|
|
947
|
+
}
|
|
948
|
+
onActionLogout() {
|
|
949
|
+
this.getApp().events.emit("auth:logout", { action: "logout" });
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Handle open group selector action (from clicking group name in brand)
|
|
953
|
+
*/
|
|
954
|
+
async onActionOpenGroupSelector(event) {
|
|
955
|
+
if (this.groupSelectorButton) {
|
|
956
|
+
await this.groupSelectorButton.onActionShowSelector(event);
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
const { GroupList: GroupList2 } = await import("./ContextMenu-CyfbvpND.js").then((n) => n.k);
|
|
960
|
+
const tempSelector = new GroupSelectorButton({
|
|
961
|
+
Collection: GroupList2,
|
|
962
|
+
currentGroup: this.getApp()?.activeGroup
|
|
963
|
+
});
|
|
964
|
+
await tempSelector.onActionShowSelector(event);
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Handle dynamic action dispatch for right items
|
|
969
|
+
*/
|
|
970
|
+
async handleAction(actionName, event, element) {
|
|
971
|
+
const itemId = element.getAttribute("data-id");
|
|
972
|
+
if (itemId && this.rightItemHandlers && this.rightItemHandlers.has(itemId)) {
|
|
973
|
+
const handler = this.rightItemHandlers.get(itemId);
|
|
974
|
+
if (typeof handler === "function") {
|
|
975
|
+
return await handler.call(this, actionName, event, element);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
const methodName = `onAction${actionName.charAt(0).toUpperCase() + actionName.slice(1).replace(/-([a-z])/g, (g) => g[1].toUpperCase())}`;
|
|
979
|
+
if (typeof this[methodName] === "function") {
|
|
980
|
+
return await this[methodName](event, element);
|
|
981
|
+
}
|
|
982
|
+
this.emit("action", {
|
|
983
|
+
action: actionName,
|
|
984
|
+
event,
|
|
985
|
+
element,
|
|
986
|
+
topnav: this
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Handle default actions by searching through rightItems and navItems
|
|
991
|
+
*/
|
|
992
|
+
async onActionDefault(action, event, el) {
|
|
993
|
+
if (this.config.navItems) {
|
|
994
|
+
for (const item of this.config.navItems) {
|
|
995
|
+
if (item.action === action && item.handler) {
|
|
996
|
+
await item.handler.call(this, action, event, el);
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (this.config.rightItems) {
|
|
1002
|
+
for (const item of this.config.rightItems) {
|
|
1003
|
+
if (item.action === action && item.handler) {
|
|
1004
|
+
await item.handler.call(this, action, event, el);
|
|
1005
|
+
return true;
|
|
1006
|
+
}
|
|
1007
|
+
if (item.items) {
|
|
1008
|
+
for (const subItem of item.items) {
|
|
1009
|
+
if (subItem.action === action && subItem.handler) {
|
|
1010
|
+
await subItem.handler.call(this, action, event, el);
|
|
1011
|
+
return true;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
this.getApp().events.emit("portal:action", { action, event, el });
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Filter items by user permissions
|
|
1022
|
+
*/
|
|
1023
|
+
filterItemsByPermissions(items) {
|
|
1024
|
+
if (!items) return [];
|
|
1025
|
+
const app = this.getApp();
|
|
1026
|
+
const activeUser = app?.activeUser;
|
|
1027
|
+
return items.filter((item) => {
|
|
1028
|
+
if (item.permissions && activeUser) {
|
|
1029
|
+
return activeUser.hasPermission(item.permissions);
|
|
1030
|
+
}
|
|
1031
|
+
return true;
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
export {
|
|
1036
|
+
SimpleSearchView as S,
|
|
1037
|
+
TopNav as T
|
|
1038
|
+
};
|
|
1039
|
+
//# sourceMappingURL=TopNav-Bat8EzdF.js.map
|