@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,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Request Renderer - Interactive input forms for user input
|
|
3
|
+
*
|
|
4
|
+
* Renders interactive input request forms from backend:
|
|
5
|
+
* - Text input (string, password)
|
|
6
|
+
* - Selection (radio/checkbox)
|
|
7
|
+
* - Button confirmation
|
|
8
|
+
*
|
|
9
|
+
* Extracted from zdisplay_renderer.js (Phase 4)
|
|
10
|
+
*
|
|
11
|
+
* @module rendering/specialized/input_request_renderer
|
|
12
|
+
* @layer 3 (Specialized Rendering)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { TYPOGRAPHY } from '../../../L1_Foundation/constants/bifrost_constants.js';
|
|
16
|
+
|
|
17
|
+
export class InputRequestRenderer {
|
|
18
|
+
constructor(logger = null, defaultZone = 'zVaF-content') {
|
|
19
|
+
this.logger = logger || console;
|
|
20
|
+
this.defaultZone = defaultZone;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Render input request as HTML form
|
|
25
|
+
* @param {Object} inputRequest - Input request event from backend
|
|
26
|
+
* @param {string} targetZone - Target DOM element ID
|
|
27
|
+
*/
|
|
28
|
+
renderInputRequest(inputRequest, targetZone = null) {
|
|
29
|
+
const zone = targetZone || this.defaultZone;
|
|
30
|
+
const container = document.getElementById(zone);
|
|
31
|
+
|
|
32
|
+
if (!container) {
|
|
33
|
+
this.logger.error(`[InputRequestRenderer] Cannot render input - zone not found: ${zone}`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Extract input details
|
|
38
|
+
const requestId = inputRequest.requestId || inputRequest.data?.requestId;
|
|
39
|
+
const inputType = inputRequest.type || inputRequest.data?.type || 'string';
|
|
40
|
+
const prompt = inputRequest.prompt || inputRequest.data?.prompt || 'Enter input:';
|
|
41
|
+
const masked = inputRequest.masked || inputRequest.data?.masked || (inputType === 'password');
|
|
42
|
+
|
|
43
|
+
this.logger.log('[InputRequestRenderer] Rendering input form:', { requestId, inputType, prompt, masked });
|
|
44
|
+
|
|
45
|
+
// Create form container
|
|
46
|
+
const form = document.createElement('form');
|
|
47
|
+
form.className = 'zInputForm';
|
|
48
|
+
form.style.cssText = `
|
|
49
|
+
margin: 1rem 0;
|
|
50
|
+
padding: 1rem;
|
|
51
|
+
border: 2px solid var(--color-primary, #00D4FF);
|
|
52
|
+
border-radius: 8px;
|
|
53
|
+
background-color: var(--color-base, #fff);
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
// Create label
|
|
57
|
+
const label = document.createElement('label');
|
|
58
|
+
label.textContent = prompt;
|
|
59
|
+
label.style.cssText = `
|
|
60
|
+
display: block;
|
|
61
|
+
margin-bottom: 0.5rem;
|
|
62
|
+
font-weight: bold;
|
|
63
|
+
color: var(--color-darkgray, #333);
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
// Create input field
|
|
67
|
+
const input = document.createElement('input');
|
|
68
|
+
input.type = masked ? 'password' : 'text';
|
|
69
|
+
input.placeholder = masked ? '••••••••' : 'Type here...';
|
|
70
|
+
input.required = true;
|
|
71
|
+
input.style.cssText = `
|
|
72
|
+
width: 100%;
|
|
73
|
+
padding: 0.5rem;
|
|
74
|
+
margin-bottom: 1rem;
|
|
75
|
+
border: 1px solid var(--color-gray, #ccc);
|
|
76
|
+
border-radius: 4px;
|
|
77
|
+
font-size: 1rem;
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
// Create submit button
|
|
81
|
+
const submitBtn = document.createElement('button');
|
|
82
|
+
submitBtn.type = 'submit';
|
|
83
|
+
submitBtn.textContent = '[ok] Submit';
|
|
84
|
+
submitBtn.className = 'zoloButton zBtnPrimary';
|
|
85
|
+
submitBtn.style.cssText = `
|
|
86
|
+
padding: 0.5rem 1.5rem;
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
// Handle form submission
|
|
91
|
+
form.addEventListener('submit', (e) => {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
const value = input.value.trim();
|
|
94
|
+
|
|
95
|
+
this.logger.log('[InputRequestRenderer] Input submitted:', { requestId, value: masked ? '***' : value });
|
|
96
|
+
|
|
97
|
+
// Send input_response back to server (one-way, no response expected)
|
|
98
|
+
if (window.bifrostClient && window.bifrostClient.connection) {
|
|
99
|
+
try {
|
|
100
|
+
const payload = {
|
|
101
|
+
event: 'input_response',
|
|
102
|
+
requestId: requestId,
|
|
103
|
+
value: value
|
|
104
|
+
};
|
|
105
|
+
this.logger.log('[InputRequestRenderer] Sending input_response:', payload);
|
|
106
|
+
window.bifrostClient.connection.send(JSON.stringify(payload));
|
|
107
|
+
this.logger.log('[InputRequestRenderer] Input response sent successfully (one-way)');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.logger.error('[InputRequestRenderer] [ERROR] Failed to send input response:', error);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
this.logger.error('[InputRequestRenderer] Cannot send input response - bifrostClient not found on window');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Replace form with confirmation message
|
|
116
|
+
const confirmation = document.createElement('p');
|
|
117
|
+
confirmation.style.cssText = `
|
|
118
|
+
margin: 1rem 0;
|
|
119
|
+
padding: 0.75rem;
|
|
120
|
+
background-color: var(--color-success-light, #d4edda);
|
|
121
|
+
border: 1px solid var(--color-success, #28a745);
|
|
122
|
+
border-radius: 4px;
|
|
123
|
+
color: var(--color-success-dark, #155724);
|
|
124
|
+
`;
|
|
125
|
+
confirmation.textContent = masked
|
|
126
|
+
? `[ok] Password submitted (${value.length} characters)`
|
|
127
|
+
: `[ok] Submitted: ${value}`;
|
|
128
|
+
|
|
129
|
+
form.replaceWith(confirmation);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Assemble form
|
|
133
|
+
form.appendChild(label);
|
|
134
|
+
form.appendChild(input);
|
|
135
|
+
form.appendChild(submitBtn);
|
|
136
|
+
|
|
137
|
+
// Add to container
|
|
138
|
+
container.appendChild(form);
|
|
139
|
+
|
|
140
|
+
// Focus input
|
|
141
|
+
input.focus();
|
|
142
|
+
|
|
143
|
+
this.logger.log('[InputRequestRenderer] Input form rendered');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Render selection request as HTML form with radio/checkboxes
|
|
148
|
+
* @param {Object} selectionRequest - Selection request event from backend
|
|
149
|
+
* @param {string} targetZone - Target DOM element ID
|
|
150
|
+
*/
|
|
151
|
+
renderSelectionRequest(selectionRequest, targetZone = null) {
|
|
152
|
+
const zone = targetZone || this.defaultZone;
|
|
153
|
+
const container = document.getElementById(zone);
|
|
154
|
+
|
|
155
|
+
if (!container) {
|
|
156
|
+
this.logger.error(`[InputRequestRenderer] Cannot render selection - zone not found: ${zone}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Extract selection details
|
|
161
|
+
const requestId = selectionRequest.requestId || selectionRequest.data?.requestId;
|
|
162
|
+
const prompt = selectionRequest.prompt || selectionRequest.data?.prompt || 'Select:';
|
|
163
|
+
const options = selectionRequest.options || selectionRequest.data?.options || [];
|
|
164
|
+
const multi = selectionRequest.multi || selectionRequest.data?.multi || false;
|
|
165
|
+
const defaultVal = selectionRequest.default || selectionRequest.data?.default;
|
|
166
|
+
|
|
167
|
+
this.logger.log('[InputRequestRenderer] Rendering selection form:', { requestId, prompt, options, multi });
|
|
168
|
+
|
|
169
|
+
// Create form container
|
|
170
|
+
const form = document.createElement('form');
|
|
171
|
+
form.className = 'zSelectionForm';
|
|
172
|
+
form.style.cssText = `
|
|
173
|
+
margin: 1rem 0;
|
|
174
|
+
padding: 1rem;
|
|
175
|
+
border: 2px solid var(--color-primary, #00D4FF);
|
|
176
|
+
border-radius: 8px;
|
|
177
|
+
background-color: var(--color-base, #fff);
|
|
178
|
+
`;
|
|
179
|
+
|
|
180
|
+
// Create label
|
|
181
|
+
const label = document.createElement('label');
|
|
182
|
+
label.textContent = prompt;
|
|
183
|
+
label.style.cssText = `
|
|
184
|
+
display: block;
|
|
185
|
+
margin-bottom: 1rem;
|
|
186
|
+
font-weight: bold;
|
|
187
|
+
color: var(--color-darkgray, #333);
|
|
188
|
+
`;
|
|
189
|
+
form.appendChild(label);
|
|
190
|
+
|
|
191
|
+
// Create options container
|
|
192
|
+
const optionsContainer = document.createElement('div');
|
|
193
|
+
optionsContainer.style.cssText = `
|
|
194
|
+
margin-bottom: 1rem;
|
|
195
|
+
max-height: 300px;
|
|
196
|
+
overflow-y: auto;
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
// Create option elements
|
|
200
|
+
const inputType = multi ? 'checkbox' : 'radio';
|
|
201
|
+
const inputName = `selection_${requestId}`;
|
|
202
|
+
|
|
203
|
+
options.forEach((option, index) => {
|
|
204
|
+
const optionDiv = document.createElement('div');
|
|
205
|
+
optionDiv.style.cssText = `
|
|
206
|
+
padding: 0.5rem;
|
|
207
|
+
margin: 0.25rem 0;
|
|
208
|
+
border-radius: 4px;
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
display: flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
`;
|
|
213
|
+
optionDiv.onmouseover = () => optionDiv.style.backgroundColor = 'var(--color-lightgray, #f8f9fa)';
|
|
214
|
+
optionDiv.onmouseout = () => optionDiv.style.backgroundColor = 'transparent';
|
|
215
|
+
|
|
216
|
+
const input = document.createElement('input');
|
|
217
|
+
input.type = inputType;
|
|
218
|
+
input.name = inputName;
|
|
219
|
+
input.value = option;
|
|
220
|
+
input.id = `${inputName}_${index}`;
|
|
221
|
+
input.style.cssText = `
|
|
222
|
+
margin-right: 0.5rem;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
// Set default selection
|
|
227
|
+
if (defaultVal) {
|
|
228
|
+
if (multi && Array.isArray(defaultVal)) {
|
|
229
|
+
input.checked = defaultVal.includes(option);
|
|
230
|
+
} else if (!multi && defaultVal === option) {
|
|
231
|
+
input.checked = true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const optionLabel = document.createElement('label');
|
|
236
|
+
optionLabel.htmlFor = input.id;
|
|
237
|
+
optionLabel.textContent = option;
|
|
238
|
+
optionLabel.style.cssText = `
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
flex: 1;
|
|
241
|
+
`;
|
|
242
|
+
|
|
243
|
+
optionDiv.appendChild(input);
|
|
244
|
+
optionDiv.appendChild(optionLabel);
|
|
245
|
+
optionDiv.onclick = () => input.checked = !input.checked;
|
|
246
|
+
|
|
247
|
+
optionsContainer.appendChild(optionDiv);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
form.appendChild(optionsContainer);
|
|
251
|
+
|
|
252
|
+
// Create submit button
|
|
253
|
+
const submitBtn = document.createElement('button');
|
|
254
|
+
submitBtn.type = 'submit';
|
|
255
|
+
submitBtn.textContent = '[ok] Submit';
|
|
256
|
+
submitBtn.className = 'zoloButton zBtnPrimary';
|
|
257
|
+
submitBtn.style.cssText = `
|
|
258
|
+
padding: 0.5rem 1.5rem;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
`;
|
|
261
|
+
form.appendChild(submitBtn);
|
|
262
|
+
|
|
263
|
+
// Handle form submission
|
|
264
|
+
form.addEventListener('submit', (e) => {
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
|
|
267
|
+
// Get selected values
|
|
268
|
+
const selectedInputs = form.querySelectorAll(`input[name="${inputName}"]:checked`);
|
|
269
|
+
const selectedValues = Array.from(selectedInputs).map(input => input.value);
|
|
270
|
+
|
|
271
|
+
// Return appropriate format
|
|
272
|
+
const value = multi ? selectedValues : (selectedValues[0] || null);
|
|
273
|
+
|
|
274
|
+
this.logger.log('[InputRequestRenderer] Selection submitted:', { requestId, value });
|
|
275
|
+
|
|
276
|
+
// Send selection_response back to server (one-way)
|
|
277
|
+
if (window.bifrostClient && window.bifrostClient.connection) {
|
|
278
|
+
try {
|
|
279
|
+
const payload = {
|
|
280
|
+
event: 'input_response',
|
|
281
|
+
requestId: requestId,
|
|
282
|
+
value: value
|
|
283
|
+
};
|
|
284
|
+
this.logger.log('[InputRequestRenderer] Sending selection response:', payload);
|
|
285
|
+
window.bifrostClient.connection.send(JSON.stringify(payload));
|
|
286
|
+
this.logger.log('[InputRequestRenderer] Selection response sent successfully');
|
|
287
|
+
} catch (error) {
|
|
288
|
+
this.logger.error('[InputRequestRenderer] [ERROR] Failed to send selection response:', error);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
this.logger.error('[InputRequestRenderer] Cannot send selection response - bifrostClient not found');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Replace form with confirmation message
|
|
295
|
+
const confirmation = document.createElement('p');
|
|
296
|
+
confirmation.style.cssText = `
|
|
297
|
+
margin: 1rem 0;
|
|
298
|
+
padding: 0.75rem;
|
|
299
|
+
background-color: var(--color-success-light, #d4edda);
|
|
300
|
+
border: 1px solid var(--color-success, #28a745);
|
|
301
|
+
border-radius: 4px;
|
|
302
|
+
color: var(--color-success-dark, #155724);
|
|
303
|
+
`;
|
|
304
|
+
|
|
305
|
+
if (multi) {
|
|
306
|
+
confirmation.textContent = selectedValues.length > 0
|
|
307
|
+
? `[ok] Selected: ${selectedValues.join(', ')}`
|
|
308
|
+
: '[ok] No selections made';
|
|
309
|
+
} else {
|
|
310
|
+
confirmation.textContent = value ? `[ok] Selected: ${value}` : '[ok] No selection made';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
form.replaceWith(confirmation);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Add to container
|
|
317
|
+
container.appendChild(form);
|
|
318
|
+
|
|
319
|
+
this.logger.log('[InputRequestRenderer] Selection form rendered');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Render button request as interactive confirmation button
|
|
324
|
+
* @param {Object} buttonRequest - Button request event from backend
|
|
325
|
+
* @param {string} targetZone - Target DOM element ID
|
|
326
|
+
*/
|
|
327
|
+
renderButtonRequest(buttonRequest, targetZone = null) {
|
|
328
|
+
const zone = targetZone || this.defaultZone;
|
|
329
|
+
const container = document.getElementById(zone);
|
|
330
|
+
|
|
331
|
+
if (!container) {
|
|
332
|
+
this.logger.error(`[InputRequestRenderer] Cannot render button - zone not found: ${zone}`);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Extract button details
|
|
337
|
+
const requestId = buttonRequest.requestId || buttonRequest.data?.requestId;
|
|
338
|
+
const label = buttonRequest.prompt || buttonRequest.data?.prompt || 'Click Me';
|
|
339
|
+
const action = buttonRequest.action || buttonRequest.data?.action || null;
|
|
340
|
+
const color = buttonRequest.color || buttonRequest.data?.color || 'primary';
|
|
341
|
+
const style = buttonRequest.style || buttonRequest.data?.style || 'default';
|
|
342
|
+
|
|
343
|
+
this.logger.log('[InputRequestRenderer] Rendering button:', { requestId, label, action, color, style });
|
|
344
|
+
|
|
345
|
+
// Create button container
|
|
346
|
+
const buttonContainer = document.createElement('div');
|
|
347
|
+
buttonContainer.className = 'zButtonContainer';
|
|
348
|
+
buttonContainer.style.cssText = `
|
|
349
|
+
margin: 1rem 0;
|
|
350
|
+
padding: 1rem;
|
|
351
|
+
display: flex;
|
|
352
|
+
align-items: center;
|
|
353
|
+
gap: 1rem;
|
|
354
|
+
`;
|
|
355
|
+
|
|
356
|
+
// Create the button with zTheme classes
|
|
357
|
+
const button = document.createElement('button');
|
|
358
|
+
button.type = 'button';
|
|
359
|
+
button.textContent = label;
|
|
360
|
+
|
|
361
|
+
// Apply zTheme button classes based on color
|
|
362
|
+
const colorClass = {
|
|
363
|
+
'primary': 'zBtnPrimary',
|
|
364
|
+
'success': 'zBtnSuccess',
|
|
365
|
+
'danger': 'zBtnDanger',
|
|
366
|
+
'warning': 'zBtnWarning',
|
|
367
|
+
'info': 'zBtnInfo',
|
|
368
|
+
'secondary': 'zBtnSecondary'
|
|
369
|
+
}[color] || 'zBtnPrimary';
|
|
370
|
+
|
|
371
|
+
button.className = `zoloButton ${colorClass}`;
|
|
372
|
+
button.style.cssText = `
|
|
373
|
+
padding: 0.5rem 1.5rem;
|
|
374
|
+
font-size: 1rem;
|
|
375
|
+
cursor: pointer;
|
|
376
|
+
transition: transform 0.1s ease;
|
|
377
|
+
`;
|
|
378
|
+
|
|
379
|
+
// Add hover effect
|
|
380
|
+
button.addEventListener('mouseenter', () => {
|
|
381
|
+
button.style.transform = 'scale(1.02)';
|
|
382
|
+
});
|
|
383
|
+
button.addEventListener('mouseleave', () => {
|
|
384
|
+
button.style.transform = 'scale(1)';
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Handle button click
|
|
388
|
+
button.addEventListener('click', () => {
|
|
389
|
+
this.logger.log('[InputRequestRenderer] Button clicked:', label);
|
|
390
|
+
|
|
391
|
+
// Send response back to server (True = clicked)
|
|
392
|
+
if (window.bifrostClient && window.bifrostClient.connection) {
|
|
393
|
+
window.bifrostClient.connection.send(JSON.stringify({
|
|
394
|
+
event: 'input_response',
|
|
395
|
+
requestId: requestId,
|
|
396
|
+
value: true // Button clicked = True
|
|
397
|
+
}));
|
|
398
|
+
this.logger.log('[InputRequestRenderer] Button response sent');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Replace button with confirmation
|
|
402
|
+
const confirmation = document.createElement('p');
|
|
403
|
+
confirmation.style.cssText = `
|
|
404
|
+
margin: 0;
|
|
405
|
+
padding: 0.5rem 1rem;
|
|
406
|
+
color: var(--color-success, #10b981);
|
|
407
|
+
font-weight: ${TYPOGRAPHY.FONT_WEIGHTS.MEDIUM};
|
|
408
|
+
`;
|
|
409
|
+
confirmation.textContent = `[ok] ${label} clicked!`;
|
|
410
|
+
|
|
411
|
+
buttonContainer.replaceWith(confirmation);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Add cancel button (optional - for explicit "No" response)
|
|
415
|
+
const cancelBtn = document.createElement('button');
|
|
416
|
+
cancelBtn.type = 'button';
|
|
417
|
+
cancelBtn.textContent = 'Cancel';
|
|
418
|
+
cancelBtn.className = 'zoloButton zBtnSecondary';
|
|
419
|
+
cancelBtn.style.cssText = `
|
|
420
|
+
padding: 0.5rem 1.5rem;
|
|
421
|
+
font-size: 1rem;
|
|
422
|
+
cursor: pointer;
|
|
423
|
+
transition: transform 0.1s ease;
|
|
424
|
+
`;
|
|
425
|
+
|
|
426
|
+
cancelBtn.addEventListener('mouseenter', () => {
|
|
427
|
+
cancelBtn.style.transform = 'scale(1.02)';
|
|
428
|
+
});
|
|
429
|
+
cancelBtn.addEventListener('mouseleave', () => {
|
|
430
|
+
cancelBtn.style.transform = 'scale(1)';
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
cancelBtn.addEventListener('click', () => {
|
|
434
|
+
this.logger.log('[InputRequestRenderer] Button cancelled');
|
|
435
|
+
|
|
436
|
+
// Send False response
|
|
437
|
+
if (window.bifrostClient && window.bifrostClient.connection) {
|
|
438
|
+
window.bifrostClient.connection.send(JSON.stringify({
|
|
439
|
+
event: 'input_response',
|
|
440
|
+
requestId: requestId,
|
|
441
|
+
value: false // Cancelled = False
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Replace with cancellation message
|
|
446
|
+
const cancellation = document.createElement('p');
|
|
447
|
+
cancellation.style.cssText = `
|
|
448
|
+
margin: 0;
|
|
449
|
+
padding: 0.5rem 1rem;
|
|
450
|
+
color: var(--color-gray, #6b7280);
|
|
451
|
+
font-style: italic;
|
|
452
|
+
`;
|
|
453
|
+
cancellation.textContent = '[x] Cancelled';
|
|
454
|
+
|
|
455
|
+
buttonContainer.replaceWith(cancellation);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Assemble container
|
|
459
|
+
buttonContainer.appendChild(button);
|
|
460
|
+
buttonContainer.appendChild(cancelBtn);
|
|
461
|
+
|
|
462
|
+
// Add to container
|
|
463
|
+
container.appendChild(buttonContainer);
|
|
464
|
+
|
|
465
|
+
this.logger.log('[InputRequestRenderer] Button rendered');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2_Handling/hooks - Widget Hook System
|
|
3
|
+
*
|
|
4
|
+
* Hook registration, execution, and default hooks.
|
|
5
|
+
* Depends on: L1_Foundation (Logger)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { WidgetHookManager } from './widget_hook_manager.js';
|
|
9
|
+
export { MenuIntegration } from './menu_integration.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu Integration for Bifrost Client
|
|
3
|
+
*
|
|
4
|
+
* This module registers the onMenu hook with the BifrostClient to enable
|
|
5
|
+
* menu rendering and interaction in Bifrost mode.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* <script type="module" src="/bifrost/src/menu_integration.js"></script>
|
|
9
|
+
*
|
|
10
|
+
* Or dynamically:
|
|
11
|
+
* import { registerMenuHook } from './menu_integration.js';
|
|
12
|
+
* registerMenuHook(bifrostClient);
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ─────────────────────────────────────────────────────────────────
|
|
16
|
+
// Imports
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
// Layer 3: Renderers
|
|
20
|
+
import { MenuRenderer } from '../display/navigation/menu_renderer.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register the onMenu hook with a BifrostClient instance
|
|
24
|
+
* @param {BifrostClient} client - The Bifrost client instance
|
|
25
|
+
*/
|
|
26
|
+
export function registerMenuHook(client) {
|
|
27
|
+
const logger = client.logger || console;
|
|
28
|
+
logger.debug('[MenuIntegration] Registering onMenu hook');
|
|
29
|
+
|
|
30
|
+
// Create menu renderer
|
|
31
|
+
const menuRenderer = new MenuRenderer(client);
|
|
32
|
+
|
|
33
|
+
// Register the onMenu hook
|
|
34
|
+
// New-format zMenu events have flat 'options' array and 'title'.
|
|
35
|
+
// Legacy menu events have 'menu_key' and nested options with breadcrumbs.
|
|
36
|
+
client.registerHook('onMenu', (message) => {
|
|
37
|
+
logger.debug('[MenuIntegration] onMenu hook called with message:', message);
|
|
38
|
+
if (Array.isArray(message.options) && !message.menu_key) {
|
|
39
|
+
menuRenderer.renderZMenu(message);
|
|
40
|
+
} else {
|
|
41
|
+
menuRenderer.renderMenu(message);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
logger.info('[MenuIntegration] onMenu hook registered successfully');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Auto-register if BifrostClient is already initialized
|
|
50
|
+
* This allows the script to be loaded after the client is created
|
|
51
|
+
*/
|
|
52
|
+
if (typeof window !== 'undefined' && window.bifrostClient) {
|
|
53
|
+
const logger = window.bifrostClient.logger || console;
|
|
54
|
+
logger.debug('[MenuIntegration] Auto-registering with existing BifrostClient');
|
|
55
|
+
registerMenuHook(window.bifrostClient);
|
|
56
|
+
}
|
|
57
|
+
|