@iasiv5/dsh-quota-watch 0.1.1 → 0.1.3
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 +2 -2
- package/lib/client.js +104 -52
- package/package.json +1 -1
- package/src/client/prefs.mjs +4 -3
- package/src/client.mjs +125 -50
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
- **悬浮胶囊**(0.1.1 起为唯一浮动形态,圆球已退役):常驻屏幕右下角(距边 16px 起步),可整体拖拽到任意位置,位置跨重启记忆,越界自动钳回视口。胶囊一行显示「GLM 13% · Copilot 24%」,已用超过 80% 转橙、超过 95% 转红,任一 provider 超 95% 时胶囊轻微脉动(尊重系统「减少动态效果」设置,开启时不脉动);颜色使用 DSH 主题语义变量,自动适配浅色 / 深色主题。悬停提示与 aria-label 实时给出摘要。
|
|
10
10
|
- **面板**:点击球或胶囊展开浮动详情面板(半透明毛玻璃底,宽 `min(320px, 100vw−24px)`,靠边自动翻侧,滚动 / 缩放跟随),头部带标题与 ✕,Esc 或点击面板外关闭。概览列两个 provider 行,点击进入详情:GLM 为今日 Tokens / 调用次数双大数字 + 五列对齐窗口网格(MCP(月)→ 5 小时 → 每月 → 每周固定排序,周窗口未下发时显示「每周额度 · ♾️(无限)」占位)+ 分模型用量行;Copilot 为可用额度、本期已用(credits 消耗 + 弱化百分比)、重置时间三行。接口未下发有效 `remaining` / `percent_remaining` 时该行不渲染,`credits_used` 绝不用于推算余额。
|
|
11
|
-
-
|
|
12
|
-
- **右键菜单**:显示 / 隐藏侧边栏卡片 ·
|
|
11
|
+
- **侧边栏卡片(可选,默认隐藏)**:胶囊是默认界面;需要侧栏卡片时在胶囊右键菜单开启「显示侧边栏卡片」(Web profile 侧栏底部区块存在时才可选)。卡片显示时提供摘要行 + 行点击开面板 + 行首 provider 名称点击立即探测。侧栏收起(Web 窄栏)或容器过窄(<160px,跨 profile 通用判据)时卡片自动隐藏,侧栏整体不存在(Desktop 收起态)同样自然缺席、无报错——入口始终由悬浮胶囊承载。
|
|
12
|
+
- **右键菜单**:显示 / 隐藏侧边栏卡片 · 立即刷新。菜单项随上下文增减(无侧栏卡片挂载点时只剩刷新项)。
|
|
13
13
|
- 凭据缺失的 provider 不渲染摘要行与胶囊分段(两者皆缺时悬浮胶囊与卡片一并隐藏);探测失败时保留最后一次成功数据并在行上加 ⚠ 过期标记(⚠ 只出现在面板与卡片行内,不上球)。
|
|
14
14
|
- 宿主默认每 60 秒探测(窗口 + 用量 + Copilot);profile patch 可将 `pollIntervalSec` 设置在 30–3600 秒。浏览器页签可见时每 30 秒刷新视图,隐藏时暂停。
|
|
15
15
|
|
package/lib/client.js
CHANGED
|
@@ -117,14 +117,14 @@ function saveFloatGeometry(storage, point) {
|
|
|
117
117
|
function loadSurfaceFlags(storage) {
|
|
118
118
|
try {
|
|
119
119
|
const raw = storage.getItem(SURFACE_FLAGS_KEY);
|
|
120
|
-
if (typeof raw !== "string" || raw === "") return { cardHidden:
|
|
120
|
+
if (typeof raw !== "string" || raw === "") return { cardHidden: true };
|
|
121
121
|
const parsed = JSON.parse(raw);
|
|
122
122
|
if (typeof parsed !== "object" || parsed === null || typeof parsed.cardHidden !== "boolean") {
|
|
123
|
-
return { cardHidden:
|
|
123
|
+
return { cardHidden: true };
|
|
124
124
|
}
|
|
125
125
|
return { cardHidden: parsed.cardHidden };
|
|
126
126
|
} catch {
|
|
127
|
-
return { cardHidden:
|
|
127
|
+
return { cardHidden: true };
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
function saveSurfaceFlags(storage, flags) {
|
|
@@ -138,9 +138,11 @@ function saveSurfaceFlags(storage, flags) {
|
|
|
138
138
|
var name = "quota-watch-client";
|
|
139
139
|
var inject = [];
|
|
140
140
|
var CARD_SELECTOR = "[data-dsh-quota-watch-card]";
|
|
141
|
+
var FLOAT_SELECTOR = "[data-dsh-quota-watch-float]";
|
|
141
142
|
var FETCH_TIMEOUT_MS = 15e3;
|
|
142
143
|
var SURFACE_SIZE = 38;
|
|
143
144
|
var SURFACE_MARGIN = 16;
|
|
145
|
+
var CARD_MIN_WIDTH = 160;
|
|
144
146
|
var COPY = {
|
|
145
147
|
zh: {
|
|
146
148
|
title: "Token\u989D\u5EA6",
|
|
@@ -149,9 +151,7 @@ var COPY = {
|
|
|
149
151
|
menu: {
|
|
150
152
|
showCard: "\u663E\u793A\u4FA7\u8FB9\u680F\u5361\u7247",
|
|
151
153
|
hideCard: "\u9690\u85CF\u4FA7\u8FB9\u680F\u5361\u7247",
|
|
152
|
-
refreshNow: "\u7ACB\u5373\u5237\u65B0"
|
|
153
|
-
hideBall: "\u9690\u85CF\u60AC\u6D6E\u80F6\u56CA",
|
|
154
|
-
showBall: "\u663E\u793A\u60AC\u6D6E\u80F6\u56CA"
|
|
154
|
+
refreshNow: "\u7ACB\u5373\u5237\u65B0"
|
|
155
155
|
},
|
|
156
156
|
backToOverview: "\u8FD4\u56DE\u989D\u5EA6\u6982\u89C8",
|
|
157
157
|
providerNames: { glm: "GLM", copilot: "Copilot" },
|
|
@@ -184,9 +184,7 @@ var COPY = {
|
|
|
184
184
|
menu: {
|
|
185
185
|
showCard: "Show sidebar card",
|
|
186
186
|
hideCard: "Hide sidebar card",
|
|
187
|
-
refreshNow: "Refresh now"
|
|
188
|
-
hideBall: "Hide floating capsule",
|
|
189
|
-
showBall: "Show floating capsule"
|
|
187
|
+
refreshNow: "Refresh now"
|
|
190
188
|
},
|
|
191
189
|
backToOverview: "Back to quota overview",
|
|
192
190
|
providerNames: { glm: "GLM", copilot: "Copilot" },
|
|
@@ -236,11 +234,14 @@ var STYLE_TEXT = `
|
|
|
236
234
|
.dqw-chev { font-size: 9px; line-height: 14px; opacity: .6; flex: none; width: 10px; text-align: center; }
|
|
237
235
|
.dqw-stale-mark { flex: none; font-size: 9px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); cursor: help; }
|
|
238
236
|
.dqw-errtext { font-size: 10px; line-height: 14px; opacity: .7; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
239
|
-
.dqw-
|
|
240
|
-
.dqw-
|
|
241
|
-
.dqw-
|
|
242
|
-
.dqw-
|
|
243
|
-
.dqw-
|
|
237
|
+
.dqw-rail-trigger { display: none; flex: none; align-items: center; justify-content: center; width: 36px; height: 36px; margin: 0; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--dsw-alias-label-primary, inherit); cursor: pointer; transition: background-color .12s, color .12s; }
|
|
238
|
+
.dqw-rail-trigger svg { display: block; width: 16px; height: 16px; }
|
|
239
|
+
.dqw-rail-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
240
|
+
.dqw-rail-trigger:active:not(:disabled), .dqw-rail-trigger[aria-expanded="true"] { background: var(--dsw-alias-interactive-bg-active, rgba(128,128,128,.18)); color: var(--dsw-alias-label-primary, inherit); }
|
|
241
|
+
.dqw-rail-trigger:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #5b8def); outline-offset: 2px; }
|
|
242
|
+
:host([data-dsh-quota-watch-sidebar-collapsed]) .dqw-card { display: none; }
|
|
243
|
+
:host([data-dsh-quota-watch-sidebar-collapsed]) .dqw-rail-trigger { display: inline-flex; }
|
|
244
|
+
@media print { :host { display: none !important; } }
|
|
244
245
|
`;
|
|
245
246
|
var FLOAT_STYLE_TEXT = `
|
|
246
247
|
:host { position: fixed; z-index: 2147483000; }
|
|
@@ -251,15 +252,17 @@ var FLOAT_STYLE_TEXT = `
|
|
|
251
252
|
.dqw-capsule-glm, .dqw-capsule-copilot { font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
252
253
|
.dqw-capsule-glm.warn, .dqw-capsule-copilot.warn { color: var(--dsw-alias-label-warning, #d29922); }
|
|
253
254
|
.dqw-capsule-glm.danger, .dqw-capsule-copilot.danger { color: var(--dsw-alias-label-danger, #c93c3c); }
|
|
255
|
+
.dqw-capsule[hidden] { display: none; }
|
|
254
256
|
@media (prefers-reduced-motion: no-preference) {
|
|
255
257
|
.dqw-capsule[data-alert] { animation: dqw-pulse 1.6s ease-in-out infinite; }
|
|
256
258
|
}
|
|
259
|
+
@media print { :host { display: none !important; } }
|
|
257
260
|
@keyframes dqw-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
|
|
258
261
|
.dqw-menu { position: fixed; min-width: 150px; padding: 4px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 88%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); font: inherit; display: flex; flex-direction: column; }
|
|
259
262
|
.dqw-menu[hidden] { display: none; }
|
|
260
263
|
.dqw-menu-item { display: flex; align-items: center; gap: 6px; margin: 0; padding: 6px 10px; border: 0; border-radius: 6px; background: transparent; color: inherit; font: inherit; font-size: 11px; line-height: 16px; text-align: left; cursor: pointer; }
|
|
261
264
|
.dqw-menu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
262
|
-
.dqw-panel { position: fixed; width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
265
|
+
.dqw-panel { position: fixed; z-index: 2147483000; min-width: 166px; max-width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
263
266
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-panel { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
264
267
|
.dqw-panel[hidden] { display: none; }
|
|
265
268
|
.dqw-panel *, .dqw-panel *::before, .dqw-panel *::after { box-sizing: border-box; }
|
|
@@ -271,6 +274,7 @@ var FLOAT_STYLE_TEXT = `
|
|
|
271
274
|
.dqw-panel-close { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 24px; height: 24px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 12px; line-height: 1; }
|
|
272
275
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
273
276
|
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
277
|
+
.dqw-bar { background: var(--dsw-alias-bg-tertiary, rgba(128,128,128,.38)); }
|
|
274
278
|
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding: 3px 6px; margin: 0 -2px; border-radius: 8px; }
|
|
275
279
|
.dqw-row-summary[data-expandable] { cursor: pointer; }
|
|
276
280
|
.dqw-row-summary[data-expandable]:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.12)); }
|
|
@@ -465,7 +469,7 @@ function mountQuotaCard({
|
|
|
465
469
|
} = {}) {
|
|
466
470
|
if (!doc?.body || typeof fetchImpl !== "function") return () => {
|
|
467
471
|
};
|
|
468
|
-
if (doc.querySelector(CARD_SELECTOR)) return () => {
|
|
472
|
+
if (doc.querySelector(CARD_SELECTOR) || doc.querySelector(FLOAT_SELECTOR)) return () => {
|
|
469
473
|
};
|
|
470
474
|
const locale = localeFor(win);
|
|
471
475
|
const copy = COPY[locale];
|
|
@@ -485,15 +489,16 @@ function mountQuotaCard({
|
|
|
485
489
|
const lastUpdated = text(doc, "p", "dqw-meta", copy.loading);
|
|
486
490
|
lastUpdated.dataset.role = "updated";
|
|
487
491
|
body.append(lastUpdated);
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
492
|
+
const railTrigger = doc.createElement("button");
|
|
493
|
+
railTrigger.type = "button";
|
|
494
|
+
railTrigger.className = "dqw-rail-trigger";
|
|
495
|
+
railTrigger.dataset.dshQuotaWatchRail = "";
|
|
496
|
+
railTrigger.title = copy.title;
|
|
497
|
+
railTrigger.setAttribute("aria-label", copy.title);
|
|
498
|
+
railTrigger.setAttribute("aria-haspopup", "dialog");
|
|
499
|
+
railTrigger.setAttribute("aria-expanded", "false");
|
|
500
|
+
railTrigger.append(quotaIcon(doc));
|
|
501
|
+
card.append(body, railTrigger);
|
|
497
502
|
cardRoot.append(style, card);
|
|
498
503
|
const panel = doc.createElement("div");
|
|
499
504
|
panel.className = "dqw-panel";
|
|
@@ -523,12 +528,8 @@ function mountQuotaCard({
|
|
|
523
528
|
let surface = buildCapsule();
|
|
524
529
|
floatRoot.append(surface);
|
|
525
530
|
const syncFloatVisibility = () => {
|
|
526
|
-
surface.hidden =
|
|
531
|
+
surface.hidden = dataHidden;
|
|
527
532
|
floatHost.hidden = dataHidden;
|
|
528
|
-
ballToggle.setAttribute("data-ball-hidden", String(ballSessionHidden));
|
|
529
|
-
const toggleLabel = ballSessionHidden ? copy.menu.showBall : copy.menu.hideBall;
|
|
530
|
-
ballToggle.setAttribute("aria-label", toggleLabel);
|
|
531
|
-
ballToggle.title = toggleLabel;
|
|
532
533
|
};
|
|
533
534
|
doc.documentElement.append(floatHost);
|
|
534
535
|
const applyFloatGeometry = () => {
|
|
@@ -542,7 +543,6 @@ function mountQuotaCard({
|
|
|
542
543
|
);
|
|
543
544
|
floatHost.style.left = `${Math.round(point.x)}px`;
|
|
544
545
|
floatHost.style.top = `${Math.round(point.y)}px`;
|
|
545
|
-
saveFloatGeometry(win.localStorage, point);
|
|
546
546
|
};
|
|
547
547
|
applyFloatGeometry();
|
|
548
548
|
floatRoot.append(panel);
|
|
@@ -607,18 +607,8 @@ function mountQuotaCard({
|
|
|
607
607
|
let queuedPlace = false;
|
|
608
608
|
let openKey;
|
|
609
609
|
let panelNav = false;
|
|
610
|
-
let ballSessionHidden = false;
|
|
611
610
|
let dataHidden = false;
|
|
612
611
|
let surfaceFlags = loadSurfaceFlags(win.localStorage);
|
|
613
|
-
const setBallSessionHidden = (hidden) => {
|
|
614
|
-
const wasHidden = ballSessionHidden;
|
|
615
|
-
ballSessionHidden = hidden;
|
|
616
|
-
if (hidden && !wasHidden && openKey !== void 0) {
|
|
617
|
-
openKey = void 0;
|
|
618
|
-
syncPop();
|
|
619
|
-
}
|
|
620
|
-
syncFloatVisibility();
|
|
621
|
-
};
|
|
622
612
|
const setCardHidden = (hidden) => {
|
|
623
613
|
surfaceFlags = { cardHidden: hidden };
|
|
624
614
|
saveSurfaceFlags(win.localStorage, surfaceFlags);
|
|
@@ -813,9 +803,13 @@ function mountQuotaCard({
|
|
|
813
803
|
}
|
|
814
804
|
}
|
|
815
805
|
let lastAnchor = null;
|
|
806
|
+
const anchorFromRow = (key) => () => body.querySelector(`[data-dsh-quota-watch-row="${key}"]`);
|
|
807
|
+
const anchorFromEl = (el) => () => el;
|
|
816
808
|
const placePanel = () => {
|
|
817
|
-
if (panel.hidden
|
|
818
|
-
const
|
|
809
|
+
if (panel.hidden) return;
|
|
810
|
+
const anchor = lastAnchor?.();
|
|
811
|
+
if (!anchor || !anchor.isConnected) return;
|
|
812
|
+
const rect = anchor.getBoundingClientRect();
|
|
819
813
|
const viewWidth = win.innerWidth ?? 1024;
|
|
820
814
|
const viewHeight = win.innerHeight ?? 768;
|
|
821
815
|
const width = panel.offsetWidth || 240;
|
|
@@ -837,6 +831,7 @@ function mountQuotaCard({
|
|
|
837
831
|
lastAnchor = null;
|
|
838
832
|
panelNav = false;
|
|
839
833
|
surface.setAttribute("aria-expanded", "false");
|
|
834
|
+
railTrigger.setAttribute("aria-expanded", "false");
|
|
840
835
|
return;
|
|
841
836
|
}
|
|
842
837
|
if (openKey === "overview") {
|
|
@@ -847,6 +842,7 @@ function mountQuotaCard({
|
|
|
847
842
|
panel.hidden = false;
|
|
848
843
|
placePanel();
|
|
849
844
|
surface.setAttribute("aria-expanded", "true");
|
|
845
|
+
railTrigger.setAttribute("aria-expanded", "true");
|
|
850
846
|
return;
|
|
851
847
|
}
|
|
852
848
|
const provider = (snapshot?.providers ?? []).find((item) => item?.key === openKey);
|
|
@@ -863,6 +859,7 @@ function mountQuotaCard({
|
|
|
863
859
|
panel.hidden = false;
|
|
864
860
|
placePanel();
|
|
865
861
|
surface.setAttribute("aria-expanded", "true");
|
|
862
|
+
railTrigger.setAttribute("aria-expanded", "true");
|
|
866
863
|
};
|
|
867
864
|
const renderFloatFace = () => {
|
|
868
865
|
const providers = snapshot?.providers ?? [];
|
|
@@ -917,7 +914,6 @@ function mountQuotaCard({
|
|
|
917
914
|
items.push(menuItem(doc, "toggle-card", surfaceFlags.cardHidden ? copy.menu.showCard : copy.menu.hideCard));
|
|
918
915
|
}
|
|
919
916
|
items.push(menuItem(doc, "refresh", copy.menu.refreshNow));
|
|
920
|
-
items.push(menuItem(doc, "hide-ball", copy.menu.hideBall));
|
|
921
917
|
menu.replaceChildren(...items);
|
|
922
918
|
menu.hidden = false;
|
|
923
919
|
const rect = surface.getBoundingClientRect();
|
|
@@ -935,6 +931,14 @@ function mountQuotaCard({
|
|
|
935
931
|
event.preventDefault();
|
|
936
932
|
openMenu();
|
|
937
933
|
};
|
|
934
|
+
const onRailClick = () => {
|
|
935
|
+
if (openKey !== void 0 && !panel.hidden) {
|
|
936
|
+
openKey = void 0;
|
|
937
|
+
syncPop();
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
openOverview(railTrigger);
|
|
941
|
+
};
|
|
938
942
|
const onMenuClick = (event) => {
|
|
939
943
|
const item = event.target?.closest?.("[data-menu]");
|
|
940
944
|
if (!item) return;
|
|
@@ -943,12 +947,28 @@ function mountQuotaCard({
|
|
|
943
947
|
setCardHidden(!surfaceFlags.cardHidden);
|
|
944
948
|
} else if (action === "refresh") {
|
|
945
949
|
void poll(true);
|
|
946
|
-
} else if (action === "hide-ball") {
|
|
947
|
-
setBallSessionHidden(true);
|
|
948
950
|
}
|
|
949
951
|
menu.hidden = true;
|
|
950
952
|
};
|
|
951
953
|
menu.addEventListener("click", onMenuClick);
|
|
954
|
+
let currentFoot = null;
|
|
955
|
+
let footTooNarrow = false;
|
|
956
|
+
let noDataHidden = false;
|
|
957
|
+
const syncFootNarrowness = () => {
|
|
958
|
+
footTooNarrow = currentFoot !== null && currentFoot.clientWidth > 0 && currentFoot.clientWidth < CARD_MIN_WIDTH;
|
|
959
|
+
};
|
|
960
|
+
const syncCardVisibility = () => {
|
|
961
|
+
syncFootNarrowness();
|
|
962
|
+
const cardOn = !surfaceFlags.cardHidden;
|
|
963
|
+
const showCard = cardOn && !sidebarCollapsed && !footTooNarrow && !noDataHidden;
|
|
964
|
+
const showRail = cardOn && sidebarCollapsed && !noDataHidden;
|
|
965
|
+
container.hidden = !showCard && !showRail;
|
|
966
|
+
if (showRail) container.dataset.dshQuotaWatchSidebarCollapsed = "";
|
|
967
|
+
else delete container.dataset.dshQuotaWatchSidebarCollapsed;
|
|
968
|
+
};
|
|
969
|
+
const footResizeObserver = typeof win.ResizeObserver === "function" ? new win.ResizeObserver(() => {
|
|
970
|
+
syncCardVisibility();
|
|
971
|
+
}) : null;
|
|
952
972
|
const renderAll = () => {
|
|
953
973
|
const providers = snapshot?.providers ?? [];
|
|
954
974
|
const glm = providers.find((provider) => provider?.key === "glm");
|
|
@@ -960,7 +980,8 @@ function mountQuotaCard({
|
|
|
960
980
|
if (glmParts) fragment.append(...glmParts);
|
|
961
981
|
if (copilotParts) fragment.append(...copilotParts);
|
|
962
982
|
body.replaceChildren(fragment);
|
|
963
|
-
|
|
983
|
+
noDataHidden = glmParts === null && copilotParts === null;
|
|
984
|
+
syncCardVisibility();
|
|
964
985
|
const updated = formatTime(snapshot?.updatedAt ? new Date(snapshot.updatedAt).toISOString() : "", locale);
|
|
965
986
|
if (updated) {
|
|
966
987
|
lastUpdated.hidden = true;
|
|
@@ -995,6 +1016,11 @@ function mountQuotaCard({
|
|
|
995
1016
|
if (container.parentElement !== foot || foot.firstElementChild !== container) {
|
|
996
1017
|
foot.insertBefore(container, foot.firstChild);
|
|
997
1018
|
}
|
|
1019
|
+
if (currentFoot !== foot) {
|
|
1020
|
+
currentFoot = foot;
|
|
1021
|
+
footResizeObserver?.disconnect();
|
|
1022
|
+
footResizeObserver?.observe(foot);
|
|
1023
|
+
}
|
|
998
1024
|
};
|
|
999
1025
|
const schedulePlace = () => {
|
|
1000
1026
|
if (queuedPlace || disposed) return;
|
|
@@ -1004,10 +1030,24 @@ function mountQuotaCard({
|
|
|
1004
1030
|
if (!disposed) place();
|
|
1005
1031
|
});
|
|
1006
1032
|
};
|
|
1033
|
+
let sidebarCollapsed = Boolean(doc.querySelector("[data-sidebar-collapsed]"));
|
|
1034
|
+
const syncSidebarCollapsed = () => {
|
|
1035
|
+
const next = Boolean(doc.querySelector("[data-sidebar-collapsed]"));
|
|
1036
|
+
if (next !== sidebarCollapsed) {
|
|
1037
|
+
sidebarCollapsed = next;
|
|
1038
|
+
renderAll();
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1007
1041
|
const observer = new win.MutationObserver(() => {
|
|
1042
|
+
syncSidebarCollapsed();
|
|
1008
1043
|
schedulePlace();
|
|
1009
1044
|
});
|
|
1010
|
-
observer.observe(doc.body, {
|
|
1045
|
+
observer.observe(doc.body, {
|
|
1046
|
+
childList: true,
|
|
1047
|
+
subtree: true,
|
|
1048
|
+
attributes: true,
|
|
1049
|
+
attributeFilter: ["data-sidebar-collapsed"]
|
|
1050
|
+
});
|
|
1011
1051
|
place();
|
|
1012
1052
|
const startPolling = () => {
|
|
1013
1053
|
if (timer !== void 0 || doc.visibilityState === "hidden" || disposed) return;
|
|
@@ -1024,15 +1064,15 @@ function mountQuotaCard({
|
|
|
1024
1064
|
if (doc.visibilityState === "hidden") stopPolling();
|
|
1025
1065
|
else startPolling();
|
|
1026
1066
|
};
|
|
1027
|
-
const toggleRow = (key
|
|
1067
|
+
const toggleRow = (key) => {
|
|
1028
1068
|
panelNav = false;
|
|
1029
|
-
lastAnchor =
|
|
1069
|
+
lastAnchor = anchorFromRow(key);
|
|
1030
1070
|
openKey = openKey === key ? void 0 : key;
|
|
1031
1071
|
renderAll();
|
|
1032
1072
|
};
|
|
1033
1073
|
const openOverview = (anchorEl) => {
|
|
1034
1074
|
panelNav = false;
|
|
1035
|
-
lastAnchor = anchorEl
|
|
1075
|
+
lastAnchor = anchorFromEl(anchorEl);
|
|
1036
1076
|
openKey = "overview";
|
|
1037
1077
|
syncPop();
|
|
1038
1078
|
};
|
|
@@ -1060,7 +1100,7 @@ function mountQuotaCard({
|
|
|
1060
1100
|
return;
|
|
1061
1101
|
}
|
|
1062
1102
|
const row = event.target?.closest?.("[data-dsh-quota-watch-row][data-expandable]");
|
|
1063
|
-
if (row) toggleRow(row.dataset.dshQuotaWatchRow
|
|
1103
|
+
if (row) toggleRow(row.dataset.dshQuotaWatchRow);
|
|
1064
1104
|
};
|
|
1065
1105
|
const onBodyKeydown = (event) => {
|
|
1066
1106
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
@@ -1085,6 +1125,16 @@ function mountQuotaCard({
|
|
|
1085
1125
|
};
|
|
1086
1126
|
const onWinResize = () => {
|
|
1087
1127
|
placePanel();
|
|
1128
|
+
syncCardVisibility();
|
|
1129
|
+
const viewWidth = win.innerWidth ?? 1024;
|
|
1130
|
+
const viewHeight = win.innerHeight ?? 768;
|
|
1131
|
+
const x = parseFloat(floatHost.style.left);
|
|
1132
|
+
const y = parseFloat(floatHost.style.top);
|
|
1133
|
+
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
1134
|
+
const point = clampPoint({ x, y }, { width: viewWidth, height: viewHeight }, SURFACE_SIZE);
|
|
1135
|
+
floatHost.style.left = `${Math.round(point.x)}px`;
|
|
1136
|
+
floatHost.style.top = `${Math.round(point.y)}px`;
|
|
1137
|
+
}
|
|
1088
1138
|
};
|
|
1089
1139
|
const onDocKeydown = (event) => {
|
|
1090
1140
|
if (event.key !== "Escape") return;
|
|
@@ -1112,6 +1162,7 @@ function mountQuotaCard({
|
|
|
1112
1162
|
surface.addEventListener("click", onSurfaceClick);
|
|
1113
1163
|
surface.addEventListener("keydown", onSurfaceKeydown);
|
|
1114
1164
|
surface.addEventListener("contextmenu", onSurfaceContext);
|
|
1165
|
+
railTrigger.addEventListener("click", onRailClick);
|
|
1115
1166
|
let disposeDrag = attachDrag(surface);
|
|
1116
1167
|
doc.addEventListener("visibilitychange", onVisibilityChange);
|
|
1117
1168
|
doc.addEventListener("pointerdown", onDocPointerDown, true);
|
|
@@ -1134,6 +1185,7 @@ function mountQuotaCard({
|
|
|
1134
1185
|
surface.removeEventListener("click", onSurfaceClick);
|
|
1135
1186
|
surface.removeEventListener("keydown", onSurfaceKeydown);
|
|
1136
1187
|
surface.removeEventListener("contextmenu", onSurfaceContext);
|
|
1188
|
+
railTrigger.removeEventListener("click", onRailClick);
|
|
1137
1189
|
menu.removeEventListener("click", onMenuClick);
|
|
1138
1190
|
disposeDrag();
|
|
1139
1191
|
container.remove();
|
package/package.json
CHANGED
package/src/client/prefs.mjs
CHANGED
|
@@ -47,14 +47,15 @@ export function saveFloatGeometry(storage, point) {
|
|
|
47
47
|
export function loadSurfaceFlags(storage) {
|
|
48
48
|
try {
|
|
49
49
|
const raw = storage.getItem(SURFACE_FLAGS_KEY)
|
|
50
|
-
|
|
50
|
+
// The sidebar card is opt-in: the floating capsule is the default surface.
|
|
51
|
+
if (typeof raw !== 'string' || raw === '') return { cardHidden: true }
|
|
51
52
|
const parsed = JSON.parse(raw)
|
|
52
53
|
if (typeof parsed !== 'object' || parsed === null || typeof parsed.cardHidden !== 'boolean') {
|
|
53
|
-
return { cardHidden:
|
|
54
|
+
return { cardHidden: true }
|
|
54
55
|
}
|
|
55
56
|
return { cardHidden: parsed.cardHidden }
|
|
56
57
|
} catch {
|
|
57
|
-
return { cardHidden:
|
|
58
|
+
return { cardHidden: true }
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
package/src/client.mjs
CHANGED
|
@@ -6,9 +6,14 @@ export const name = 'quota-watch-client'
|
|
|
6
6
|
export const inject = []
|
|
7
7
|
|
|
8
8
|
const CARD_SELECTOR = '[data-dsh-quota-watch-card]'
|
|
9
|
+
const FLOAT_SELECTOR = '[data-dsh-quota-watch-float]'
|
|
9
10
|
const FETCH_TIMEOUT_MS = 15_000
|
|
10
11
|
const SURFACE_SIZE = 38
|
|
11
12
|
const SURFACE_MARGIN = 16
|
|
13
|
+
// Below this a mounted card would clip into unreadable text (collapsed web
|
|
14
|
+
// rails, squeezed desktop layouts) — hide it and let the capsule carry the
|
|
15
|
+
// entry. clientWidth 0 (no layout yet, jsdom) counts as "unknown", not narrow.
|
|
16
|
+
const CARD_MIN_WIDTH = 160
|
|
12
17
|
|
|
13
18
|
const COPY = {
|
|
14
19
|
zh: {
|
|
@@ -19,8 +24,6 @@ const COPY = {
|
|
|
19
24
|
showCard: '显示侧边栏卡片',
|
|
20
25
|
hideCard: '隐藏侧边栏卡片',
|
|
21
26
|
refreshNow: '立即刷新',
|
|
22
|
-
hideBall: '隐藏悬浮胶囊',
|
|
23
|
-
showBall: '显示悬浮胶囊',
|
|
24
27
|
},
|
|
25
28
|
backToOverview: '返回额度概览',
|
|
26
29
|
providerNames: { glm: 'GLM', copilot: 'Copilot' },
|
|
@@ -54,8 +57,6 @@ const COPY = {
|
|
|
54
57
|
showCard: 'Show sidebar card',
|
|
55
58
|
hideCard: 'Hide sidebar card',
|
|
56
59
|
refreshNow: 'Refresh now',
|
|
57
|
-
hideBall: 'Hide floating capsule',
|
|
58
|
-
showBall: 'Show floating capsule',
|
|
59
60
|
},
|
|
60
61
|
backToOverview: 'Back to quota overview',
|
|
61
62
|
providerNames: { glm: 'GLM', copilot: 'Copilot' },
|
|
@@ -106,11 +107,14 @@ const STYLE_TEXT = `
|
|
|
106
107
|
.dqw-chev { font-size: 9px; line-height: 14px; opacity: .6; flex: none; width: 10px; text-align: center; }
|
|
107
108
|
.dqw-stale-mark { flex: none; font-size: 9px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); cursor: help; }
|
|
108
109
|
.dqw-errtext { font-size: 10px; line-height: 14px; opacity: .7; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
109
|
-
.dqw-
|
|
110
|
-
.dqw-
|
|
111
|
-
.dqw-
|
|
112
|
-
.dqw-
|
|
113
|
-
.dqw-
|
|
110
|
+
.dqw-rail-trigger { display: none; flex: none; align-items: center; justify-content: center; width: 36px; height: 36px; margin: 0; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--dsw-alias-label-primary, inherit); cursor: pointer; transition: background-color .12s, color .12s; }
|
|
111
|
+
.dqw-rail-trigger svg { display: block; width: 16px; height: 16px; }
|
|
112
|
+
.dqw-rail-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
113
|
+
.dqw-rail-trigger:active:not(:disabled), .dqw-rail-trigger[aria-expanded="true"] { background: var(--dsw-alias-interactive-bg-active, rgba(128,128,128,.18)); color: var(--dsw-alias-label-primary, inherit); }
|
|
114
|
+
.dqw-rail-trigger:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #5b8def); outline-offset: 2px; }
|
|
115
|
+
:host([data-dsh-quota-watch-sidebar-collapsed]) .dqw-card { display: none; }
|
|
116
|
+
:host([data-dsh-quota-watch-sidebar-collapsed]) .dqw-rail-trigger { display: inline-flex; }
|
|
117
|
+
@media print { :host { display: none !important; } }
|
|
114
118
|
`
|
|
115
119
|
|
|
116
120
|
// Float shell (capsule) and the panel live on their own shadow host at the
|
|
@@ -127,15 +131,17 @@ const FLOAT_STYLE_TEXT = `
|
|
|
127
131
|
.dqw-capsule-glm, .dqw-capsule-copilot { font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
128
132
|
.dqw-capsule-glm.warn, .dqw-capsule-copilot.warn { color: var(--dsw-alias-label-warning, #d29922); }
|
|
129
133
|
.dqw-capsule-glm.danger, .dqw-capsule-copilot.danger { color: var(--dsw-alias-label-danger, #c93c3c); }
|
|
134
|
+
.dqw-capsule[hidden] { display: none; }
|
|
130
135
|
@media (prefers-reduced-motion: no-preference) {
|
|
131
136
|
.dqw-capsule[data-alert] { animation: dqw-pulse 1.6s ease-in-out infinite; }
|
|
132
137
|
}
|
|
138
|
+
@media print { :host { display: none !important; } }
|
|
133
139
|
@keyframes dqw-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
|
|
134
140
|
.dqw-menu { position: fixed; min-width: 150px; padding: 4px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 88%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); font: inherit; display: flex; flex-direction: column; }
|
|
135
141
|
.dqw-menu[hidden] { display: none; }
|
|
136
142
|
.dqw-menu-item { display: flex; align-items: center; gap: 6px; margin: 0; padding: 6px 10px; border: 0; border-radius: 6px; background: transparent; color: inherit; font: inherit; font-size: 11px; line-height: 16px; text-align: left; cursor: pointer; }
|
|
137
143
|
.dqw-menu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
138
|
-
.dqw-panel { position: fixed; width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
144
|
+
.dqw-panel { position: fixed; z-index: 2147483000; min-width: 166px; max-width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
139
145
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-panel { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
140
146
|
.dqw-panel[hidden] { display: none; }
|
|
141
147
|
.dqw-panel *, .dqw-panel *::before, .dqw-panel *::after { box-sizing: border-box; }
|
|
@@ -147,6 +153,7 @@ const FLOAT_STYLE_TEXT = `
|
|
|
147
153
|
.dqw-panel-close { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 24px; height: 24px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 12px; line-height: 1; }
|
|
148
154
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
149
155
|
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
156
|
+
.dqw-bar { background: var(--dsw-alias-bg-tertiary, rgba(128,128,128,.38)); }
|
|
150
157
|
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding: 3px 6px; margin: 0 -2px; border-radius: 8px; }
|
|
151
158
|
.dqw-row-summary[data-expandable] { cursor: pointer; }
|
|
152
159
|
.dqw-row-summary[data-expandable]:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.12)); }
|
|
@@ -363,7 +370,7 @@ export function mountQuotaCard({
|
|
|
363
370
|
pollIntervalMs = CLIENT_POLL_INTERVAL_MS,
|
|
364
371
|
} = {}) {
|
|
365
372
|
if (!doc?.body || typeof fetchImpl !== 'function') return () => {}
|
|
366
|
-
if (doc.querySelector(CARD_SELECTOR)) return () => {}
|
|
373
|
+
if (doc.querySelector(CARD_SELECTOR) || doc.querySelector(FLOAT_SELECTOR)) return () => {}
|
|
367
374
|
|
|
368
375
|
const locale = localeFor(win)
|
|
369
376
|
const copy = COPY[locale]
|
|
@@ -385,13 +392,18 @@ export function mountQuotaCard({
|
|
|
385
392
|
const lastUpdated = text(doc, 'p', 'dqw-meta', copy.loading)
|
|
386
393
|
lastUpdated.dataset.role = 'updated'
|
|
387
394
|
body.append(lastUpdated)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
+
// Collapsed web rail (0.0.18 behaviour, card opt-in only): a 36px gauge icon
|
|
396
|
+
// replaces the card body; the shadow CSS swaps them on the host's collapsed flag.
|
|
397
|
+
const railTrigger = doc.createElement('button')
|
|
398
|
+
railTrigger.type = 'button'
|
|
399
|
+
railTrigger.className = 'dqw-rail-trigger'
|
|
400
|
+
railTrigger.dataset.dshQuotaWatchRail = ''
|
|
401
|
+
railTrigger.title = copy.title
|
|
402
|
+
railTrigger.setAttribute('aria-label', copy.title)
|
|
403
|
+
railTrigger.setAttribute('aria-haspopup', 'dialog')
|
|
404
|
+
railTrigger.setAttribute('aria-expanded', 'false')
|
|
405
|
+
railTrigger.append(quotaIcon(doc))
|
|
406
|
+
card.append(body, railTrigger)
|
|
395
407
|
cardRoot.append(style, card)
|
|
396
408
|
// Details open in a viewport-level floating panel — the only detail surface.
|
|
397
409
|
const panel = doc.createElement('div')
|
|
@@ -423,14 +435,10 @@ export function mountQuotaCard({
|
|
|
423
435
|
// Capsule is the only floating surface (0.1.1: the round ball was retired).
|
|
424
436
|
let surface = buildCapsule()
|
|
425
437
|
floatRoot.append(surface)
|
|
426
|
-
/**
|
|
438
|
+
/** The capsule is always present unless no provider reports anything. */
|
|
427
439
|
const syncFloatVisibility = () => {
|
|
428
|
-
surface.hidden =
|
|
440
|
+
surface.hidden = dataHidden
|
|
429
441
|
floatHost.hidden = dataHidden
|
|
430
|
-
ballToggle.setAttribute('data-ball-hidden', String(ballSessionHidden))
|
|
431
|
-
const toggleLabel = ballSessionHidden ? copy.menu.showBall : copy.menu.hideBall
|
|
432
|
-
ballToggle.setAttribute('aria-label', toggleLabel)
|
|
433
|
-
ballToggle.title = toggleLabel
|
|
434
442
|
}
|
|
435
443
|
doc.documentElement.append(floatHost)
|
|
436
444
|
const applyFloatGeometry = () => {
|
|
@@ -444,8 +452,9 @@ export function mountQuotaCard({
|
|
|
444
452
|
)
|
|
445
453
|
floatHost.style.left = `${Math.round(point.x)}px`
|
|
446
454
|
floatHost.style.top = `${Math.round(point.y)}px`
|
|
447
|
-
//
|
|
448
|
-
|
|
455
|
+
// Deliberately NOT persisted here: a window that starts small (Electron
|
|
456
|
+
// restore, split-screen) must not clobber the user's preferred coordinates.
|
|
457
|
+
// The visual clamp above applies every mount; storage updates on drag only.
|
|
449
458
|
}
|
|
450
459
|
applyFloatGeometry()
|
|
451
460
|
// Panel lives inside the float shadow root from now on (契约总表「归属时序」).
|
|
@@ -515,20 +524,8 @@ export function mountQuotaCard({
|
|
|
515
524
|
let queuedPlace = false
|
|
516
525
|
let openKey
|
|
517
526
|
let panelNav = false
|
|
518
|
-
let ballSessionHidden = false
|
|
519
527
|
let dataHidden = false
|
|
520
528
|
let surfaceFlags = loadSurfaceFlags(win.localStorage)
|
|
521
|
-
const setBallSessionHidden = (hidden) => {
|
|
522
|
-
const wasHidden = ballSessionHidden
|
|
523
|
-
ballSessionHidden = hidden
|
|
524
|
-
// Closing the panel is a one-time consequence of hiding, not a steady state:
|
|
525
|
-
// card rows must keep opening the panel while the ball stays hidden.
|
|
526
|
-
if (hidden && !wasHidden && openKey !== undefined) {
|
|
527
|
-
openKey = undefined
|
|
528
|
-
syncPop()
|
|
529
|
-
}
|
|
530
|
-
syncFloatVisibility()
|
|
531
|
-
}
|
|
532
529
|
const setCardHidden = (hidden) => {
|
|
533
530
|
surfaceFlags = { cardHidden: hidden }
|
|
534
531
|
saveSurfaceFlags(win.localStorage, surfaceFlags)
|
|
@@ -741,10 +738,17 @@ export function mountQuotaCard({
|
|
|
741
738
|
}
|
|
742
739
|
|
|
743
740
|
/** Anchor the panel beside its opener (a card row today, the float surface later). */
|
|
741
|
+
// Anchor resolver: card rows are re-created on every render pass, so keep a
|
|
742
|
+
// "how to find the anchor" closure instead of a detached element reference
|
|
743
|
+
// (a detached node's getBoundingClientRect is all zeros → panel teleports).
|
|
744
744
|
let lastAnchor = null
|
|
745
|
+
const anchorFromRow = (key) => () => body.querySelector(`[data-dsh-quota-watch-row="${key}"]`)
|
|
746
|
+
const anchorFromEl = (el) => () => el
|
|
745
747
|
const placePanel = () => {
|
|
746
|
-
if (panel.hidden
|
|
747
|
-
const
|
|
748
|
+
if (panel.hidden) return
|
|
749
|
+
const anchor = lastAnchor?.()
|
|
750
|
+
if (!anchor || !anchor.isConnected) return
|
|
751
|
+
const rect = anchor.getBoundingClientRect()
|
|
748
752
|
const viewWidth = win.innerWidth ?? 1024
|
|
749
753
|
const viewHeight = win.innerHeight ?? 768
|
|
750
754
|
const width = panel.offsetWidth || 240
|
|
@@ -767,6 +771,7 @@ export function mountQuotaCard({
|
|
|
767
771
|
lastAnchor = null
|
|
768
772
|
panelNav = false
|
|
769
773
|
surface.setAttribute('aria-expanded', 'false')
|
|
774
|
+
railTrigger.setAttribute('aria-expanded', 'false')
|
|
770
775
|
return
|
|
771
776
|
}
|
|
772
777
|
if (openKey === 'overview') {
|
|
@@ -777,6 +782,7 @@ export function mountQuotaCard({
|
|
|
777
782
|
panel.hidden = false
|
|
778
783
|
placePanel()
|
|
779
784
|
surface.setAttribute('aria-expanded', 'true')
|
|
785
|
+
railTrigger.setAttribute('aria-expanded', 'true')
|
|
780
786
|
return
|
|
781
787
|
}
|
|
782
788
|
const provider = (snapshot?.providers ?? []).find((item) => item?.key === openKey)
|
|
@@ -793,6 +799,7 @@ export function mountQuotaCard({
|
|
|
793
799
|
panel.hidden = false
|
|
794
800
|
placePanel()
|
|
795
801
|
surface.setAttribute('aria-expanded', 'true')
|
|
802
|
+
railTrigger.setAttribute('aria-expanded', 'true')
|
|
796
803
|
}
|
|
797
804
|
|
|
798
805
|
const renderFloatFace = () => {
|
|
@@ -853,7 +860,6 @@ export function mountQuotaCard({
|
|
|
853
860
|
items.push(menuItem(doc, 'toggle-card', surfaceFlags.cardHidden ? copy.menu.showCard : copy.menu.hideCard))
|
|
854
861
|
}
|
|
855
862
|
items.push(menuItem(doc, 'refresh', copy.menu.refreshNow))
|
|
856
|
-
items.push(menuItem(doc, 'hide-ball', copy.menu.hideBall))
|
|
857
863
|
menu.replaceChildren(...items)
|
|
858
864
|
menu.hidden = false
|
|
859
865
|
const rect = surface.getBoundingClientRect()
|
|
@@ -871,6 +877,15 @@ export function mountQuotaCard({
|
|
|
871
877
|
event.preventDefault()
|
|
872
878
|
openMenu()
|
|
873
879
|
}
|
|
880
|
+
/** Collapsed-rail gauge icon: toggles the panel, exactly like the capsule. */
|
|
881
|
+
const onRailClick = () => {
|
|
882
|
+
if (openKey !== undefined && !panel.hidden) {
|
|
883
|
+
openKey = undefined
|
|
884
|
+
syncPop()
|
|
885
|
+
return
|
|
886
|
+
}
|
|
887
|
+
openOverview(railTrigger)
|
|
888
|
+
}
|
|
874
889
|
const onMenuClick = (event) => {
|
|
875
890
|
const item = event.target?.closest?.('[data-menu]')
|
|
876
891
|
if (!item) return
|
|
@@ -879,13 +894,33 @@ export function mountQuotaCard({
|
|
|
879
894
|
setCardHidden(!surfaceFlags.cardHidden)
|
|
880
895
|
} else if (action === 'refresh') {
|
|
881
896
|
void poll(true)
|
|
882
|
-
} else if (action === 'hide-ball') {
|
|
883
|
-
setBallSessionHidden(true)
|
|
884
897
|
}
|
|
885
898
|
menu.hidden = true
|
|
886
899
|
}
|
|
887
900
|
menu.addEventListener('click', onMenuClick)
|
|
888
901
|
|
|
902
|
+
// Cross-profile narrow-container detection: a measurably narrow foot would
|
|
903
|
+
// clip the card into unreadable text regardless of HOW the host signals its
|
|
904
|
+
// collapsed state — hide the card and let the capsule carry the entry.
|
|
905
|
+
// clientWidth 0 (no layout yet, jsdom) counts as "unknown", not narrow.
|
|
906
|
+
let currentFoot = null
|
|
907
|
+
let footTooNarrow = false
|
|
908
|
+
let noDataHidden = false
|
|
909
|
+
const syncFootNarrowness = () => {
|
|
910
|
+
footTooNarrow = currentFoot !== null && currentFoot.clientWidth > 0 && currentFoot.clientWidth < CARD_MIN_WIDTH
|
|
911
|
+
}
|
|
912
|
+
const syncCardVisibility = () => {
|
|
913
|
+
syncFootNarrowness()
|
|
914
|
+
const cardOn = !surfaceFlags.cardHidden
|
|
915
|
+
const showCard = cardOn && !sidebarCollapsed && !footTooNarrow && !noDataHidden
|
|
916
|
+
const showRail = cardOn && sidebarCollapsed && !noDataHidden
|
|
917
|
+
container.hidden = !showCard && !showRail
|
|
918
|
+
if (showRail) container.dataset.dshQuotaWatchSidebarCollapsed = ''
|
|
919
|
+
else delete container.dataset.dshQuotaWatchSidebarCollapsed
|
|
920
|
+
}
|
|
921
|
+
const footResizeObserver = typeof win.ResizeObserver === 'function'
|
|
922
|
+
? new win.ResizeObserver(() => { syncCardVisibility() })
|
|
923
|
+
: null
|
|
889
924
|
const renderAll = () => {
|
|
890
925
|
const providers = snapshot?.providers ?? []
|
|
891
926
|
const glm = providers.find((provider) => provider?.key === 'glm')
|
|
@@ -897,7 +932,8 @@ export function mountQuotaCard({
|
|
|
897
932
|
if (glmParts) fragment.append(...glmParts)
|
|
898
933
|
if (copilotParts) fragment.append(...copilotParts)
|
|
899
934
|
body.replaceChildren(fragment)
|
|
900
|
-
|
|
935
|
+
noDataHidden = glmParts === null && copilotParts === null
|
|
936
|
+
syncCardVisibility()
|
|
901
937
|
const updated = formatTime(snapshot?.updatedAt ? new Date(snapshot.updatedAt).toISOString() : '', locale)
|
|
902
938
|
if (updated) {
|
|
903
939
|
lastUpdated.hidden = true
|
|
@@ -939,6 +975,11 @@ export function mountQuotaCard({
|
|
|
939
975
|
if (container.parentElement !== foot || foot.firstElementChild !== container) {
|
|
940
976
|
foot.insertBefore(container, foot.firstChild)
|
|
941
977
|
}
|
|
978
|
+
if (currentFoot !== foot) {
|
|
979
|
+
currentFoot = foot
|
|
980
|
+
footResizeObserver?.disconnect()
|
|
981
|
+
footResizeObserver?.observe(foot)
|
|
982
|
+
}
|
|
942
983
|
}
|
|
943
984
|
|
|
944
985
|
const schedulePlace = () => {
|
|
@@ -950,10 +991,28 @@ export function mountQuotaCard({
|
|
|
950
991
|
})
|
|
951
992
|
}
|
|
952
993
|
|
|
994
|
+
// Web narrow-rail collapse: the host keeps the footArea but squeezes it, which
|
|
995
|
+
// clips the card into garbled text — hide the card and let the capsule carry
|
|
996
|
+
// the entry (0.0.18 shipped dedicated rail CSS; 0.1.0 retired it, and 0.1.2
|
|
997
|
+
// reintroduces only this visibility signal — zero layout coupling).
|
|
998
|
+
let sidebarCollapsed = Boolean(doc.querySelector('[data-sidebar-collapsed]'))
|
|
999
|
+
const syncSidebarCollapsed = () => {
|
|
1000
|
+
const next = Boolean(doc.querySelector('[data-sidebar-collapsed]'))
|
|
1001
|
+
if (next !== sidebarCollapsed) {
|
|
1002
|
+
sidebarCollapsed = next
|
|
1003
|
+
renderAll()
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
953
1006
|
const observer = new win.MutationObserver(() => {
|
|
1007
|
+
syncSidebarCollapsed()
|
|
954
1008
|
schedulePlace()
|
|
955
1009
|
})
|
|
956
|
-
observer.observe(doc.body, {
|
|
1010
|
+
observer.observe(doc.body, {
|
|
1011
|
+
childList: true,
|
|
1012
|
+
subtree: true,
|
|
1013
|
+
attributes: true,
|
|
1014
|
+
attributeFilter: ['data-sidebar-collapsed'],
|
|
1015
|
+
})
|
|
957
1016
|
place()
|
|
958
1017
|
|
|
959
1018
|
const startPolling = () => {
|
|
@@ -969,15 +1028,15 @@ export function mountQuotaCard({
|
|
|
969
1028
|
if (doc.visibilityState === 'hidden') stopPolling()
|
|
970
1029
|
else startPolling()
|
|
971
1030
|
}
|
|
972
|
-
const toggleRow = (key
|
|
1031
|
+
const toggleRow = (key) => {
|
|
973
1032
|
panelNav = false
|
|
974
|
-
lastAnchor =
|
|
1033
|
+
lastAnchor = anchorFromRow(key)
|
|
975
1034
|
openKey = openKey === key ? undefined : key
|
|
976
1035
|
renderAll()
|
|
977
1036
|
}
|
|
978
1037
|
const openOverview = (anchorEl) => {
|
|
979
1038
|
panelNav = false
|
|
980
|
-
lastAnchor = anchorEl
|
|
1039
|
+
lastAnchor = anchorFromEl(anchorEl)
|
|
981
1040
|
openKey = 'overview'
|
|
982
1041
|
syncPop()
|
|
983
1042
|
}
|
|
@@ -1005,7 +1064,7 @@ export function mountQuotaCard({
|
|
|
1005
1064
|
return
|
|
1006
1065
|
}
|
|
1007
1066
|
const row = event.target?.closest?.('[data-dsh-quota-watch-row][data-expandable]')
|
|
1008
|
-
if (row) toggleRow(row.dataset.dshQuotaWatchRow
|
|
1067
|
+
if (row) toggleRow(row.dataset.dshQuotaWatchRow)
|
|
1009
1068
|
}
|
|
1010
1069
|
const onBodyKeydown = (event) => {
|
|
1011
1070
|
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
@@ -1026,7 +1085,21 @@ export function mountQuotaCard({
|
|
|
1026
1085
|
renderAll()
|
|
1027
1086
|
}
|
|
1028
1087
|
const onDocScroll = () => { placePanel() }
|
|
1029
|
-
const onWinResize = () => {
|
|
1088
|
+
const onWinResize = () => {
|
|
1089
|
+
placePanel()
|
|
1090
|
+
syncCardVisibility()
|
|
1091
|
+
// Keep the capsule inside a shrunk viewport; storage keeps the user's
|
|
1092
|
+
// preferred coordinates — only the visual position is clamped here.
|
|
1093
|
+
const viewWidth = win.innerWidth ?? 1024
|
|
1094
|
+
const viewHeight = win.innerHeight ?? 768
|
|
1095
|
+
const x = parseFloat(floatHost.style.left)
|
|
1096
|
+
const y = parseFloat(floatHost.style.top)
|
|
1097
|
+
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
1098
|
+
const point = clampPoint({ x, y }, { width: viewWidth, height: viewHeight }, SURFACE_SIZE)
|
|
1099
|
+
floatHost.style.left = `${Math.round(point.x)}px`
|
|
1100
|
+
floatHost.style.top = `${Math.round(point.y)}px`
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1030
1103
|
const onDocKeydown = (event) => {
|
|
1031
1104
|
if (event.key !== 'Escape') return
|
|
1032
1105
|
if (!menu.hidden) {
|
|
@@ -1053,6 +1126,7 @@ export function mountQuotaCard({
|
|
|
1053
1126
|
surface.addEventListener('click', onSurfaceClick)
|
|
1054
1127
|
surface.addEventListener('keydown', onSurfaceKeydown)
|
|
1055
1128
|
surface.addEventListener('contextmenu', onSurfaceContext)
|
|
1129
|
+
railTrigger.addEventListener('click', onRailClick)
|
|
1056
1130
|
let disposeDrag = attachDrag(surface)
|
|
1057
1131
|
doc.addEventListener('visibilitychange', onVisibilityChange)
|
|
1058
1132
|
doc.addEventListener('pointerdown', onDocPointerDown, true)
|
|
@@ -1076,6 +1150,7 @@ export function mountQuotaCard({
|
|
|
1076
1150
|
surface.removeEventListener('click', onSurfaceClick)
|
|
1077
1151
|
surface.removeEventListener('keydown', onSurfaceKeydown)
|
|
1078
1152
|
surface.removeEventListener('contextmenu', onSurfaceContext)
|
|
1153
|
+
railTrigger.removeEventListener('click', onRailClick)
|
|
1079
1154
|
menu.removeEventListener('click', onMenuClick)
|
|
1080
1155
|
disposeDrag()
|
|
1081
1156
|
container.remove()
|