clay-server 5.2.0-beta.8 → 5.2.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/README.md +0 -4
- package/lib/clay-product-control-tools.js +1 -0
- package/lib/clay-product-control.js +14 -11
- package/lib/clay-vendor-usage.js +44 -0
- package/lib/github-star-count.js +147 -0
- package/lib/notes-checklist.js +43 -1
- package/lib/notes.js +2 -1
- package/lib/pane-pending-interactions.js +20 -0
- package/lib/pane-session-activation.js +114 -0
- package/lib/pane-session-transport.js +26 -0
- package/lib/project-session-notes.js +33 -8
- package/lib/project-sessions.js +78 -29
- package/lib/project-start-protocol.js +29 -0
- package/lib/project-vendor-login.js +2 -0
- package/lib/project.js +16 -4
- package/lib/public/app.js +41 -18
- package/lib/public/css/contextual-clay.css +2 -2
- package/lib/public/css/filebrowser.css +1 -1
- package/lib/public/css/input.css +6 -0
- package/lib/public/css/mate-loading-scene.css +21 -0
- package/lib/public/css/overlays.css +64 -256
- package/lib/public/css/project-mate-creation.css +5 -6
- package/lib/public/css/project-start.css +108 -0
- package/lib/public/css/projects-hub.css +53 -14
- package/lib/public/css/settings-shell.css +103 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/css/sticky-notes.css +37 -11
- package/lib/public/css/title-bar.css +83 -0
- package/lib/public/index.html +50 -63
- package/lib/public/modules/account-transport-settings.js +8 -2
- package/lib/public/modules/admin-settings-email.js +7 -2
- package/lib/public/modules/admin-settings-invitations.js +2 -2
- package/lib/public/modules/admin-settings-projects.js +2 -2
- package/lib/public/modules/admin-settings-ui.js +20 -4
- package/lib/public/modules/admin-settings-users.js +4 -4
- package/lib/public/modules/app-connection.js +3 -0
- package/lib/public/modules/app-home-hub.js +2 -1
- package/lib/public/modules/app-message-router.js +13 -0
- package/lib/public/modules/app-messages.js +35 -7
- package/lib/public/modules/app-misc.js +3 -1
- package/lib/public/modules/app-projects.js +6 -407
- package/lib/public/modules/app-rate-limit.js +12 -1
- package/lib/public/modules/clay-owner-navigation.js +0 -3
- package/lib/public/modules/clay-studio-render.js +3 -1
- package/lib/public/modules/clay-tool-presentation.js +1 -0
- package/lib/public/modules/command-palette.js +52 -14
- package/lib/public/modules/contextual-clay-workspace.js +14 -2
- package/lib/public/modules/contextual-clay.js +19 -4
- package/lib/public/modules/contextual-mate-context.js +6 -4
- package/lib/public/modules/elicitation-form.js +5 -0
- package/lib/public/modules/github-star.js +66 -0
- package/lib/public/modules/history-replay-batch.js +8 -0
- package/lib/public/modules/home-surface-boot.js +0 -1
- package/lib/public/modules/input.js +55 -11
- package/lib/public/modules/mate-loading-scene.js +17 -0
- package/lib/public/modules/message-delivery.js +10 -2
- package/lib/public/modules/model-picker.js +2 -0
- package/lib/public/modules/pane-activation.js +214 -0
- package/lib/public/modules/pane-appearance-bridge.js +59 -0
- package/lib/public/modules/pane-binding.js +82 -0
- package/lib/public/modules/pane-bridge.js +5 -2
- package/lib/public/modules/pane-browser-bridge.js +5 -3
- package/lib/public/modules/pane-file-bridge.js +4 -0
- package/lib/public/modules/pane-issue-bridge.js +4 -1
- package/lib/public/modules/profile.js +11 -3
- package/lib/public/modules/project-mate-creation-workspace.js +13 -4
- package/lib/public/modules/project-session-visibility-settings.js +19 -16
- package/lib/public/modules/project-settings-environment.js +12 -1
- package/lib/public/modules/project-settings.js +119 -129
- package/lib/public/modules/project-start-dialog.js +130 -0
- package/lib/public/modules/project-start-folder-state.js +78 -0
- package/lib/public/modules/project-start-folder-view.js +190 -0
- package/lib/public/modules/project-start-forms.js +66 -0
- package/lib/public/modules/project-start-path.js +60 -0
- package/lib/public/modules/project-start-state.js +54 -0
- package/lib/public/modules/project-start-submit.js +67 -0
- package/lib/public/modules/projects-hub-view.js +32 -44
- package/lib/public/modules/projects-hub.js +39 -28
- package/lib/public/modules/rewind.js +5 -0
- package/lib/public/modules/server-settings.js +36 -91
- package/lib/public/modules/session-actions.js +5 -2
- package/lib/public/modules/session-context-menu.js +5 -0
- package/lib/public/modules/session-rich-drafts.js +21 -0
- package/lib/public/modules/session-switch-defer.js +67 -0
- package/lib/public/modules/settings-project-target.js +56 -0
- package/lib/public/modules/settings-select.js +1 -1
- package/lib/public/modules/settings-shell-registry.js +88 -0
- package/lib/public/modules/settings-shell-view.js +104 -0
- package/lib/public/modules/settings-shell.js +342 -0
- package/lib/public/modules/sidebar-mobile.js +8 -30
- package/lib/public/modules/sidebar-projects.js +2 -1
- package/lib/public/modules/split-group-restore.js +68 -0
- package/lib/public/modules/split-pair-ui.js +1 -0
- package/lib/public/modules/split-pane-reconciler.js +15 -0
- package/lib/public/modules/split-pane-renderer.js +87 -6
- package/lib/public/modules/split-session-boundary.js +1 -1
- package/lib/public/modules/split-view.js +51 -52
- package/lib/public/modules/sticky-notes-checklist.js +51 -6
- package/lib/public/modules/stt-controller.js +4 -1
- package/lib/public/modules/stt-recording.js +1 -1
- package/lib/public/modules/theme.js +31 -3
- package/lib/public/modules/tools.js +18 -2
- package/lib/public/modules/user-settings-integrations.js +2 -1
- package/lib/public/modules/user-settings-layout.js +12 -34
- package/lib/public/modules/user-settings-profile.js +1 -1
- package/lib/public/modules/user-settings.js +18 -85
- package/lib/public/modules/vendor-usage-format.js +93 -0
- package/lib/public/modules/vendor-usage-list.js +45 -0
- package/lib/public/modules/vendor-usage-model.js +215 -0
- package/lib/public/modules/vendor-usage-overview.js +323 -0
- package/lib/public/style.css +10 -7
- package/lib/public/welcome-art/conversation-desk.png +0 -0
- package/lib/public/welcome-art/mate-clay.png +0 -0
- package/lib/public/welcome-art/project-clay.png +0 -0
- package/lib/sdk-bridge.js +6 -1
- package/lib/sdk-message-processor.js +9 -1
- package/lib/sdk-quota-snapshot.js +20 -2
- package/lib/server-github-stars.js +39 -0
- package/lib/server-global-ws.js +21 -17
- package/lib/server.js +11 -0
- package/lib/session-notes-mcp-server.js +26 -4
- package/lib/session-notes-writes.js +88 -0
- package/lib/session-visibility.js +20 -8
- package/lib/sessions.js +27 -10
- package/lib/vendor-usage-acp-readers.js +97 -0
- package/lib/vendor-usage-capabilities.js +88 -0
- package/lib/vendor-usage-http-readers.js +214 -0
- package/lib/vendor-usage-metrics.js +47 -0
- package/lib/vendor-usage-native-readers.js +106 -0
- package/lib/vendor-usage-overview.js +406 -0
- package/lib/vendor-usage-service.js +281 -0
- package/lib/ws-schema.js +4 -2
- package/lib/yoke/adapters/claude.js +12 -0
- package/lib/yoke/adapters/codex-query-contract.js +1 -0
- package/lib/yoke/adapters/codex.js +45 -12
- package/lib/yoke/claude-hook-installer.js +1 -0
- package/lib/yoke/codex-dynamic-tool-names.js +80 -0
- package/lib/yoke/codex-model-catalog.js +37 -3
- package/lib/yoke/muse-services.js +16 -3
- package/lib/yoke/providers/claude-model-descriptions.js +22 -2
- package/lib/yoke/vendor-registry.js +6 -2
- package/package.json +5 -3
- package/lib/public/modules/server-settings-navigation.js +0 -39
|
@@ -222,6 +222,7 @@ var customSet = {}; // ids that came from ~/.clay/themes/
|
|
|
222
222
|
var themesLoaded = false;
|
|
223
223
|
var currentThemeId = DEFAULT_DARK_THEME_ID;
|
|
224
224
|
var changeCallbacks = [];
|
|
225
|
+
var externalThemeId = null; // set by the parent shell for pane iframes
|
|
225
226
|
var STORAGE_KEY = "clay-theme";
|
|
226
227
|
var MODE_KEY = "clay-mode"; // "light" | "dark" | null (system)
|
|
227
228
|
var SKIN_KEY = "clay-skin"; // theme id within current variant pair
|
|
@@ -274,6 +275,23 @@ export function onThemeChange(fn) {
|
|
|
274
275
|
changeCallbacks.push(fn);
|
|
275
276
|
}
|
|
276
277
|
|
|
278
|
+
// The appearance this frame currently shows, for the parent shell to hand to
|
|
279
|
+
// its pane iframes. Never persisted from here.
|
|
280
|
+
export function getAppearanceSnapshot() {
|
|
281
|
+
var pref = store.get('appearanceDraft') || store.get('appearancePreference') || {};
|
|
282
|
+
return { themeId: currentThemeId, userId: store.get('myUserId') || null,
|
|
283
|
+
preference: { configured: !!pref.configured || !!pref.mode || !!pref.theme, theme: pref.theme || null, mode: pref.mode || null,
|
|
284
|
+
lightTheme: pref.lightTheme || null, darkTheme: pref.darkTheme || null } };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// A pane iframe mirrors the parent's live appearance. The server remains the
|
|
288
|
+
// only persisted source, so this applies the view without saving anything.
|
|
289
|
+
export function applyExternalAppearance(snapshot) {
|
|
290
|
+
externalThemeId = snapshot.themeId;
|
|
291
|
+
store.set({ appearancePreference: snapshot.preference, appearanceDraft: null });
|
|
292
|
+
applyTheme(snapshot.themeId);
|
|
293
|
+
}
|
|
294
|
+
|
|
277
295
|
export function getThemes() {
|
|
278
296
|
// Return a copy
|
|
279
297
|
var all = {};
|
|
@@ -367,7 +385,7 @@ function loadThemes() {
|
|
|
367
385
|
|
|
368
386
|
// Always apply the current theme now that real data is loaded
|
|
369
387
|
// (before this, only defaultExactVars was used as fallback)
|
|
370
|
-
applyTheme(currentThemeId);
|
|
388
|
+
applyTheme(externalThemeId && getTheme(externalThemeId) ? externalThemeId : currentThemeId);
|
|
371
389
|
}).catch(function () {
|
|
372
390
|
// API unavailable — keep the embedded Clay Studio pair.
|
|
373
391
|
themes[DEFAULT_DARK_THEME_ID] = defaultDarkFallback;
|
|
@@ -482,8 +500,13 @@ function updateToggleIcon() {
|
|
|
482
500
|
// User settings toggle
|
|
483
501
|
var usToggle = document.getElementById("us-theme-toggle");
|
|
484
502
|
if (usToggle) usToggle.checked = isLight;
|
|
485
|
-
|
|
486
|
-
|
|
503
|
+
var quickToggle = document.getElementById("appearance-mode-toggle");
|
|
504
|
+
if (quickToggle) {
|
|
505
|
+
var label = isLight ? "Switch to dark mode" : "Switch to light mode";
|
|
506
|
+
quickToggle.setAttribute("aria-label", label);
|
|
507
|
+
quickToggle.title = label;
|
|
508
|
+
quickToggle.innerHTML = '<i data-lucide="' + (isLight ? "moon" : "sun") + '" class="cursor-share-icon" aria-hidden="true"></i>';
|
|
509
|
+
}
|
|
487
510
|
refreshIcons();
|
|
488
511
|
}
|
|
489
512
|
|
|
@@ -649,6 +672,9 @@ export function initTheme() {
|
|
|
649
672
|
}
|
|
650
673
|
}
|
|
651
674
|
|
|
675
|
+
var quickToggle = document.getElementById("appearance-mode-toggle");
|
|
676
|
+
if (quickToggle) quickToggle.addEventListener("click", toggleDarkMode);
|
|
677
|
+
|
|
652
678
|
// Set initial toggle icon
|
|
653
679
|
updateToggleIcon();
|
|
654
680
|
|
|
@@ -711,6 +737,8 @@ async function loadAppearance() {
|
|
|
711
737
|
if (!response.ok) throw new Error('Appearance preferences unavailable.');
|
|
712
738
|
var data = await response.json();
|
|
713
739
|
if(store.get('appearanceSaveRequest'))return;
|
|
740
|
+
// A pane that already mirrors its parent's live view is newer than this GET, which may predate a pending save.
|
|
741
|
+
if(externalThemeId)return;
|
|
714
742
|
if (data.configured) {store.set({appearancePreference:data});applyTheme(themeIdForMode(data.mode || getSystemMode()));}
|
|
715
743
|
else {
|
|
716
744
|
var legacy = null;try {legacy = localStorage.getItem(STORAGE_KEY);}catch(error){}
|
|
@@ -9,6 +9,7 @@ import { openFile } from './filebrowser.js';
|
|
|
9
9
|
import { mateAvatarUrl } from './avatar.js';
|
|
10
10
|
import { getChatLayout } from './theme.js';
|
|
11
11
|
import { store } from './store.js';
|
|
12
|
+
import { currentPaneReview } from './pane-binding.js';
|
|
12
13
|
import { applyPermissionHints } from './permission-hints.js';
|
|
13
14
|
import { VENDOR_NAMES } from './app-rendering.js';
|
|
14
15
|
import { startThinkingSegment, appendThinkingText, stopThinkingSegment, resetThinkingTurn, clearThinkingState, saveThinkingState, restoreThinkingState, updateThinkingTokenEstimate } from './thinking-lifecycle.js';
|
|
@@ -394,12 +395,13 @@ export function renderAskUserQuestion(toolId, input) {
|
|
|
394
395
|
skipBtn.className = "ask-user-skip";
|
|
395
396
|
skipBtn.textContent = "Skip";
|
|
396
397
|
skipBtn.addEventListener("click", function () {
|
|
398
|
+
if (!currentPaneReview(container)) return;
|
|
397
399
|
if (container.classList.contains("answered")) return;
|
|
398
400
|
container.classList.add("answered");
|
|
399
401
|
markAskUserRequestAnswered(toolId);
|
|
400
402
|
enableMainInput();
|
|
401
403
|
if (ctx.ws && ctx.connected) {
|
|
402
|
-
ctx.ws.send(JSON.stringify({ type: "stop" }));
|
|
404
|
+
ctx.ws.send(JSON.stringify({ type: "stop", paneBinding: container.paneBinding || undefined }));
|
|
403
405
|
}
|
|
404
406
|
});
|
|
405
407
|
actions.appendChild(skipBtn);
|
|
@@ -419,6 +421,7 @@ export function renderAskUserQuestion(toolId, input) {
|
|
|
419
421
|
container.appendChild(mateContentWrap);
|
|
420
422
|
}
|
|
421
423
|
|
|
424
|
+
container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
|
|
422
425
|
ctx.addToMessages(container);
|
|
423
426
|
disableMainInput();
|
|
424
427
|
ctx.setActivity(null);
|
|
@@ -436,6 +439,7 @@ export function enableMainInput() {
|
|
|
436
439
|
}
|
|
437
440
|
|
|
438
441
|
function submitAskUserAnswer(container, toolId, questions, answers, multiSelections) {
|
|
442
|
+
if (!currentPaneReview(container)) return;
|
|
439
443
|
if (container.classList.contains("answered") || container.dataset.askUserSubmitted === "true") return;
|
|
440
444
|
|
|
441
445
|
var result = {};
|
|
@@ -461,6 +465,7 @@ function submitAskUserAnswer(container, toolId, questions, answers, multiSelecti
|
|
|
461
465
|
if (ctx.ws && ctx.connected) {
|
|
462
466
|
ctx.ws.send(JSON.stringify({
|
|
463
467
|
type: "ask_user_response",
|
|
468
|
+
paneBinding: container.paneBinding || undefined,
|
|
464
469
|
toolId: toolId,
|
|
465
470
|
answers: result,
|
|
466
471
|
}));
|
|
@@ -655,6 +660,7 @@ function renderFormalPermission(requestId, toolName, toolInput, decisionReason)
|
|
|
655
660
|
container.appendChild(header);
|
|
656
661
|
container.appendChild(body);
|
|
657
662
|
container.appendChild(actions);
|
|
663
|
+
container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
|
|
658
664
|
ctx.addToMessages(container);
|
|
659
665
|
|
|
660
666
|
pendingPermissions[requestId] = container;
|
|
@@ -762,6 +768,7 @@ function renderPlanPermission(requestId) {
|
|
|
762
768
|
container.appendChild(body);
|
|
763
769
|
container.appendChild(actions);
|
|
764
770
|
container.appendChild(feedbackRow);
|
|
771
|
+
container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
|
|
765
772
|
ctx.addToMessages(container);
|
|
766
773
|
|
|
767
774
|
pendingPermissions[requestId] = container;
|
|
@@ -769,10 +776,11 @@ function renderPlanPermission(requestId) {
|
|
|
769
776
|
ctx.setActivity(null);
|
|
770
777
|
maybeScrollToBottom();
|
|
771
778
|
// Focus the feedback input after render
|
|
772
|
-
setTimeout(function () { feedbackInput.focus(); }, 50);
|
|
779
|
+
setTimeout(function () { if (currentPaneReview(container)) feedbackInput.focus(); }, 50);
|
|
773
780
|
}
|
|
774
781
|
|
|
775
782
|
function sendPlanResponse(container, requestId, decision, feedback) {
|
|
783
|
+
if (!currentPaneReview(container)) return;
|
|
776
784
|
if (container.classList.contains("resolved")) return;
|
|
777
785
|
container.classList.add("resolved");
|
|
778
786
|
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
@@ -800,6 +808,7 @@ function sendPlanResponse(container, requestId, decision, feedback) {
|
|
|
800
808
|
if (ctx.ws && ctx.connected) {
|
|
801
809
|
var payload = {
|
|
802
810
|
type: "permission_response",
|
|
811
|
+
paneBinding: container.paneBinding || undefined,
|
|
803
812
|
requestId: requestId,
|
|
804
813
|
decision: decision,
|
|
805
814
|
};
|
|
@@ -954,6 +963,7 @@ function renderConversationalPermission(requestId, toolName, toolInput, mateId,
|
|
|
954
963
|
|
|
955
964
|
container.appendChild(content);
|
|
956
965
|
|
|
966
|
+
container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
|
|
957
967
|
ctx.addToMessages(container);
|
|
958
968
|
pendingPermissions[requestId] = container;
|
|
959
969
|
refreshIcons();
|
|
@@ -962,6 +972,7 @@ function renderConversationalPermission(requestId, toolName, toolInput, mateId,
|
|
|
962
972
|
}
|
|
963
973
|
|
|
964
974
|
function sendPermissionResponse(container, requestId, decision) {
|
|
975
|
+
if (!currentPaneReview(container)) return;
|
|
965
976
|
if (container.classList.contains("resolved")) return;
|
|
966
977
|
container.classList.add("resolved");
|
|
967
978
|
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
@@ -979,6 +990,7 @@ function sendPermissionResponse(container, requestId, decision) {
|
|
|
979
990
|
if (ctx.ws && ctx.connected) {
|
|
980
991
|
ctx.ws.send(JSON.stringify({
|
|
981
992
|
type: "permission_response",
|
|
993
|
+
paneBinding: container.paneBinding || undefined,
|
|
982
994
|
requestId: requestId,
|
|
983
995
|
decision: decision,
|
|
984
996
|
}));
|
|
@@ -1053,6 +1065,7 @@ export function renderElicitationRequest(msg) {
|
|
|
1053
1065
|
var container = buildElicitationCard(msg, function () {
|
|
1054
1066
|
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
1055
1067
|
});
|
|
1068
|
+
container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
|
|
1056
1069
|
ctx.addToMessages(container);
|
|
1057
1070
|
pendingElicitations[msg.requestId] = container;
|
|
1058
1071
|
refreshIcons();
|
|
@@ -1079,6 +1092,7 @@ export function markElicitationError(requestId, error, expired) {
|
|
|
1079
1092
|
var pendingUserDialogs = {};
|
|
1080
1093
|
|
|
1081
1094
|
function sendUserDialogResponse(container, requestId, behavior, result) {
|
|
1095
|
+
if (!currentPaneReview(container)) return;
|
|
1082
1096
|
if (container.classList.contains("resolved")) return;
|
|
1083
1097
|
container.classList.add("resolved");
|
|
1084
1098
|
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
@@ -1094,6 +1108,7 @@ function sendUserDialogResponse(container, requestId, behavior, result) {
|
|
|
1094
1108
|
if (ctx.ws && ctx.connected) {
|
|
1095
1109
|
var msg = {
|
|
1096
1110
|
type: "user_dialog_response",
|
|
1111
|
+
paneBinding: container.paneBinding || undefined,
|
|
1097
1112
|
requestId: requestId,
|
|
1098
1113
|
behavior: isCancel ? "cancelled" : "completed",
|
|
1099
1114
|
};
|
|
@@ -1185,6 +1200,7 @@ export function renderUserDialogRequest(msg) {
|
|
|
1185
1200
|
container.appendChild(header);
|
|
1186
1201
|
container.appendChild(body);
|
|
1187
1202
|
container.appendChild(actions);
|
|
1203
|
+
container.paneBinding = store.get("panePendingBinding") || store.get("paneCommittedBinding") || null;
|
|
1188
1204
|
ctx.addToMessages(container);
|
|
1189
1205
|
|
|
1190
1206
|
pendingUserDialogs[msg.requestId] = container;
|
|
@@ -54,7 +54,8 @@ export function initPersonalIntegrations() {
|
|
|
54
54
|
if (!store.get('personalLinearDirty')) { action(); return; }
|
|
55
55
|
confirmSettingsAction('Discard this unsaved connection draft?', 'Discard', action);
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
// Only an open editor holds a draft; autofill or restore into the hidden form is not an edit.
|
|
58
|
+
form.oninput = function () { if (!store.get('personalLinearEditing')) return; store.set({personalLinearDirty: !!key.value || name.value !== store.get('personalLinearOriginalName')}); };
|
|
58
59
|
async function save(disconnect) {
|
|
59
60
|
if (store.get('personalLinearSaving')) return;
|
|
60
61
|
var actor = store.get('myUserId'), generation = store.get('personalLinearGeneration');
|
|
@@ -4,15 +4,9 @@ import { store } from './store.js';
|
|
|
4
4
|
import { closeSpeechPicker } from './stt-picker.js';
|
|
5
5
|
import { loadSpeechSettings } from './stt-settings.js';
|
|
6
6
|
import { mountDefaultAi, unmountDefaultAi, requestDefaultAi } from './default-ai.js';
|
|
7
|
-
import {
|
|
7
|
+
import { resolvePage } from './settings-shell-registry.js';
|
|
8
8
|
|
|
9
|
-
export
|
|
10
|
-
['us-account', 'Account'], ['us-appearance', 'Appearance'], ['us-chat', 'Conversation'],
|
|
11
|
-
['us-notifications', 'Notifications'], ['us-ai', 'AI preferences'], ['us-integrations', 'Integrations']
|
|
12
|
-
];
|
|
13
|
-
export function resolveUserSettingsPage(id) {
|
|
14
|
-
return ({'us-mates':'us-chat','us-experimental':'us-chat','us-connections':'us-integrations','us-email':'us-integrations','us-logout':'us-account'})[id] || id;
|
|
15
|
-
}
|
|
9
|
+
export function resolveUserSettingsPage(id) { return resolvePage('personal', id); }
|
|
16
10
|
function section(id) { return document.querySelector('#user-settings .us-section[data-section="' + id + '"]'); }
|
|
17
11
|
function heading(target, text, copy) {
|
|
18
12
|
var title = document.createElement('h3'); title.textContent = text; target.appendChild(title);
|
|
@@ -20,15 +14,6 @@ function heading(target, text, copy) {
|
|
|
20
14
|
}
|
|
21
15
|
export function buildUserSettingsLayout() {
|
|
22
16
|
var root = document.getElementById('user-settings');
|
|
23
|
-
root.querySelector('.us-modal').setAttribute('role','dialog');root.querySelector('.us-modal').setAttribute('aria-modal','true');root.querySelector('.us-modal').setAttribute('aria-label','User settings');
|
|
24
|
-
var nav = root.querySelector('.us-modal-nav-items'); nav.replaceChildren();
|
|
25
|
-
var dropdown = document.getElementById('user-settings-nav-dropdown'); dropdown.replaceChildren();
|
|
26
|
-
USER_SETTINGS_PAGES.forEach(function (page) {
|
|
27
|
-
var button = document.createElement('button'); button.className = 'us-nav-item'; button.dataset.section = page[0]; button.textContent = page[1]; button.type = 'button'; nav.appendChild(button);
|
|
28
|
-
var option = document.createElement('option'); option.value = page[0]; option.textContent = page[1]; dropdown.appendChild(option);
|
|
29
|
-
});
|
|
30
|
-
dropdown.setAttribute('aria-label', 'Personal settings page');
|
|
31
|
-
root.querySelector('.us-modal-nav').setAttribute('aria-label', 'Personal settings pages');
|
|
32
17
|
var notifications=section('us-notifications');
|
|
33
18
|
var push=document.getElementById('settings-notif-push').closest('label');
|
|
34
19
|
heading(notifications,'This device','Browser permission and push delivery are configured separately on each browser.');notifications.appendChild(push);
|
|
@@ -54,36 +39,29 @@ export function buildUserSettingsLayout() {
|
|
|
54
39
|
document.getElementById('us-logout-btn').closest('.settings-card').classList.remove('settings-card-danger');
|
|
55
40
|
document.getElementById('us-pin-set-btn').parentElement.querySelector('.settings-hint').textContent = 'Change your personal account PIN. Server sign-in policy is managed separately by an administrator.';
|
|
56
41
|
root.querySelectorAll('.layout-option-icon').forEach(function (el) {el.remove();});
|
|
57
|
-
var back = document.createElement('button');back.type = 'button';back.className = 'settings-btn-sm us-back';back.textContent = '← Settings';root.querySelector('.us-modal-content').prepend(back);
|
|
58
|
-
back.onclick = function () {root.classList.add('us-show-navigation');nav.querySelector('.active').focus();};
|
|
59
|
-
nav.addEventListener('keydown', function (event) {
|
|
60
|
-
if (['ArrowDown','ArrowUp','Home','End'].indexOf(event.key) < 0) return;
|
|
61
|
-
var items = Array.from(nav.querySelectorAll('button'));var index = items.indexOf(document.activeElement);if(index < 0)return;
|
|
62
|
-
event.preventDefault();index = event.key === 'Home' ? 0 : event.key === 'End' ? items.length-1 : (index+(event.key === 'ArrowDown'?1:-1)+items.length)%items.length;items[index].focus();
|
|
63
|
-
});
|
|
64
42
|
}
|
|
65
43
|
export function loadUserSettingsExtras(page) {
|
|
66
|
-
document.getElementById('user-settings').classList.remove('us-show-navigation');
|
|
67
44
|
if (page === 'us-integrations') loadPersonalIntegrations();
|
|
68
45
|
if (page === 'us-chat') loadSpeechSettings();
|
|
69
46
|
if (page === 'us-ai') { mountDefaultAi(document.getElementById('us-default-ai')); requestDefaultAi(); var ws=getWs();if(ws && ws.readyState===1)ws.send(JSON.stringify({type:'get_claude_allow_list'})); }
|
|
70
47
|
}
|
|
71
48
|
export function finishUserSettingsExtras() {closeSpeechPicker();unmountDefaultAi(document.getElementById('us-default-ai'));}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function leaving(id) {return !destination || page === id;}
|
|
49
|
+
// Returns the draft prompt for leaving one personal page (or every page when
|
|
50
|
+
// pageId is omitted); the shell shows it with the shared confirmation.
|
|
51
|
+
export function personalLeavePrompt(pageId) {
|
|
52
|
+
function leaving(id) {return !pageId || pageId === id;}
|
|
77
53
|
var pin = document.getElementById('us-pin-form');
|
|
78
54
|
var pinDirty = pin && !pin.classList.contains('hidden') && Array.from(pin.querySelectorAll('input')).some(function (input) {return !!input.value;});
|
|
55
|
+
// A value left in the hidden key field (browser autofill or form restore) is
|
|
56
|
+
// not an edit; only an open connection editor can hold a draft key.
|
|
79
57
|
var key = document.getElementById('us-linear-key');
|
|
80
|
-
|
|
81
|
-
var dirty = leaving('us-integrations') && (store.get('personalEmailDirty') || store.get('personalLinearDirty') ||
|
|
58
|
+
var keyDraft = key && key.value && store.get('personalLinearEditing') && !(key.form && key.form.hidden);
|
|
59
|
+
var dirty = leaving('us-integrations') && (store.get('personalEmailDirty') || store.get('personalLinearDirty') || keyDraft) ||
|
|
82
60
|
leaving('us-chat') && store.get('userVoiceDirty') ||
|
|
83
61
|
leaving('us-notifications') && store.get('notificationDraft') ||
|
|
84
62
|
leaving('us-appearance') && (store.get('appearanceDraft') || store.get('userTerminalDirty')) ||
|
|
85
63
|
leaving('us-account') && (store.get('userProfileDirty') || pinDirty) ||
|
|
86
64
|
leaving('us-ai') && (store.get('defaultAiDraftDirty') || store.get('userAllowDirty'));
|
|
87
|
-
if (!dirty)
|
|
88
|
-
|
|
65
|
+
if (!dirty) return null;
|
|
66
|
+
return { message: 'Your unsaved changes will remain in this tab. Leave this editor and keep the draft?', action: 'Keep draft and leave' };
|
|
89
67
|
}
|
|
@@ -55,7 +55,7 @@ export function initPersonalProfile() {
|
|
|
55
55
|
URL.revokeObjectURL(url); status.textContent = 'Uploading picture…';
|
|
56
56
|
uploadAvatarBlob(blob).then(function () { status.textContent = 'Picture updated.'; populateAccount(); avatar.focus(); })
|
|
57
57
|
.catch(function (error) { status.textContent = error.message; status.dataset.state = 'error'; });
|
|
58
|
-
});
|
|
58
|
+
}, document.querySelector('#user-settings .us-section[data-section="us-account"]'));
|
|
59
59
|
};
|
|
60
60
|
image.onerror = function () { URL.revokeObjectURL(url); status.textContent = 'Could not read that image. Choose another file.'; };
|
|
61
61
|
image.src = url; file.value = '';
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { initPersonalAppearance } from './user-settings-appearance.js';
|
|
2
2
|
import { initAccountSessions, resetAccountSessions } from './user-settings-sessions.js';
|
|
3
3
|
import { initPersonalVoice } from './user-settings-voice.js';
|
|
4
|
-
import {
|
|
4
|
+
import { registerSettingsScope, openSettings, closeSettings, isSettingsShellOpen, activeSettingsScope } from './settings-shell.js';
|
|
5
5
|
import { initPersonalProfile } from './user-settings-profile.js';
|
|
6
6
|
import { initPersonalEditors } from './user-settings-editors.js';
|
|
7
7
|
import { initPersonalPreferences } from './user-settings-preferences.js';
|
|
8
8
|
import { showPinForm, hidePinForm, populateAccount, savePin, renderEmailSettings } from './user-settings-account.js';
|
|
9
|
-
import { buildUserSettingsLayout, resolveUserSettingsPage, loadUserSettingsExtras, finishUserSettingsExtras,
|
|
9
|
+
import { buildUserSettingsLayout, resolveUserSettingsPage, loadUserSettingsExtras, finishUserSettingsExtras, personalLeavePrompt } from './user-settings-layout.js';
|
|
10
10
|
import { enhanceSettingsSelects } from './settings-select.js';
|
|
11
11
|
import { loadNotificationSettings } from './notification-settings.js';
|
|
12
|
-
// user-settings.js —
|
|
12
|
+
// user-settings.js — Personal settings pane of the unified settings shell
|
|
13
13
|
// Account management and logout
|
|
14
14
|
|
|
15
15
|
import { refreshIcons } from './icons.js';
|
|
@@ -18,10 +18,6 @@ import { store } from './store.js';
|
|
|
18
18
|
import { initCapsulePreference } from './capsule-preference.js';
|
|
19
19
|
|
|
20
20
|
var settingsEl = null;
|
|
21
|
-
var openBtn = null;
|
|
22
|
-
var closeBtn = null;
|
|
23
|
-
var backdrop = null;
|
|
24
|
-
var navItems = null;
|
|
25
21
|
var sections = null;
|
|
26
22
|
var pinForm = null;
|
|
27
23
|
var pinSetBtn = null;
|
|
@@ -32,61 +28,20 @@ export function initUserSettings() {
|
|
|
32
28
|
buildUserSettingsLayout();
|
|
33
29
|
initAccountSessions();
|
|
34
30
|
settingsEl = document.getElementById('user-settings');
|
|
35
|
-
|
|
36
|
-
closeBtn = document.getElementById('user-settings-close');
|
|
37
|
-
backdrop = document.getElementById('user-settings-backdrop');
|
|
38
|
-
|
|
39
|
-
if (!settingsEl || !openBtn) return;
|
|
31
|
+
if (!settingsEl) return;
|
|
40
32
|
|
|
41
33
|
initCapsulePreference();
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
navItems = settingsEl.querySelectorAll('.us-nav-item');
|
|
45
35
|
sections = settingsEl.querySelectorAll('.us-section');
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
36
|
+
registerSettingsScope('personal', {
|
|
37
|
+
pane: settingsEl,
|
|
38
|
+
enter: function () { populateAccount(); },
|
|
39
|
+
activate: function (page) { switchSection(page); },
|
|
40
|
+
leave: function () { resetAccountSessions(); finishUserSettingsExtras(); },
|
|
41
|
+
busy: function () { return store.get('speechPopup') ? 'Finish or close the open voice settings editor first.' : ''; },
|
|
42
|
+
leavePrompt: function (kind, pageId) { return personalLeavePrompt(kind === 'page' ? pageId : undefined); }
|
|
50
43
|
});
|
|
51
|
-
|
|
52
|
-
if (closeBtn) {
|
|
53
|
-
closeBtn.addEventListener('click', function () {
|
|
54
|
-
closeUserSettings();
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (backdrop) {
|
|
59
|
-
backdrop.addEventListener('click', function () {
|
|
60
|
-
closeUserSettings();
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
document.addEventListener('keydown', function (e) {
|
|
65
|
-
if (e.key === 'Tab' && isUserSettingsOpen() && settingsEl.contains(document.activeElement)) {
|
|
66
|
-
var items = Array.from(settingsEl.querySelectorAll('button:not(:disabled),input:not(:disabled),select:not(:disabled),textarea:not(:disabled),a[href]')).filter(function (el) {return el.getClientRects().length && el.tabIndex !== -1;});
|
|
67
|
-
var first=items[0],last=items[items.length-1];
|
|
68
|
-
if(e.shiftKey && document.activeElement===first){e.preventDefault();last.focus();}else if(!e.shiftKey && document.activeElement===last){e.preventDefault();first.focus();}
|
|
69
|
-
}
|
|
70
|
-
if (e.key === 'Escape' && isUserSettingsOpen() && !e.defaultPrevented && !store.get('speechPopup') && !document.querySelector('.admin-modal-overlay, #settings-confirm, .avatar-positioner-overlay, #email-setup-modal')) {
|
|
71
|
-
e.preventDefault();closeUserSettings();
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
for (var i = 0; i < navItems.length; i++) {
|
|
76
|
-
navItems[i].addEventListener('click', function () {
|
|
77
|
-
var section = this.dataset.section;
|
|
78
|
-
guardUserSettingsLeave(function () { switchSection(section); }, section);
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Mobile nav dropdown
|
|
83
|
-
var navDropdown = document.getElementById('user-settings-nav-dropdown');
|
|
84
|
-
if (navDropdown) {
|
|
85
|
-
navDropdown.addEventListener('change', function () {
|
|
86
|
-
var value = this.value; guardUserSettingsLeave(function () {switchSection(value);}, value);
|
|
87
|
-
this.value = settingsEl.querySelector('.us-section.active').dataset.section;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
44
|
+
pinForm = document.getElementById('us-pin-form');
|
|
90
45
|
|
|
91
46
|
// PIN save button
|
|
92
47
|
var pinInput = document.getElementById('us-pin-input');
|
|
@@ -190,51 +145,29 @@ export function initUserSettings() {
|
|
|
190
145
|
});
|
|
191
146
|
}
|
|
192
147
|
|
|
193
|
-
// Optional `initialSection`
|
|
194
|
-
//
|
|
195
|
-
// Falls back to Account so existing callers and the icon click are unchanged.
|
|
148
|
+
// Optional `initialSection` deep-links into a personal page (e.g. the
|
|
149
|
+
// disabled-Mates picker passes 'us-mates'). Defaults to Account.
|
|
196
150
|
export function openUserSettings(initialSection) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
settingsEl.classList.remove('hidden');
|
|
200
|
-
openBtn.classList.add('active');
|
|
201
|
-
refreshIcons(settingsEl);
|
|
202
|
-
populateAccount();
|
|
203
|
-
switchSection(initialSection || 'us-account');
|
|
204
|
-
enhanceSettingsSelects(settingsEl);
|
|
205
|
-
var title=settingsEl.querySelector(".us-section.active h2"); if(title){title.tabIndex=-1;title.focus();}
|
|
151
|
+
return openSettings({ scope: 'personal', page: initialSection || 'us-account' });
|
|
206
152
|
}
|
|
207
153
|
|
|
208
154
|
export function closeUserSettings() {
|
|
209
|
-
|
|
210
|
-
resetAccountSessions();
|
|
211
|
-
finishUserSettingsExtras();
|
|
212
|
-
settingsEl.classList.add('hidden');
|
|
213
|
-
var restore=store.get('userSettingsRestore');if(restore)restore();store.set({userSettingsRestore:null});
|
|
214
|
-
openBtn.classList.remove('active');
|
|
215
|
-
var focus = store.get('userSettingsReturnFocus'); if (focus && focus.isConnected) focus.focus();
|
|
216
|
-
});
|
|
155
|
+
if (isUserSettingsOpen()) closeSettings();
|
|
217
156
|
}
|
|
218
157
|
|
|
219
158
|
export function isUserSettingsOpen() {
|
|
220
|
-
return
|
|
159
|
+
return isSettingsShellOpen() && activeSettingsScope() === 'personal';
|
|
221
160
|
}
|
|
222
161
|
|
|
223
162
|
function switchSection(sectionName) {
|
|
224
163
|
sectionName = resolveUserSettingsPage(sectionName);
|
|
225
164
|
loadUserSettingsExtras(sectionName);
|
|
226
|
-
for (var i = 0; i < navItems.length; i++) {
|
|
227
|
-
navItems[i].classList.toggle('active', navItems[i].dataset.section === sectionName);
|
|
228
|
-
if(navItems[i].dataset.section===sectionName)navItems[i].setAttribute('aria-current','page');else navItems[i].removeAttribute('aria-current');
|
|
229
|
-
}
|
|
230
165
|
for (var j = 0; j < sections.length; j++) {
|
|
231
166
|
sections[j].classList.toggle('active', sections[j].dataset.section === sectionName);
|
|
232
167
|
}
|
|
233
|
-
var navDropdown = document.getElementById('user-settings-nav-dropdown');
|
|
234
|
-
if (navDropdown) navDropdown.value = sectionName;
|
|
235
168
|
if (sectionName === 'us-notifications') loadNotificationSettings();
|
|
236
169
|
if (sectionName === 'us-integrations') renderEmailSettings();
|
|
237
|
-
|
|
170
|
+
enhanceSettingsSelects(settingsEl); refreshIcons(settingsEl);
|
|
238
171
|
}
|
|
239
172
|
|
|
240
173
|
function stopProp(e) {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// vendor-usage-format.js - Pure formatting of account usage metrics.
|
|
2
|
+
//
|
|
3
|
+
// Each unit is shown in its own terms. Counts, credits, currency and unnamed
|
|
4
|
+
// amounts are never turned into a percentage, and a missing field is omitted,
|
|
5
|
+
// never shown as 0.
|
|
6
|
+
|
|
7
|
+
var WARNING_PERCENT = 80;
|
|
8
|
+
|
|
9
|
+
function finite(value) {
|
|
10
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function plain(value) {
|
|
14
|
+
if (!finite(value)) return "";
|
|
15
|
+
var rounded = Math.abs(value) >= 100 ? Math.round(value) : Math.round(value * 100) / 100;
|
|
16
|
+
return rounded.toLocaleString("en-US");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function money(value, code) {
|
|
20
|
+
if (!finite(value)) return "";
|
|
21
|
+
try {
|
|
22
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency: code }).format(value);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
return plain(value) + " " + code;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function windowText(mins) {
|
|
29
|
+
if (!finite(mins) || mins <= 0) return "";
|
|
30
|
+
if (mins % 1440 === 0) return (mins / 1440) + "d";
|
|
31
|
+
if (mins % 60 === 0) return (mins / 60) + "h";
|
|
32
|
+
return mins + "m";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// One metric -> { label, value, headline, percent, warning, unit }.
|
|
36
|
+
// `value` is the full detail text; `headline` is the short chip text.
|
|
37
|
+
export function formatMetric(metric) {
|
|
38
|
+
var label = metric.label || "Usage";
|
|
39
|
+
var win = windowText(metric.windowMins);
|
|
40
|
+
if (win && label.indexOf(win) === -1) label += " (" + win + ")";
|
|
41
|
+
var out = { id: metric.id, unit: metric.unit, label: label, value: "", headline: "", percent: null, warning: false, resetsAtMs: finite(metric.resetsAtMs) ? metric.resetsAtMs : null };
|
|
42
|
+
if (metric.unit === "percent") {
|
|
43
|
+
out.percent = metric.usedPercent;
|
|
44
|
+
out.value = Math.round(metric.usedPercent) + "% used";
|
|
45
|
+
out.headline = Math.round(metric.usedPercent) + "%";
|
|
46
|
+
out.warning = metric.usedPercent >= WARNING_PERCENT;
|
|
47
|
+
} else if (metric.unit === "count" || metric.unit === "amount") {
|
|
48
|
+
var unitWord = metric.unit === "count" ? "" : " (unit not stated)";
|
|
49
|
+
if (finite(metric.used) && finite(metric.limit)) {
|
|
50
|
+
out.value = plain(metric.used) + " of " + plain(metric.limit) + " used" + unitWord;
|
|
51
|
+
out.headline = plain(metric.used) + "/" + plain(metric.limit);
|
|
52
|
+
out.warning = metric.limit > 0 && metric.used / metric.limit * 100 >= WARNING_PERCENT;
|
|
53
|
+
} else if (finite(metric.used)) {
|
|
54
|
+
out.value = plain(metric.used) + " used" + unitWord;
|
|
55
|
+
out.headline = plain(metric.used);
|
|
56
|
+
} else {
|
|
57
|
+
out.value = plain(metric.remaining) + " left" + unitWord;
|
|
58
|
+
out.headline = plain(metric.remaining) + " left";
|
|
59
|
+
}
|
|
60
|
+
if (finite(metric.remaining) && finite(metric.used) && finite(metric.limit)) out.value += ", " + plain(metric.remaining) + " left";
|
|
61
|
+
} else if (metric.unit === "credits") {
|
|
62
|
+
if (metric.unlimited) { out.headline = "Unlimited"; out.value = "Unlimited credits"; }
|
|
63
|
+
else if (finite(metric.used) && finite(metric.limit)) {
|
|
64
|
+
out.headline = plain(metric.used) + "/" + plain(metric.limit);
|
|
65
|
+
out.value = plain(metric.used) + " of " + plain(metric.limit) + " credits used";
|
|
66
|
+
out.warning = metric.limit > 0 && metric.used / metric.limit * 100 >= WARNING_PERCENT;
|
|
67
|
+
} else if (finite(metric.used)) { out.headline = plain(metric.used); out.value = plain(metric.used) + " credits used"; }
|
|
68
|
+
else { out.headline = plain(metric.balance); out.value = plain(metric.balance) + " credits"; }
|
|
69
|
+
} else if (metric.unit === "currency") {
|
|
70
|
+
var code = metric.currencyCode;
|
|
71
|
+
if (finite(metric.used) && finite(metric.limit)) {
|
|
72
|
+
out.value = money(metric.used, code) + " of " + money(metric.limit, code) + " used";
|
|
73
|
+
out.headline = money(metric.used, code);
|
|
74
|
+
} else if (finite(metric.used)) {
|
|
75
|
+
out.value = money(metric.used, code) + " used";
|
|
76
|
+
out.headline = money(metric.used, code);
|
|
77
|
+
} else {
|
|
78
|
+
out.value = money(metric.balance, code) + " balance";
|
|
79
|
+
out.headline = money(metric.balance, code);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The metric that best summarises a vendor: the highest percent when any
|
|
86
|
+
// metric is a percent, otherwise the first reported.
|
|
87
|
+
export function pickHeadline(formatted) {
|
|
88
|
+
var best = null;
|
|
89
|
+
formatted.forEach(function (item) {
|
|
90
|
+
if (item.percent !== null && (best === null || best.percent === null || item.percent > best.percent)) best = item;
|
|
91
|
+
});
|
|
92
|
+
return best || formatted[0] || null;
|
|
93
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// vendor-usage-list.js - Compact "all vendors" list for the top-bar usage
|
|
2
|
+
// overview. One two-line row per installed vendor: name, where its figure
|
|
3
|
+
// comes from, the figure, then either the metrics or the honest reason there
|
|
4
|
+
// is none. Presentational only; views come from vendor-usage-model.js.
|
|
5
|
+
|
|
6
|
+
var MAX_DETAIL_METRICS = 3;
|
|
7
|
+
|
|
8
|
+
function sourceLabel(view) {
|
|
9
|
+
if (view.mode === "unintegrated" || view.mode === "unsupported") return "Not integrated";
|
|
10
|
+
if (view.accountCount > 0) return "Account";
|
|
11
|
+
if (view.observedCount > 0) return "Session";
|
|
12
|
+
return view.mode === "query" ? "Account" : "Session";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function detailText(view) {
|
|
16
|
+
if ((view.mode === "unintegrated" || view.mode === "unsupported") && view.capability && view.capability.reason) return view.capability.reason;
|
|
17
|
+
if (!view.known) return view.status || "Usage is unknown, not zero.";
|
|
18
|
+
var parts = view.rows.slice(0, MAX_DETAIL_METRICS).map(function (row) {
|
|
19
|
+
var value = row.source === "account" ? row.value : (row.percentText || (row.resetText ? "resets in " + row.resetText : ""));
|
|
20
|
+
return row.label + ": " + value;
|
|
21
|
+
});
|
|
22
|
+
if (view.rows.length > MAX_DETAIL_METRICS) parts.push("+" + (view.rows.length - MAX_DETAIL_METRICS) + " more");
|
|
23
|
+
return parts.join(" · ");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function fillListPopover(container, views, el) {
|
|
27
|
+
container.textContent = "";
|
|
28
|
+
views.forEach(function (view) {
|
|
29
|
+
var row = el("div", "vendor-usage-list-row" + (view.warning ? " warning" : "") + (view.known ? "" : " unknown") + (view.stale ? " stale" : ""));
|
|
30
|
+
row.setAttribute("data-vendor", view.vendor);
|
|
31
|
+
var top = el("div", "vendor-usage-list-top");
|
|
32
|
+
if (view.icon) {
|
|
33
|
+
var icon = el("img", "usage-check-vendor-icon");
|
|
34
|
+
icon.alt = "";
|
|
35
|
+
icon.setAttribute("src", view.icon);
|
|
36
|
+
top.appendChild(icon);
|
|
37
|
+
}
|
|
38
|
+
top.appendChild(el("span", "vendor-usage-list-name", view.name));
|
|
39
|
+
top.appendChild(el("span", "vendor-usage-list-source", sourceLabel(view)));
|
|
40
|
+
top.appendChild(el("span", "vendor-usage-list-value", view.known ? view.text : (view.mode === "unintegrated" || view.mode === "unsupported" ? "—" : "Unknown")));
|
|
41
|
+
row.appendChild(top);
|
|
42
|
+
row.appendChild(el("div", "vendor-usage-list-detail", detailText(view)));
|
|
43
|
+
container.appendChild(row);
|
|
44
|
+
});
|
|
45
|
+
}
|