@zolomedia/bifrost-client 1.7.74
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/L1_Foundation/L1_Foundation.js +13 -0
- package/L1_Foundation/bootstrap/bootstrap.js +11 -0
- package/L1_Foundation/bootstrap/bootstrap_hooks.js +123 -0
- package/L1_Foundation/bootstrap/bootstrap_index.js +15 -0
- package/L1_Foundation/bootstrap/bootstrap_logger.js +135 -0
- package/L1_Foundation/bootstrap/cdn_loader.js +217 -0
- package/L1_Foundation/bootstrap/module_registry.js +102 -0
- package/L1_Foundation/bootstrap/prism_loader.js +164 -0
- package/L1_Foundation/config/client_config.js +110 -0
- package/L1_Foundation/config/config.js +7 -0
- package/L1_Foundation/connection/connection.js +8 -0
- package/L1_Foundation/connection/websocket_connection.js +122 -0
- package/L1_Foundation/constants/bifrost_constants.js +284 -0
- package/L1_Foundation/constants/constants.js +7 -0
- package/L1_Foundation/logger/logger.js +10 -0
- package/L2_Handling/L2_Handling.js +15 -0
- package/L2_Handling/cache/cache.js +22 -0
- package/L2_Handling/cache/cache_constants.js +69 -0
- package/L2_Handling/cache/orchestration/cache_manager.js +299 -0
- package/L2_Handling/cache/orchestration/cache_orchestrator.js +260 -0
- package/L2_Handling/cache/orchestration/orchestration.js +12 -0
- package/L2_Handling/cache/storage/session_manager.js +289 -0
- package/L2_Handling/cache/storage/storage.js +10 -0
- package/L2_Handling/cache/storage/storage_manager.js +590 -0
- package/L2_Handling/display/composite/composite.js +13 -0
- package/L2_Handling/display/composite/dashboard_renderer.js +221 -0
- package/L2_Handling/display/composite/swiper_renderer.js +564 -0
- package/L2_Handling/display/composite/terminal_renderer.js +922 -0
- package/L2_Handling/display/composite/wizard_conditional_renderer.js +274 -0
- package/L2_Handling/display/display.js +30 -0
- package/L2_Handling/display/feedback/feedback.js +11 -0
- package/L2_Handling/display/feedback/progressbar_renderer.js +418 -0
- package/L2_Handling/display/feedback/spinner_renderer.js +246 -0
- package/L2_Handling/display/inputs/button_renderer.js +634 -0
- package/L2_Handling/display/inputs/form_renderer.js +583 -0
- package/L2_Handling/display/inputs/input_renderer.js +658 -0
- package/L2_Handling/display/inputs/inputs.js +12 -0
- package/L2_Handling/display/navigation/menu_renderer.js +206 -0
- package/L2_Handling/display/navigation/navigation.js +11 -0
- package/L2_Handling/display/navigation/navigation_renderer.js +703 -0
- package/L2_Handling/display/orchestration/orchestration.js +11 -0
- package/L2_Handling/display/orchestration/renderer.js +430 -0
- package/L2_Handling/display/orchestration/zdisplay_orchestrator.js +1759 -0
- package/L2_Handling/display/outputs/alert_renderer.js +161 -0
- package/L2_Handling/display/outputs/audio_renderer.js +94 -0
- package/L2_Handling/display/outputs/card_renderer.js +229 -0
- package/L2_Handling/display/outputs/code_renderer.js +66 -0
- package/L2_Handling/display/outputs/dl_renderer.js +131 -0
- package/L2_Handling/display/outputs/header_renderer.js +162 -0
- package/L2_Handling/display/outputs/icon_renderer.js +107 -0
- package/L2_Handling/display/outputs/image_renderer.js +145 -0
- package/L2_Handling/display/outputs/list_renderer.js +190 -0
- package/L2_Handling/display/outputs/outputs.js +19 -0
- package/L2_Handling/display/outputs/table_renderer.js +765 -0
- package/L2_Handling/display/outputs/text_renderer.js +818 -0
- package/L2_Handling/display/outputs/typography_renderer.js +293 -0
- package/L2_Handling/display/outputs/video_renderer.js +116 -0
- package/L2_Handling/display/primitives/document_structure_primitives.js +319 -0
- package/L2_Handling/display/primitives/form_primitives.js +526 -0
- package/L2_Handling/display/primitives/generic_containers.js +109 -0
- package/L2_Handling/display/primitives/interactive_primitives.js +305 -0
- package/L2_Handling/display/primitives/link_primitives.js +552 -0
- package/L2_Handling/display/primitives/lists_primitives.js +262 -0
- package/L2_Handling/display/primitives/media_primitives.js +383 -0
- package/L2_Handling/display/primitives/primitives.js +19 -0
- package/L2_Handling/display/primitives/semantic_element_primitive.js +226 -0
- package/L2_Handling/display/primitives/table_primitives.js +528 -0
- package/L2_Handling/display/primitives/typography_primitives.js +175 -0
- package/L2_Handling/display/specialized/input_request_renderer.js +467 -0
- package/L2_Handling/display/specialized/specialized.js +10 -0
- package/L2_Handling/hooks/hooks.js +9 -0
- package/L2_Handling/hooks/menu_integration.js +57 -0
- package/L2_Handling/hooks/widget_hook_manager.js +292 -0
- package/L2_Handling/message/message.js +8 -0
- package/L2_Handling/message/message_handler.js +701 -0
- package/L2_Handling/navigation/navigation.js +8 -0
- package/L2_Handling/navigation/navigation_manager.js +403 -0
- package/L2_Handling/zhooks/features/cache_live.js +287 -0
- package/L2_Handling/zhooks/features/crumbs_live.js +292 -0
- package/L2_Handling/zhooks/zhooks_manager.js +65 -0
- package/L2_Handling/zvaf/zvaf.js +8 -0
- package/L2_Handling/zvaf/zvaf_manager.js +334 -0
- package/L3_Abstraction/L3_Abstraction.js +12 -0
- package/L3_Abstraction/orchestrator/container_unwrapper.js +101 -0
- package/L3_Abstraction/orchestrator/group_renderer.js +698 -0
- package/L3_Abstraction/orchestrator/input_event_handler.js +797 -0
- package/L3_Abstraction/orchestrator/metadata_processor.js +249 -0
- package/L3_Abstraction/orchestrator/navbar_builder.js +201 -0
- package/L3_Abstraction/orchestrator/orchestrator.js +13 -0
- package/L3_Abstraction/orchestrator/wizard_gate_handler.js +360 -0
- package/L3_Abstraction/renderer/renderer.js +1 -0
- package/L3_Abstraction/session/session.js +1 -0
- package/L4_Orchestration/L4_Orchestration.js +11 -0
- package/L4_Orchestration/client/client.js +1 -0
- package/L4_Orchestration/facade/facade.js +9 -0
- package/L4_Orchestration/facade/manager_registry.js +118 -0
- package/L4_Orchestration/facade/renderer_registry.js +274 -0
- package/L4_Orchestration/lifecycle/asset_loader.js +255 -0
- package/L4_Orchestration/lifecycle/initializer.js +135 -0
- package/L4_Orchestration/lifecycle/lifecycle.js +8 -0
- package/L4_Orchestration/rendering/facade.js +94 -0
- package/L4_Orchestration/rendering/rendering.js +7 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/bifrost_client.js +204 -0
- package/bifrost_core.js +1686 -0
- package/docs/ARCHITECTURE.md +111 -0
- package/docs/PROTOCOL.md +106 -0
- package/docs/RENDERERS.md +101 -0
- package/docs/SECURITY.md +92 -0
- package/package.json +24 -0
- package/syntax/prism-zconfig.js +41 -0
- package/syntax/prism-zenv.js +69 -0
- package/syntax/prism-zolo-theme.css +288 -0
- package/syntax/prism-zolo.js +380 -0
- package/syntax/prism-zschema.js +38 -0
- package/syntax/prism-zspark.js +25 -0
- package/syntax/prism-zui.js +68 -0
- package/zSys/accessibility/accessibility.js +10 -0
- package/zSys/accessibility/emoji_accessibility.js +173 -0
- package/zSys/dom/block_utils.js +122 -0
- package/zSys/dom/container_utils.js +370 -0
- package/zSys/dom/dom.js +13 -0
- package/zSys/dom/dom_utils.js +328 -0
- package/zSys/dom/encoding_utils.js +117 -0
- package/zSys/dom/style_utils.js +71 -0
- package/zSys/errors/error_display.js +299 -0
- package/zSys/errors/errors.js +10 -0
- package/zSys/theme/color_utils.js +274 -0
- package/zSys/theme/dark_mode_utils.js +272 -0
- package/zSys/theme/size_utils.js +256 -0
- package/zSys/theme/spacing_utils.js +405 -0
- package/zSys/theme/theme.js +14 -0
- package/zSys/theme/zbase.css +1735 -0
- package/zSys/theme/zbase_inject.js +161 -0
- package/zSys/theme/ztheme_utils.js +305 -0
- package/zSys/validation/error_boundary.js +201 -0
- package/zSys/validation/validation.js +11 -0
- package/zSys/validation/validation_utils.js +238 -0
- package/zSys/zSys.js +14 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering Orchestration Module Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* High-level orchestrators for declarative rendering and zDisplay events.
|
|
5
|
+
*
|
|
6
|
+
* @module rendering/orchestration
|
|
7
|
+
* @layer 3 (Rendering Orchestration)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export * from './renderer.js';
|
|
11
|
+
export * from './zdisplay_orchestrator.js';
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Renderer Module - Auto-Rendering with zTheme
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class Renderer {
|
|
8
|
+
constructor(logger) {
|
|
9
|
+
this.logger = logger;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render data as a table with zTheme styling
|
|
14
|
+
*/
|
|
15
|
+
renderTable(data, container, _options = {}) {
|
|
16
|
+
const element = this._getElement(container);
|
|
17
|
+
if (!element) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
22
|
+
element.innerHTML = '<p class="zEmpty">No data to display</p>';
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const columns = Object.keys(data[0]);
|
|
27
|
+
let html = '<table class="zTable zTable-striped zTable-hover">';
|
|
28
|
+
|
|
29
|
+
// Table header
|
|
30
|
+
html += '<thead><tr>';
|
|
31
|
+
columns.forEach(col => {
|
|
32
|
+
html += `<th>${this._formatColumnName(col)}</th>`;
|
|
33
|
+
});
|
|
34
|
+
html += '</tr></thead>';
|
|
35
|
+
|
|
36
|
+
// Table body
|
|
37
|
+
html += '<tbody>';
|
|
38
|
+
data.forEach(row => {
|
|
39
|
+
html += '<tr>';
|
|
40
|
+
columns.forEach(col => {
|
|
41
|
+
html += `<td>${this._escapeHtml(row[col])}</td>`;
|
|
42
|
+
});
|
|
43
|
+
html += '</tr>';
|
|
44
|
+
});
|
|
45
|
+
html += '</tbody></table>';
|
|
46
|
+
|
|
47
|
+
element.innerHTML = html;
|
|
48
|
+
this.logger.log('Table rendered', { rows: data.length, columns: columns.length });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Render a menu with buttons
|
|
53
|
+
*/
|
|
54
|
+
renderMenu(items, container) {
|
|
55
|
+
const element = this._getElement(container);
|
|
56
|
+
if (!element) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let html = '<div class="zMenu">';
|
|
61
|
+
items.forEach((item, index) => {
|
|
62
|
+
const icon = item.icon || '';
|
|
63
|
+
const variant = item.variant || '';
|
|
64
|
+
html += `<button class="zoloButton ${variant}" data-action="${index}">
|
|
65
|
+
${icon} ${item.label}
|
|
66
|
+
</button>`;
|
|
67
|
+
});
|
|
68
|
+
html += '</div>';
|
|
69
|
+
|
|
70
|
+
element.innerHTML = html;
|
|
71
|
+
|
|
72
|
+
// Attach event listeners
|
|
73
|
+
element.querySelectorAll('button[data-action]').forEach((btn, index) => {
|
|
74
|
+
btn.addEventListener('click', () => {
|
|
75
|
+
if (items[index].action) {
|
|
76
|
+
items[index].action();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.logger.log('Menu rendered', { items: items.length });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Render a form with zTheme styling
|
|
86
|
+
*/
|
|
87
|
+
renderForm(fields, container, onSubmit) {
|
|
88
|
+
const element = this._getElement(container);
|
|
89
|
+
if (!element) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let html = '<form class="zForm">';
|
|
94
|
+
|
|
95
|
+
fields.forEach(field => {
|
|
96
|
+
html += '<div class="zForm-group">';
|
|
97
|
+
html += `<label for="${field.name}">${field.label}</label>`;
|
|
98
|
+
|
|
99
|
+
if (field.type === 'textarea') {
|
|
100
|
+
html += `<textarea id="${field.name}" name="${field.name}"
|
|
101
|
+
class="zInput" ${field.required ? 'required' : ''}
|
|
102
|
+
placeholder="${field.placeholder || ''}"></textarea>`;
|
|
103
|
+
} else {
|
|
104
|
+
html += `<input type="${field.type || 'text'}" id="${field.name}"
|
|
105
|
+
name="${field.name}" class="zInput"
|
|
106
|
+
${field.required ? 'required' : ''}
|
|
107
|
+
placeholder="${field.placeholder || ''}">`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
html += '</div>';
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
html += '<button type="submit" class="zoloButton zBtn-primary">Submit</button>';
|
|
114
|
+
html += '</form>';
|
|
115
|
+
|
|
116
|
+
element.innerHTML = html;
|
|
117
|
+
|
|
118
|
+
// Attach submit handler
|
|
119
|
+
const form = element.querySelector('form');
|
|
120
|
+
form.addEventListener('submit', (e) => {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
const formData = new FormData(form);
|
|
123
|
+
const data = Object.fromEntries(formData.entries());
|
|
124
|
+
if (onSubmit) {
|
|
125
|
+
onSubmit(data);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.logger.log('Form rendered', { fields: fields.length });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Render a message/alert
|
|
134
|
+
* @param {number} duration - Auto-hide duration in ms (default: 5000 = TIMEOUTS.AUTO_DISMISS_SHORT)
|
|
135
|
+
*/
|
|
136
|
+
renderMessage(text, type = 'info', container, duration = 5000) { // TIMEOUTS.AUTO_DISMISS_SHORT
|
|
137
|
+
const element = this._getElement(container);
|
|
138
|
+
if (!element) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const typeClass = {
|
|
143
|
+
'success': 'zAlert-success',
|
|
144
|
+
'error': 'zAlert-danger',
|
|
145
|
+
'warning': 'zAlert-warning',
|
|
146
|
+
'info': 'zAlert-info'
|
|
147
|
+
}[type] || 'zAlert-info';
|
|
148
|
+
|
|
149
|
+
const msgDiv = document.createElement('div');
|
|
150
|
+
msgDiv.className = `zAlert ${typeClass}`;
|
|
151
|
+
msgDiv.textContent = text;
|
|
152
|
+
|
|
153
|
+
element.insertBefore(msgDiv, element.firstChild);
|
|
154
|
+
|
|
155
|
+
if (duration > 0) {
|
|
156
|
+
setTimeout(() => msgDiv.remove(), duration);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.logger.log('Message rendered', { type, text });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Render a progress bar
|
|
164
|
+
* @param {string} progressId - Unique ID for this progress bar
|
|
165
|
+
* @param {number} current - Current progress value
|
|
166
|
+
* @param {number} total - Total value (null for indeterminate)
|
|
167
|
+
* @param {object} options - { label, color, showPercentage, showETA, size }
|
|
168
|
+
* @param {string|Element} container - Container selector or element
|
|
169
|
+
*/
|
|
170
|
+
renderProgressBar(progressId, current, total, options = {}, container) {
|
|
171
|
+
const element = this._getElement(container);
|
|
172
|
+
if (!element) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const {
|
|
177
|
+
label = 'Processing',
|
|
178
|
+
color = '',
|
|
179
|
+
showPercentage = true,
|
|
180
|
+
showETA = false,
|
|
181
|
+
size = '',
|
|
182
|
+
eta = null
|
|
183
|
+
} = options;
|
|
184
|
+
|
|
185
|
+
// Calculate percentage
|
|
186
|
+
const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
|
|
187
|
+
const isIndeterminate = total === null || total === 0;
|
|
188
|
+
|
|
189
|
+
// Build classes
|
|
190
|
+
const classes = ['zProgress'];
|
|
191
|
+
if (color) {
|
|
192
|
+
classes.push(`zProgress-${color}`);
|
|
193
|
+
}
|
|
194
|
+
if (size) {
|
|
195
|
+
classes.push(`zProgress-${size}`);
|
|
196
|
+
}
|
|
197
|
+
if (isIndeterminate) {
|
|
198
|
+
classes.push('zProgress-indeterminate');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if progress bar already exists
|
|
202
|
+
let progressDiv = element.querySelector(`[data-progress-id="${progressId}"]`);
|
|
203
|
+
|
|
204
|
+
if (!progressDiv) {
|
|
205
|
+
// Create new progress bar
|
|
206
|
+
progressDiv = document.createElement('div');
|
|
207
|
+
progressDiv.className = classes.join(' ');
|
|
208
|
+
progressDiv.setAttribute('data-progress-id', progressId);
|
|
209
|
+
|
|
210
|
+
let html = '<div class="zProgress-label">';
|
|
211
|
+
html += `<span>${this._escapeHtml(label)}</span>`;
|
|
212
|
+
html += '<span class="zProgress-stats"></span>';
|
|
213
|
+
html += '</div>';
|
|
214
|
+
html += '<div class="zProgress-track">';
|
|
215
|
+
html += '<div class="zProgress-fill"></div>';
|
|
216
|
+
html += '</div>';
|
|
217
|
+
|
|
218
|
+
progressDiv.innerHTML = html;
|
|
219
|
+
element.appendChild(progressDiv);
|
|
220
|
+
} else {
|
|
221
|
+
// Update existing progress bar classes
|
|
222
|
+
progressDiv.className = classes.join(' ');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Update stats
|
|
226
|
+
const statsEl = progressDiv.querySelector('.zProgress-stats');
|
|
227
|
+
let statsText = '';
|
|
228
|
+
if (showPercentage && !isIndeterminate) {
|
|
229
|
+
statsText += `${percentage}%`;
|
|
230
|
+
}
|
|
231
|
+
if (showETA && eta) {
|
|
232
|
+
statsText += statsText ? ` • ETA: ${eta}` : `ETA: ${eta}`;
|
|
233
|
+
}
|
|
234
|
+
statsEl.textContent = statsText;
|
|
235
|
+
|
|
236
|
+
// Update fill
|
|
237
|
+
const fillEl = progressDiv.querySelector('.zProgress-fill');
|
|
238
|
+
if (isIndeterminate) {
|
|
239
|
+
fillEl.style.width = '100%';
|
|
240
|
+
} else {
|
|
241
|
+
fillEl.style.width = `${percentage}%`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this.logger.log('Progress bar rendered', { progressId, current, total, percentage });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Update an existing progress bar
|
|
249
|
+
* @param {string} progressId - Unique ID of the progress bar
|
|
250
|
+
* @param {number} current - New current value
|
|
251
|
+
* @param {object} options - Optional updates { eta }
|
|
252
|
+
* @param {string|Element} container - Container selector or element
|
|
253
|
+
*/
|
|
254
|
+
updateProgress(progressId, current, options = {}, container) {
|
|
255
|
+
const element = this._getElement(container);
|
|
256
|
+
if (!element) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const progressDiv = element.querySelector(`[data-progress-id="${progressId}"]`);
|
|
261
|
+
if (!progressDiv) {
|
|
262
|
+
this.logger.log('[ERROR] Progress bar not found:', progressId);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Get total from data attribute or calculate from width
|
|
267
|
+
const fillEl = progressDiv.querySelector('.zProgress-fill');
|
|
268
|
+
const isIndeterminate = progressDiv.classList.contains('zProgress-indeterminate');
|
|
269
|
+
|
|
270
|
+
if (!isIndeterminate) {
|
|
271
|
+
// Try to extract total from current percentage
|
|
272
|
+
const currentWidth = parseFloat(fillEl.style.width) || 0;
|
|
273
|
+
const total = currentWidth > 0 ? Math.round((current * 100) / currentWidth) : 100;
|
|
274
|
+
|
|
275
|
+
const percentage = Math.round((current / total) * 100);
|
|
276
|
+
fillEl.style.width = `${percentage}%`;
|
|
277
|
+
|
|
278
|
+
// Update stats
|
|
279
|
+
const statsEl = progressDiv.querySelector('.zProgress-stats');
|
|
280
|
+
let statsText = `${percentage}%`;
|
|
281
|
+
if (options.eta) {
|
|
282
|
+
statsText += ` • ETA: ${options.eta}`;
|
|
283
|
+
}
|
|
284
|
+
statsEl.textContent = statsText;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.logger.log('Progress updated', { progressId, current });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Remove a progress bar
|
|
292
|
+
* @param {string} progressId - Unique ID of the progress bar
|
|
293
|
+
* @param {string|Element} container - Container selector or element
|
|
294
|
+
*/
|
|
295
|
+
removeProgress(progressId, container) {
|
|
296
|
+
const element = this._getElement(container);
|
|
297
|
+
if (!element) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const progressDiv = element.querySelector(`[data-progress-id="${progressId}"]`);
|
|
302
|
+
if (progressDiv) {
|
|
303
|
+
progressDiv.remove();
|
|
304
|
+
this.logger.log('Progress bar removed', { progressId });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Render a spinner
|
|
310
|
+
* @param {string} spinnerId - Unique ID for this spinner
|
|
311
|
+
* @param {string} label - Spinner label text
|
|
312
|
+
* @param {string} style - Spinner style (dots, line, arc, arrow, bouncingBall, simple, circle)
|
|
313
|
+
* @param {object} options - { size, center, inline }
|
|
314
|
+
* @param {string|Element} container - Container selector or element
|
|
315
|
+
*/
|
|
316
|
+
renderSpinner(spinnerId, label, style = 'dots', options = {}, container) {
|
|
317
|
+
const element = this._getElement(container);
|
|
318
|
+
if (!element) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const {
|
|
323
|
+
size = '',
|
|
324
|
+
center = false,
|
|
325
|
+
inline = false
|
|
326
|
+
} = options;
|
|
327
|
+
|
|
328
|
+
// Build classes
|
|
329
|
+
const classes = ['zSpinner'];
|
|
330
|
+
if (size) {
|
|
331
|
+
classes.push(`zSpinner-${size}`);
|
|
332
|
+
}
|
|
333
|
+
if (center) {
|
|
334
|
+
classes.push('zSpinner-center');
|
|
335
|
+
}
|
|
336
|
+
if (inline) {
|
|
337
|
+
classes.push('zSpinner-inline');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Check if spinner already exists
|
|
341
|
+
let spinnerDiv = element.querySelector(`[data-spinner-id="${spinnerId}"]`);
|
|
342
|
+
|
|
343
|
+
if (!spinnerDiv) {
|
|
344
|
+
// Create new spinner
|
|
345
|
+
spinnerDiv = document.createElement('div');
|
|
346
|
+
spinnerDiv.className = classes.join(' ');
|
|
347
|
+
spinnerDiv.setAttribute('data-spinner-id', spinnerId);
|
|
348
|
+
|
|
349
|
+
let html = '';
|
|
350
|
+
if (label) {
|
|
351
|
+
html += `<div class="zSpinner-label">${this._escapeHtml(label)}</div>`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Add animation based on style
|
|
355
|
+
if (style === 'circle') {
|
|
356
|
+
html += '<div class="zSpinner-animation"><div class="zSpinner-circle"></div></div>';
|
|
357
|
+
} else {
|
|
358
|
+
html += `<div class="zSpinner-animation zSpinner-${style}"></div>`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
spinnerDiv.innerHTML = html;
|
|
362
|
+
element.appendChild(spinnerDiv);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.logger.log('Spinner rendered', { spinnerId, label, style });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Remove a spinner
|
|
370
|
+
* @param {string} spinnerId - Unique ID of the spinner
|
|
371
|
+
* @param {string|Element} container - Container selector or element
|
|
372
|
+
*/
|
|
373
|
+
removeSpinner(spinnerId, container) {
|
|
374
|
+
const element = this._getElement(container);
|
|
375
|
+
if (!element) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const spinnerDiv = element.querySelector(`[data-spinner-id="${spinnerId}"]`);
|
|
380
|
+
if (spinnerDiv) {
|
|
381
|
+
spinnerDiv.remove();
|
|
382
|
+
this.logger.log('Spinner removed', { spinnerId });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Get element from selector or element
|
|
388
|
+
* @private
|
|
389
|
+
*/
|
|
390
|
+
_getElement(container) {
|
|
391
|
+
const element = typeof container === 'string'
|
|
392
|
+
? document.querySelector(container)
|
|
393
|
+
: container;
|
|
394
|
+
|
|
395
|
+
if (!element) {
|
|
396
|
+
this.logger.log('[ERROR] Container not found:', container);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return element;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Format column name for display
|
|
404
|
+
* @private
|
|
405
|
+
*/
|
|
406
|
+
_formatColumnName(name) {
|
|
407
|
+
return name
|
|
408
|
+
.replace(/_/g, ' ')
|
|
409
|
+
.replace(/\b\w/g, l => l.toUpperCase());
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Escape HTML to prevent XSS
|
|
414
|
+
* @private
|
|
415
|
+
*/
|
|
416
|
+
_escapeHtml(text) {
|
|
417
|
+
if (text === null || text === undefined) {
|
|
418
|
+
return '';
|
|
419
|
+
}
|
|
420
|
+
const map = {
|
|
421
|
+
'&': '&',
|
|
422
|
+
'<': '<',
|
|
423
|
+
'>': '>',
|
|
424
|
+
'"': '"',
|
|
425
|
+
"'": '''
|
|
426
|
+
};
|
|
427
|
+
return String(text).replace(/[&<>"']/g, m => map[m]);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|