@reticlehq/browser 2.8.0 → 2.10.0
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/NOTICE +7 -0
- package/dist/actions/actions-dom.js +23 -15
- package/dist/actions/actions.d.ts +17 -0
- package/dist/actions/actions.js +132 -29
- package/dist/actions/synthetic-input.d.ts +9 -0
- package/dist/actions/synthetic-input.js +40 -0
- package/dist/commands/commands.js +11 -1
- package/dist/connect-options.d.ts +9 -9
- package/dist/dom/a11y.d.ts +9 -0
- package/dist/dom/a11y.js +19 -0
- package/dist/dom/dom-ignore.d.ts +19 -6
- package/dist/dom/dom-ignore.js +20 -13
- package/dist/dom/query.d.ts +2 -2
- package/dist/dom/query.js +85 -45
- package/dist/dom/snapshot.d.ts +5 -0
- package/dist/dom/snapshot.js +34 -0
- package/dist/dom/source.d.ts +14 -0
- package/dist/dom/source.js +16 -0
- package/dist/observers/install-all.js +4 -1
- package/dist/observers/ipc.d.ts +16 -0
- package/dist/observers/ipc.js +19 -1
- package/dist/observers/network.d.ts +8 -0
- package/dist/observers/network.js +24 -3
- package/dist/observers/state.js +21 -2
- package/dist/presenter/presenter-brand.d.ts +24 -7
- package/dist/presenter/presenter-brand.js +23 -6
- package/dist/presenter/presenter-config.d.ts +102 -9
- package/dist/presenter/presenter-config.js +94 -8
- package/dist/presenter/presenter-controls.d.ts +10 -10
- package/dist/presenter/presenter-controls.js +175 -85
- package/dist/presenter/presenter-dock-layout.d.ts +6 -0
- package/dist/presenter/presenter-dock-layout.js +170 -0
- package/dist/presenter/presenter-drag.d.ts +48 -0
- package/dist/presenter/presenter-drag.js +198 -0
- package/dist/presenter/presenter-effects.d.ts +1 -1
- package/dist/presenter/presenter-effects.js +1 -1
- package/dist/presenter/presenter-glow.d.ts +5 -5
- package/dist/presenter/presenter-glow.js +7 -7
- package/dist/presenter/presenter-heroicons-data.d.ts +41 -0
- package/dist/presenter/presenter-heroicons-data.js +38 -0
- package/dist/presenter/presenter-hud-chrome.d.ts +17 -0
- package/dist/presenter/presenter-hud-chrome.js +41 -0
- package/dist/presenter/presenter-icons.d.ts +44 -0
- package/dist/presenter/presenter-icons.js +100 -0
- package/dist/presenter/presenter-log.d.ts +27 -5
- package/dist/presenter/presenter-log.js +114 -28
- package/dist/presenter/presenter-report-copy.d.ts +83 -0
- package/dist/presenter/presenter-report-copy.js +127 -0
- package/dist/presenter/presenter-report-styles.d.ts +7 -0
- package/dist/presenter/presenter-report-styles.js +102 -0
- package/dist/presenter/presenter-report.d.ts +23 -0
- package/dist/presenter/presenter-report.js +217 -0
- package/dist/presenter/presenter-run-state.d.ts +1 -1
- package/dist/presenter/presenter-settings-styles.d.ts +2 -0
- package/dist/presenter/presenter-settings-styles.js +153 -0
- package/dist/presenter/presenter-settings.d.ts +82 -0
- package/dist/presenter/presenter-settings.js +494 -0
- package/dist/presenter/presenter-shell-styles.d.ts +1 -0
- package/dist/presenter/presenter-shell-styles.js +375 -0
- package/dist/presenter/presenter-shell.d.ts +48 -0
- package/dist/presenter/presenter-shell.js +408 -0
- package/dist/presenter/presenter-styles.d.ts +9 -4
- package/dist/presenter/presenter-styles.js +61 -125
- package/dist/presenter/presenter-tally.d.ts +8 -11
- package/dist/presenter/presenter-tally.js +48 -23
- package/dist/presenter/presenter-test-helpers.d.ts +1 -1
- package/dist/presenter/presenter-test-helpers.js +1 -1
- package/dist/presenter/presenter-verbs.js +2 -2
- package/dist/presenter/presenter-workspace.d.ts +15 -0
- package/dist/presenter/presenter-workspace.js +180 -0
- package/dist/presenter/presenter.d.ts +15 -10
- package/dist/presenter/presenter.js +194 -46
- package/dist/reticle.d.ts +2 -2
- package/dist/reticle.js +23 -19
- package/dist/review/annotator-styles.d.ts +6 -0
- package/dist/review/annotator-styles.js +81 -0
- package/dist/review/annotator.d.ts +29 -17
- package/dist/review/annotator.js +477 -146
- package/package.json +7 -4
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { SETTINGS_ATTR, SETTINGS_BTN_ATTR, SETTINGS_PANEL_ATTR, SETTINGS_CLOSE_ATTR, SETTING_KEY_ATTR, SETTINGS_STORAGE_KEY, AMBIENT_GLOW_ATTR, BLOCK_ATTR, HIDDEN_UNTIL_RESTART_ATTR, LOG_TIMESTAMPS_ATTR, REDUCE_MOTION_ATTR, DOCK_ATTR, MCP_DOCS_URL, } from './presenter-config.js';
|
|
2
|
+
import { PresenterIcon, PRESENTER_ICON_SIZE, setHiIcon, hiIconHtml } from './presenter-icons.js';
|
|
3
|
+
import { HUD_SURFACE_CLASS } from './presenter-hud-chrome.js';
|
|
4
|
+
import { resetHudDockPosition } from './presenter-drag.js';
|
|
5
|
+
import { findDock, scheduleSyncDockLayout } from './presenter-dock-layout.js';
|
|
6
|
+
import { SETTINGS_CSS } from './presenter-settings-styles.js';
|
|
7
|
+
export { SETTINGS_CSS };
|
|
8
|
+
/** How much detail lands in copied/exported run state. */
|
|
9
|
+
export const OutputDetail = {
|
|
10
|
+
MINIMAL: 'minimal',
|
|
11
|
+
STANDARD: 'standard',
|
|
12
|
+
VERBOSE: 'verbose',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Status themes: ONE choice, three colours.
|
|
16
|
+
*
|
|
17
|
+
* This was four separate pickers - a chrome accent plus a swatch row per state - which is 28
|
|
18
|
+
* decisions to land on a set that works together. A theme is a combination we have already checked
|
|
19
|
+
* reads as a progression (working -> waiting -> done) and against a dark app, so the choice is
|
|
20
|
+
* "which mood", not "which four hex values".
|
|
21
|
+
*/
|
|
22
|
+
export const StatusThemeId = {
|
|
23
|
+
SIGNAL: 'signal',
|
|
24
|
+
TRAFFIC: 'traffic',
|
|
25
|
+
MONO: 'mono',
|
|
26
|
+
NEON: 'neon',
|
|
27
|
+
EMBER: 'ember',
|
|
28
|
+
};
|
|
29
|
+
export const STATUS_THEMES = [
|
|
30
|
+
{
|
|
31
|
+
id: StatusThemeId.SIGNAL,
|
|
32
|
+
label: 'Signal',
|
|
33
|
+
active: '#3b82f6',
|
|
34
|
+
idle: '#eab308',
|
|
35
|
+
ended: '#ef4444',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: StatusThemeId.TRAFFIC,
|
|
39
|
+
label: 'Traffic',
|
|
40
|
+
active: '#22c55e',
|
|
41
|
+
idle: '#eab308',
|
|
42
|
+
ended: '#ef4444',
|
|
43
|
+
},
|
|
44
|
+
{ id: StatusThemeId.MONO, label: 'Mono', active: '#fafafa', idle: '#a3a3a3', ended: '#525252' },
|
|
45
|
+
{ id: StatusThemeId.NEON, label: 'Neon', active: '#06b6d4', idle: '#a855f7', ended: '#f43f5e' },
|
|
46
|
+
{ id: StatusThemeId.EMBER, label: 'Ember', active: '#f97316', idle: '#facc15', ended: '#7f1d1d' },
|
|
47
|
+
];
|
|
48
|
+
const FALLBACK_THEME = {
|
|
49
|
+
id: StatusThemeId.SIGNAL,
|
|
50
|
+
label: 'Signal',
|
|
51
|
+
active: '#3b82f6',
|
|
52
|
+
idle: '#eab308',
|
|
53
|
+
ended: '#ef4444',
|
|
54
|
+
};
|
|
55
|
+
export function statusTheme(id) {
|
|
56
|
+
return STATUS_THEMES.find((t) => t.id === id) ?? FALLBACK_THEME;
|
|
57
|
+
}
|
|
58
|
+
const OUTPUT_DETAIL_OPTIONS = [
|
|
59
|
+
{ value: OutputDetail.MINIMAL, label: 'Minimal' },
|
|
60
|
+
{ value: OutputDetail.STANDARD, label: 'Standard' },
|
|
61
|
+
{ value: OutputDetail.VERBOSE, label: 'Verbose' },
|
|
62
|
+
];
|
|
63
|
+
const DEFAULT_SETTINGS = {
|
|
64
|
+
outputDetail: OutputDetail.STANDARD,
|
|
65
|
+
statusThemeId: StatusThemeId.SIGNAL,
|
|
66
|
+
ambientGlow: true,
|
|
67
|
+
reactComponents: false,
|
|
68
|
+
hideUntilRestart: false,
|
|
69
|
+
clearOnCopy: false,
|
|
70
|
+
blockPageInteractions: true,
|
|
71
|
+
showTally: false,
|
|
72
|
+
showTimestamps: true,
|
|
73
|
+
autoOpenChat: false,
|
|
74
|
+
reduceMotion: false,
|
|
75
|
+
};
|
|
76
|
+
let activeSettings = loadPresenterSettings();
|
|
77
|
+
export function getPresenterSettings() {
|
|
78
|
+
return activeSettings;
|
|
79
|
+
}
|
|
80
|
+
export function loadPresenterSettings() {
|
|
81
|
+
if ('undefined' === typeof localStorage)
|
|
82
|
+
return { ...DEFAULT_SETTINGS };
|
|
83
|
+
try {
|
|
84
|
+
const raw = localStorage.getItem(SETTINGS_STORAGE_KEY);
|
|
85
|
+
if (null === raw)
|
|
86
|
+
return { ...DEFAULT_SETTINGS };
|
|
87
|
+
const parsed = JSON.parse(raw);
|
|
88
|
+
if ('object' !== typeof parsed || null === parsed)
|
|
89
|
+
return { ...DEFAULT_SETTINGS };
|
|
90
|
+
const o = parsed;
|
|
91
|
+
return {
|
|
92
|
+
outputDetail: isOutputDetail(o['outputDetail'])
|
|
93
|
+
? o['outputDetail']
|
|
94
|
+
: DEFAULT_SETTINGS.outputDetail,
|
|
95
|
+
reactComponents: true === o['reactComponents'],
|
|
96
|
+
// NOT read back from storage. "Until restart" has to mean until restart: persisted, it
|
|
97
|
+
// survived the reload that was supposed to undo it, and the only way back was clearing
|
|
98
|
+
// localStorage by hand - the HUD was simply gone, including the settings panel that turned
|
|
99
|
+
// it off. It is a this-page-only switch, so it lives only in memory.
|
|
100
|
+
hideUntilRestart: false,
|
|
101
|
+
statusThemeId: isStatusThemeId(o['statusThemeId'])
|
|
102
|
+
? o['statusThemeId']
|
|
103
|
+
: DEFAULT_SETTINGS.statusThemeId,
|
|
104
|
+
ambientGlow: 'boolean' === typeof o['ambientGlow'] ? o['ambientGlow'] : DEFAULT_SETTINGS.ambientGlow,
|
|
105
|
+
clearOnCopy: true === o['clearOnCopy'],
|
|
106
|
+
blockPageInteractions: 'boolean' === typeof o['blockPageInteractions']
|
|
107
|
+
? o['blockPageInteractions']
|
|
108
|
+
: DEFAULT_SETTINGS.blockPageInteractions,
|
|
109
|
+
showTally: 'boolean' === typeof o['showTally'] ? o['showTally'] : DEFAULT_SETTINGS.showTally,
|
|
110
|
+
showTimestamps: 'boolean' === typeof o['showTimestamps']
|
|
111
|
+
? o['showTimestamps']
|
|
112
|
+
: DEFAULT_SETTINGS.showTimestamps,
|
|
113
|
+
autoOpenChat: 'boolean' === typeof o['autoOpenChat'] ? o['autoOpenChat'] : DEFAULT_SETTINGS.autoOpenChat,
|
|
114
|
+
reduceMotion: 'boolean' === typeof o['reduceMotion'] ? o['reduceMotion'] : DEFAULT_SETTINGS.reduceMotion,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return { ...DEFAULT_SETTINGS };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function isOutputDetail(v) {
|
|
122
|
+
return v === OutputDetail.MINIMAL || v === OutputDetail.STANDARD || v === OutputDetail.VERBOSE;
|
|
123
|
+
}
|
|
124
|
+
function isStatusThemeId(v) {
|
|
125
|
+
return STATUS_THEMES.some((t) => t.id === v);
|
|
126
|
+
}
|
|
127
|
+
function persistSettings(next) {
|
|
128
|
+
activeSettings = next;
|
|
129
|
+
if ('undefined' === typeof localStorage)
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(next));
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
/* quota / private mode - settings still apply for this page */
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function patchSettings(patch) {
|
|
139
|
+
const next = { ...activeSettings, ...patch };
|
|
140
|
+
persistSettings(next);
|
|
141
|
+
return next;
|
|
142
|
+
}
|
|
143
|
+
function settingsHelpIcon() {
|
|
144
|
+
return hiIconHtml(PresenterIcon.HELP, PRESENTER_ICON_SIZE.HELP);
|
|
145
|
+
}
|
|
146
|
+
function settingsLabel(text, helpTitle) {
|
|
147
|
+
const help = settingsHelpIcon();
|
|
148
|
+
return `<span class="reticle-settings-label">${text}<button type="button" class="reticle-settings-help" title="${helpTitle}" aria-label="${helpTitle}">${help}</button></span>`;
|
|
149
|
+
}
|
|
150
|
+
function settingsToggleRow(key, label, helpTitle, extra = '') {
|
|
151
|
+
return `<div class="reticle-settings-row" ${extra}>
|
|
152
|
+
${settingsLabel(label, helpTitle)}
|
|
153
|
+
<button type="button" class="reticle-settings-toggle" ${SETTING_KEY_ATTR}="${key}" role="switch" aria-checked="false"></button>
|
|
154
|
+
</div>`;
|
|
155
|
+
}
|
|
156
|
+
function settingsCheckRow(key, label, checked) {
|
|
157
|
+
const on = checked ? 'true' : 'false';
|
|
158
|
+
return `<label class="reticle-settings-checkrow" data-reticle-check-row="${key}">
|
|
159
|
+
<span class="reticle-settings-check" data-reticle-check="${key}" role="checkbox" aria-checked="${on}" tabindex="0"></span>
|
|
160
|
+
<span class="reticle-settings-check-label">${label}</span>
|
|
161
|
+
</label>`;
|
|
162
|
+
}
|
|
163
|
+
/** Settings panel markup - anchored above the gear in the toolbar. */
|
|
164
|
+
export function settingsPanelHtml() {
|
|
165
|
+
const close = hiIconHtml(PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
|
|
166
|
+
const caret = hiIconHtml(PresenterIcon.CARET_RIGHT, PRESENTER_ICON_SIZE.HELP);
|
|
167
|
+
const outputHelp = 'How much detail is included when you copy or export the run';
|
|
168
|
+
const reactHelp = 'Include React component paths in exported run state when available';
|
|
169
|
+
const hideHelp = 'Hide the Reticle HUD until you reload the page';
|
|
170
|
+
const tallyHelp = 'Show the pass/fail score pill in the toolbar';
|
|
171
|
+
const timestampsHelp = 'Show relative timestamps on each activity-log row';
|
|
172
|
+
const autoChatHelp = 'Open the agent chat by itself when a session starts and when you expand the HUD. Off leaves the toolbar bare until you ask for the chat.';
|
|
173
|
+
const motionHelp = 'Reduce HUD animations for accessibility';
|
|
174
|
+
const glowHelp = 'Glow the page edges in the status colour while a session is live. Off keeps the HUD signals and leaves your app alone.';
|
|
175
|
+
return `<div ${SETTINGS_PANEL_ATTR} class="reticle-settings ${HUD_SURFACE_CLASS}" role="dialog" aria-label="Reticle settings" aria-hidden="true">
|
|
176
|
+
<div class="reticle-settings-inner">
|
|
177
|
+
<div class="reticle-settings-head">
|
|
178
|
+
<span class="reticle-settings-title">Settings</span>
|
|
179
|
+
<button type="button" ${SETTINGS_CLOSE_ATTR} class="reticle-settings-close" title="Close settings" aria-label="Close settings">${close}</button>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="reticle-settings-body">
|
|
182
|
+
<div class="reticle-settings-section">Session</div>
|
|
183
|
+
<div class="reticle-settings-row">
|
|
184
|
+
${settingsLabel('Output Detail', outputHelp)}
|
|
185
|
+
<button type="button" class="reticle-settings-cycle" data-reticle-settings-cycle="outputDetail"><span data-reticle-cycle-label></span><span class="reticle-settings-dots" data-reticle-cycle-dots></span></button>
|
|
186
|
+
</div>
|
|
187
|
+
${settingsToggleRow('autoOpenChat', 'Auto-open chat', autoChatHelp)}
|
|
188
|
+
${settingsToggleRow('showTimestamps', 'Show timestamps', timestampsHelp)}
|
|
189
|
+
${settingsToggleRow('showTally', 'Show verdict tally', tallyHelp)}
|
|
190
|
+
<div class="reticle-settings-section">Inspector</div>
|
|
191
|
+
${settingsToggleRow('reactComponents', 'React Components', reactHelp, 'data-reticle-settings-react-row')}
|
|
192
|
+
<div class="reticle-settings-section">Interaction</div>
|
|
193
|
+
${settingsCheckRow('blockPageInteractions', 'Block page interactions', true)}
|
|
194
|
+
${settingsCheckRow('clearOnCopy', 'Clear on copy/send', false)}
|
|
195
|
+
${settingsToggleRow('hideUntilRestart', 'Hide Until Restart', hideHelp)}
|
|
196
|
+
${settingsToggleRow('reduceMotion', 'Reduce motion', motionHelp)}
|
|
197
|
+
<div class="reticle-settings-section">Status theme</div>
|
|
198
|
+
${settingsToggleRow('ambientGlow', 'Page glow', glowHelp)}
|
|
199
|
+
<div class="reticle-settings-themes" data-reticle-settings-themes></div>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="reticle-settings-foot">
|
|
202
|
+
<button type="button" class="reticle-settings-reset" data-reticle-settings-reset>Reset HUD position</button>
|
|
203
|
+
<button type="button" class="reticle-settings-link" data-reticle-settings-mcp>Manage MCP & Webhooks<span class="reticle-settings-link-caret" aria-hidden="true">${caret}</span></button>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>`;
|
|
207
|
+
}
|
|
208
|
+
/** Apply persisted settings onto the overlay + dock. */
|
|
209
|
+
export function applyPresenterSettings(root, settings) {
|
|
210
|
+
const theme = statusTheme(settings.statusThemeId);
|
|
211
|
+
root.style.setProperty('--reticle-mark-accent', theme.active);
|
|
212
|
+
root.style.setProperty('--reticle-accent', theme.active);
|
|
213
|
+
root.style.setProperty('--reticle-c-active', theme.active);
|
|
214
|
+
root.style.setProperty('--reticle-c-idle', theme.idle);
|
|
215
|
+
root.style.setProperty('--reticle-c-ended', theme.ended);
|
|
216
|
+
root.setAttribute(AMBIENT_GLOW_ATTR, settings.ambientGlow ? '1' : '0');
|
|
217
|
+
if (settings.hideUntilRestart) {
|
|
218
|
+
root.setAttribute(HIDDEN_UNTIL_RESTART_ATTR, '1');
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
root.removeAttribute(HIDDEN_UNTIL_RESTART_ATTR);
|
|
222
|
+
}
|
|
223
|
+
root.setAttribute(LOG_TIMESTAMPS_ATTR, settings.showTimestamps ? '1' : '0');
|
|
224
|
+
root.setAttribute(REDUCE_MOTION_ATTR, settings.reduceMotion ? '1' : '0');
|
|
225
|
+
const tally = root.querySelector('[data-reticle-tally]');
|
|
226
|
+
if (tally instanceof HTMLElement && !settings.showTally) {
|
|
227
|
+
tally.setAttribute('hidden', '');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Toggle the full-page blocker. Only active while annotate mode is live AND the user opted in -
|
|
232
|
+
* never on a collapsed FAB, so real hover/click on the host app still works.
|
|
233
|
+
*/
|
|
234
|
+
export function syncPageBlocker(root, settings, annotateLive) {
|
|
235
|
+
root.setAttribute(BLOCK_ATTR, settings.blockPageInteractions && annotateLive ? '1' : '0');
|
|
236
|
+
}
|
|
237
|
+
/** Blocker node - sits under Reticle UI, above the host page. */
|
|
238
|
+
export function blockerHtml() {
|
|
239
|
+
return '<div data-reticle-blocker aria-hidden="true"></div>';
|
|
240
|
+
}
|
|
241
|
+
export class PresenterSettingsPanel {
|
|
242
|
+
#root;
|
|
243
|
+
#panel;
|
|
244
|
+
#btn;
|
|
245
|
+
#host;
|
|
246
|
+
constructor(host = {}) {
|
|
247
|
+
this.#host = host;
|
|
248
|
+
activeSettings = loadPresenterSettings();
|
|
249
|
+
}
|
|
250
|
+
contains(node) {
|
|
251
|
+
return true === this.#panel?.contains(node);
|
|
252
|
+
}
|
|
253
|
+
mount(root) {
|
|
254
|
+
this.#root = root;
|
|
255
|
+
const panel = root.querySelector(`[${SETTINGS_PANEL_ATTR}]`);
|
|
256
|
+
this.#panel = panel instanceof HTMLElement ? panel : undefined;
|
|
257
|
+
this.#panel?.addEventListener('pointerdown', (e) => {
|
|
258
|
+
e.stopPropagation();
|
|
259
|
+
});
|
|
260
|
+
const btn = root.querySelector(`[${SETTINGS_BTN_ATTR}]`);
|
|
261
|
+
this.#btn = btn instanceof HTMLElement ? btn : undefined;
|
|
262
|
+
const closeBtn = root.querySelector(`[${SETTINGS_CLOSE_ATTR}]`);
|
|
263
|
+
if (closeBtn instanceof HTMLElement) {
|
|
264
|
+
setHiIcon(closeBtn, PresenterIcon.REMOVE, PRESENTER_ICON_SIZE.MIN);
|
|
265
|
+
}
|
|
266
|
+
if (this.#btn !== undefined) {
|
|
267
|
+
setHiIcon(this.#btn, PresenterIcon.GEAR, PRESENTER_ICON_SIZE.TOOLBAR);
|
|
268
|
+
this.#btn.addEventListener('click', (e) => {
|
|
269
|
+
e.stopPropagation();
|
|
270
|
+
this.toggle();
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
root
|
|
274
|
+
.querySelector(`[data-reticle-settings-cycle="outputDetail"]`)
|
|
275
|
+
?.addEventListener('click', (e) => {
|
|
276
|
+
e.stopPropagation();
|
|
277
|
+
const idx = OUTPUT_DETAIL_OPTIONS.findIndex((o) => o.value === activeSettings.outputDetail);
|
|
278
|
+
const next = OUTPUT_DETAIL_OPTIONS[(idx + 1) % OUTPUT_DETAIL_OPTIONS.length];
|
|
279
|
+
if (next !== undefined)
|
|
280
|
+
this.#update({ outputDetail: next.value });
|
|
281
|
+
});
|
|
282
|
+
for (const help of root.querySelectorAll('.reticle-settings-help')) {
|
|
283
|
+
help.addEventListener('click', (e) => {
|
|
284
|
+
e.stopPropagation();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
for (const toggle of root.querySelectorAll(`[${SETTING_KEY_ATTR}]`)) {
|
|
288
|
+
const activateToggle = () => {
|
|
289
|
+
if (!(toggle instanceof HTMLElement))
|
|
290
|
+
return;
|
|
291
|
+
const key = toggle.getAttribute(SETTING_KEY_ATTR);
|
|
292
|
+
if ('reactComponents' === key) {
|
|
293
|
+
this.#update({ reactComponents: !activeSettings.reactComponents });
|
|
294
|
+
}
|
|
295
|
+
else if ('hideUntilRestart' === key) {
|
|
296
|
+
const next = !activeSettings.hideUntilRestart;
|
|
297
|
+
this.#update({ hideUntilRestart: next });
|
|
298
|
+
if (next)
|
|
299
|
+
this.#host.onHideUntilRestart?.();
|
|
300
|
+
}
|
|
301
|
+
else if ('showTally' === key) {
|
|
302
|
+
this.#update({ showTally: !activeSettings.showTally });
|
|
303
|
+
}
|
|
304
|
+
else if ('autoOpenChat' === key) {
|
|
305
|
+
this.#update({ autoOpenChat: !activeSettings.autoOpenChat });
|
|
306
|
+
}
|
|
307
|
+
else if ('showTimestamps' === key) {
|
|
308
|
+
this.#update({ showTimestamps: !activeSettings.showTimestamps });
|
|
309
|
+
}
|
|
310
|
+
else if ('reduceMotion' === key) {
|
|
311
|
+
this.#update({ reduceMotion: !activeSettings.reduceMotion });
|
|
312
|
+
}
|
|
313
|
+
else if ('ambientGlow' === key) {
|
|
314
|
+
this.#update({ ambientGlow: !activeSettings.ambientGlow });
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
toggle.addEventListener('click', (e) => {
|
|
318
|
+
e.stopPropagation();
|
|
319
|
+
activateToggle();
|
|
320
|
+
});
|
|
321
|
+
toggle.addEventListener('keydown', (e) => {
|
|
322
|
+
if (e instanceof KeyboardEvent && (' ' === e.key || 'Enter' === e.key)) {
|
|
323
|
+
e.preventDefault();
|
|
324
|
+
e.stopPropagation();
|
|
325
|
+
activateToggle();
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
for (const check of root.querySelectorAll('[data-reticle-check]')) {
|
|
330
|
+
const activate = () => {
|
|
331
|
+
if (!(check instanceof HTMLElement))
|
|
332
|
+
return;
|
|
333
|
+
const key = check.getAttribute('data-reticle-check');
|
|
334
|
+
if ('clearOnCopy' === key) {
|
|
335
|
+
this.#update({ clearOnCopy: !activeSettings.clearOnCopy });
|
|
336
|
+
}
|
|
337
|
+
else if ('blockPageInteractions' === key) {
|
|
338
|
+
this.#update({ blockPageInteractions: !activeSettings.blockPageInteractions });
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
const row = check.closest('[data-reticle-check-row]');
|
|
342
|
+
row?.addEventListener('click', (e) => {
|
|
343
|
+
e.stopPropagation();
|
|
344
|
+
if (e.target instanceof HTMLElement && e.target.classList.contains('reticle-settings-help'))
|
|
345
|
+
return;
|
|
346
|
+
activate();
|
|
347
|
+
});
|
|
348
|
+
check.addEventListener('keydown', (e) => {
|
|
349
|
+
if (e instanceof KeyboardEvent && (' ' === e.key || 'Enter' === e.key)) {
|
|
350
|
+
e.preventDefault();
|
|
351
|
+
activate();
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
root.querySelector('[data-reticle-settings-mcp]')?.addEventListener('click', (e) => {
|
|
356
|
+
e.stopPropagation();
|
|
357
|
+
window.open(MCP_DOCS_URL, '_blank', 'noopener,noreferrer');
|
|
358
|
+
});
|
|
359
|
+
root.querySelector('[data-reticle-settings-reset]')?.addEventListener('click', (e) => {
|
|
360
|
+
e.stopPropagation();
|
|
361
|
+
const dock = root.querySelector(`[${DOCK_ATTR}]`);
|
|
362
|
+
if (dock instanceof HTMLElement)
|
|
363
|
+
resetHudDockPosition(dock);
|
|
364
|
+
});
|
|
365
|
+
closeBtn?.addEventListener('click', (e) => {
|
|
366
|
+
e.stopPropagation();
|
|
367
|
+
this.close();
|
|
368
|
+
});
|
|
369
|
+
this.#buildThemes();
|
|
370
|
+
this.#syncUi();
|
|
371
|
+
applyPresenterSettings(root, activeSettings);
|
|
372
|
+
this.#host.onSettingsChange?.(activeSettings);
|
|
373
|
+
}
|
|
374
|
+
teardown() {
|
|
375
|
+
this.#root = undefined;
|
|
376
|
+
this.#panel = undefined;
|
|
377
|
+
this.#btn = undefined;
|
|
378
|
+
}
|
|
379
|
+
isOpen() {
|
|
380
|
+
return '1' === this.#root?.getAttribute(SETTINGS_ATTR);
|
|
381
|
+
}
|
|
382
|
+
open() {
|
|
383
|
+
if (this.#root === undefined)
|
|
384
|
+
return;
|
|
385
|
+
this.#host.onBeforeOpen?.();
|
|
386
|
+
this.#root.setAttribute(SETTINGS_ATTR, '1');
|
|
387
|
+
this.#panel?.setAttribute('aria-hidden', 'false');
|
|
388
|
+
this.#btn?.setAttribute('data-active', '1');
|
|
389
|
+
this.#syncDockLayout();
|
|
390
|
+
}
|
|
391
|
+
close() {
|
|
392
|
+
if (this.#root === undefined)
|
|
393
|
+
return;
|
|
394
|
+
this.#root.setAttribute(SETTINGS_ATTR, '0');
|
|
395
|
+
this.#panel?.setAttribute('aria-hidden', 'true');
|
|
396
|
+
this.#btn?.setAttribute('data-active', '0');
|
|
397
|
+
this.#syncDockLayout();
|
|
398
|
+
}
|
|
399
|
+
#syncDockLayout() {
|
|
400
|
+
if (this.#root === undefined)
|
|
401
|
+
return;
|
|
402
|
+
const dock = findDock(this.#root);
|
|
403
|
+
if (dock !== undefined)
|
|
404
|
+
scheduleSyncDockLayout(dock, this.#root);
|
|
405
|
+
}
|
|
406
|
+
toggle() {
|
|
407
|
+
if (this.isOpen())
|
|
408
|
+
this.close();
|
|
409
|
+
else
|
|
410
|
+
this.open();
|
|
411
|
+
}
|
|
412
|
+
#update(patch) {
|
|
413
|
+
const next = patchSettings(patch);
|
|
414
|
+
if (this.#root !== undefined)
|
|
415
|
+
applyPresenterSettings(this.#root, next);
|
|
416
|
+
this.#syncUi();
|
|
417
|
+
this.#host.onSettingsChange?.(next);
|
|
418
|
+
}
|
|
419
|
+
#syncUi() {
|
|
420
|
+
const s = activeSettings;
|
|
421
|
+
const label = this.#panel?.querySelector('[data-reticle-cycle-label]');
|
|
422
|
+
if (label !== null && label !== undefined) {
|
|
423
|
+
label.textContent =
|
|
424
|
+
OUTPUT_DETAIL_OPTIONS.find((o) => o.value === s.outputDetail)?.label ?? 'Standard';
|
|
425
|
+
}
|
|
426
|
+
const dots = this.#panel?.querySelector('[data-reticle-cycle-dots]');
|
|
427
|
+
if (dots !== null && dots !== undefined) {
|
|
428
|
+
dots.replaceChildren(...OUTPUT_DETAIL_OPTIONS.map((o) => {
|
|
429
|
+
const dot = document.createElement('span');
|
|
430
|
+
dot.className = 'reticle-settings-dot';
|
|
431
|
+
dot.setAttribute('data-on', o.value === s.outputDetail ? '1' : '0');
|
|
432
|
+
return dot;
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
435
|
+
this.#paintToggle('reactComponents', s.reactComponents);
|
|
436
|
+
this.#paintToggle('hideUntilRestart', s.hideUntilRestart);
|
|
437
|
+
this.#paintToggle('showTally', s.showTally);
|
|
438
|
+
this.#paintToggle('autoOpenChat', s.autoOpenChat);
|
|
439
|
+
this.#paintToggle('showTimestamps', s.showTimestamps);
|
|
440
|
+
this.#paintToggle('reduceMotion', s.reduceMotion);
|
|
441
|
+
this.#paintToggle('ambientGlow', s.ambientGlow);
|
|
442
|
+
this.#paintCheck('clearOnCopy', s.clearOnCopy);
|
|
443
|
+
this.#paintCheck('blockPageInteractions', s.blockPageInteractions);
|
|
444
|
+
for (const chip of this.#panel?.querySelectorAll('[data-reticle-theme]') ?? []) {
|
|
445
|
+
if (chip instanceof HTMLElement) {
|
|
446
|
+
chip.setAttribute('data-on', chip.getAttribute('data-reticle-theme') === s.statusThemeId ? '1' : '0');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
#paintToggle(key, on) {
|
|
451
|
+
const el = this.#panel?.querySelector(`[${SETTING_KEY_ATTR}="${key}"]`);
|
|
452
|
+
if (el instanceof HTMLElement) {
|
|
453
|
+
el.setAttribute('data-on', on ? '1' : '0');
|
|
454
|
+
el.setAttribute('aria-checked', on ? 'true' : 'false');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
#paintCheck(key, on) {
|
|
458
|
+
const el = this.#panel?.querySelector(`[data-reticle-check="${key}"]`);
|
|
459
|
+
if (el instanceof HTMLElement) {
|
|
460
|
+
el.setAttribute('data-on', on ? '1' : '0');
|
|
461
|
+
el.setAttribute('aria-checked', on ? 'true' : 'false');
|
|
462
|
+
el.textContent = on ? '✓' : '';
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/** One row of themes: each chip shows the whole set - active, idle, ended - in order. */
|
|
466
|
+
#buildThemes() {
|
|
467
|
+
const host = this.#panel?.querySelector('[data-reticle-settings-themes]');
|
|
468
|
+
if (null === host || undefined === host)
|
|
469
|
+
return;
|
|
470
|
+
host.replaceChildren(...STATUS_THEMES.map((t) => {
|
|
471
|
+
const btn = document.createElement('button');
|
|
472
|
+
btn.type = 'button';
|
|
473
|
+
btn.className = 'reticle-settings-theme';
|
|
474
|
+
btn.setAttribute('data-reticle-theme', t.id);
|
|
475
|
+
btn.title = t.label;
|
|
476
|
+
btn.setAttribute('aria-label', `${t.label} status theme`);
|
|
477
|
+
for (const color of [t.active, t.idle, t.ended]) {
|
|
478
|
+
const band = document.createElement('span');
|
|
479
|
+
band.className = 'reticle-settings-theme-band';
|
|
480
|
+
band.style.background = color;
|
|
481
|
+
btn.appendChild(band);
|
|
482
|
+
}
|
|
483
|
+
const name = document.createElement('span');
|
|
484
|
+
name.className = 'reticle-settings-theme-name';
|
|
485
|
+
name.textContent = t.label;
|
|
486
|
+
btn.appendChild(name);
|
|
487
|
+
btn.addEventListener('click', (e) => {
|
|
488
|
+
e.stopPropagation();
|
|
489
|
+
this.#update({ statusThemeId: t.id });
|
|
490
|
+
});
|
|
491
|
+
return btn;
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SHELL_CSS: string;
|