@iasiv5/dsh-quota-watch 0.1.19 → 0.1.20
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 +20 -2
- package/package.json +1 -1
- package/src/client/prefs.mjs +14 -0
- package/src/client.mjs +34 -5
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
## ✨ 功能一览
|
|
16
16
|
|
|
17
|
-
- **🚀 悬浮胶囊(默认界面)**:一行「GLM 15% · Copilot 1.8%」常驻屏幕(默认右下角安全区),单击展开面板——胶囊上没有任何隐藏手势(右键 / 长按菜单已于 0.1.14 退役)。拖拽跟手(保持抓取点,transform 渲染)、任意位置停放、位置跨重启记忆、越界自动钳回视口(距边 8px
|
|
17
|
+
- **🚀 悬浮胶囊(默认界面)**:一行「GLM 15% · Copilot 1.8%」常驻屏幕(默认右下角安全区),单击展开面板——胶囊上没有任何隐藏手势(右键 / 长按菜单已于 0.1.14 退役)。拖拽跟手(保持抓取点,transform 渲染)、任意位置停放、位置跨重启记忆、越界自动钳回视口(距边 8px 安全边距),并自动避开 Windows Desktop 壳顶部的标题栏拖拽带(0.1.20:该带按布局吞掉指针输入、无视 z-index,胶囊/面板落进去就再也点不到、拖不回——现与壳自家浮窗同款消费 `--dsh-windows-titlebar-height` 让位,历史卡死位置在启动时自动钳回带外;Web 无此变量,行为零漂移);触摸设备直接拖拽(更宽的防误拖阈值)、点击热区 44px、自动避让 iPhone 刘海 / 小黑条(safe-area)。已用超过 80% 转橙、超过 95% 转红,任一 provider 超 95% 时轻微脉动(尊重系统「减少动态效果」设置)。悬停提示与 aria-label 实时给出摘要。
|
|
18
18
|
- **🖥️ 详情面板**:半透明毛玻璃底,宽 `min(320px, 100vw−24px)`,靠边自动翻侧,滚动 / 缩放跟随;≤480px 窄屏自动改为底部弹层并避开 safe-area;Esc 或点击面板外关闭。概览列两个 provider 行,点击进入详情:
|
|
19
19
|
- **GLM**:今日 Tokens / 调用次数双大数字 + 五列对齐窗口网格(MCP(月)→ 5 小时 → 每月 → 每周固定排序,周窗口未下发时显示「每周额度 · ♾️(无限)」占位)+ 分模型用量行;
|
|
20
20
|
- **Copilot**:可用额度、本期已用(credits 消耗 + 弱化百分比)、重置时间三行。
|
|
@@ -71,4 +71,4 @@ dsh plugin --profile desktop add @iasiv5/dsh-quota-watch # desktop profile
|
|
|
71
71
|
|
|
72
72
|
## English summary
|
|
73
73
|
|
|
74
|
-
DSH Quota Watch is a personal DSH quota monitor built around a persistent floating capsule: a one-line `GLM x% · Copilot y%` summary that turns amber past 80% and red past 95%. Dragging it is grab-accurate (transform-based, rAF-batched) and free to place anywhere on the screen, with position remembered across restarts and viewport boundaries automatically clamped. Touch devices drag directly (wider slop against accidental drags), the hit area is padded to 44px and placement respects `env(safe-area-inset-*)`. Clicking the capsule opens a floating glass panel whose overview reuses the classic 0.0.18 summary rows (label · bar · percent · extra · chevron) and whose detail pages replicate the 0.0.18 big numbers, window grid and per-model list; the panel closes via ✕, Esc or an outside click, and viewports ≤480px dock it to the bottom as a sheet. The panel footer holds two equal-width buttons — sidebar-card visibility and an immediate provider re-probe — replacing the retired right-click/long-press menu, so the capsule itself carries no hidden gestures. A sidebar card remains an opportunistic extra entry on web profiles, opted in from that footer button, and silently disappears whenever the sidebar does — collapsed web sidebars and the desktop profile are covered by the floating capsule alone. Credentials are resolved on every host-side probe, only normalized values reach the browser, no usage history is kept, and a Copilot balance is never inferred from credits used. UI copy follows the DSH language preference (mirrored live onto `<html lang>`, re-rendering on switch without a reload) and falls back to the browser language; window notes share one paren glyph per locale so the grid's left edges stay aligned. Colors ride DSH semantic theme tokens with hardcoded fallbacks; all UI lives inside a shadow root (ADR 0001).
|
|
74
|
+
DSH Quota Watch is a personal DSH quota monitor built around a persistent floating capsule: a one-line `GLM x% · Copilot y%` summary that turns amber past 80% and red past 95%. Dragging it is grab-accurate (transform-based, rAF-batched) and free to place anywhere on the screen, with position remembered across restarts and viewport boundaries automatically clamped. Since 0.1.20 placement also steers clear of the Windows Desktop shell titlebar drag band (a layout-level `-webkit-app-region:drag` strip that swallows pointer input regardless of z-index — the same trap dsh-m fixed in 0.9.4; the capsule and the panel header consume `--dsh-windows-titlebar-height` the way the shell's own floats do, and positions saved while stuck self-heal below the band on boot; Web, without the variable, is byte-identical). Touch devices drag directly (wider slop against accidental drags), the hit area is padded to 44px and placement respects `env(safe-area-inset-*)`. Clicking the capsule opens a floating glass panel whose overview reuses the classic 0.0.18 summary rows (label · bar · percent · extra · chevron) and whose detail pages replicate the 0.0.18 big numbers, window grid and per-model list; the panel closes via ✕, Esc or an outside click, and viewports ≤480px dock it to the bottom as a sheet. The panel footer holds two equal-width buttons — sidebar-card visibility and an immediate provider re-probe — replacing the retired right-click/long-press menu, so the capsule itself carries no hidden gestures. A sidebar card remains an opportunistic extra entry on web profiles, opted in from that footer button, and silently disappears whenever the sidebar does — collapsed web sidebars and the desktop profile are covered by the floating capsule alone. Credentials are resolved on every host-side probe, only normalized values reach the browser, no usage history is kept, and a Copilot balance is never inferred from credits used. UI copy follows the DSH language preference (mirrored live onto `<html lang>`, re-rendering on switch without a reload) and falls back to the browser language; window notes share one paren glyph per locale so the grid's left edges stay aligned. Colors ride DSH semantic theme tokens with hardcoded fallbacks; all UI lives inside a shadow root (ADR 0001).
|
package/lib/client.js
CHANGED
|
@@ -107,6 +107,11 @@ function clampPoint(point, viewport, size = 0, insets = ZERO_INSETS) {
|
|
|
107
107
|
y: Math.min(Math.max(point.y, minY), maxY)
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
+
function titlebarTopInset({ titlebar = false, fullscreen = false, height } = {}) {
|
|
111
|
+
if (!titlebar || fullscreen) return 0;
|
|
112
|
+
const value = Number.parseFloat(height);
|
|
113
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
114
|
+
}
|
|
110
115
|
function loadFloatPosition(storage) {
|
|
111
116
|
try {
|
|
112
117
|
const raw = storage.getItem(FLOAT_POSITION_KEY);
|
|
@@ -551,11 +556,23 @@ function mountQuotaCard({
|
|
|
551
556
|
const safeAreaProbe = text(doc, "div", "", "");
|
|
552
557
|
safeAreaProbe.style.cssText = "position:fixed;visibility:hidden;pointer-events:none;width:0;height:0;overflow:hidden;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);";
|
|
553
558
|
floatRoot.append(safeAreaProbe);
|
|
559
|
+
const readTitlebarInset = () => {
|
|
560
|
+
try {
|
|
561
|
+
const html = doc.documentElement;
|
|
562
|
+
return titlebarTopInset({
|
|
563
|
+
titlebar: html.hasAttribute("data-windows-titlebar"),
|
|
564
|
+
fullscreen: html.hasAttribute("data-fullscreen"),
|
|
565
|
+
height: parseInset(win.getComputedStyle(html)?.getPropertyValue("--dsh-windows-titlebar-height"))
|
|
566
|
+
});
|
|
567
|
+
} catch {
|
|
568
|
+
return 0;
|
|
569
|
+
}
|
|
570
|
+
};
|
|
554
571
|
const readInsets = () => {
|
|
555
572
|
try {
|
|
556
573
|
const style2 = win.getComputedStyle(safeAreaProbe);
|
|
557
574
|
return {
|
|
558
|
-
top: parseInset(style2?.paddingTop),
|
|
575
|
+
top: Math.max(parseInset(style2?.paddingTop), readTitlebarInset()),
|
|
559
576
|
right: parseInset(style2?.paddingRight),
|
|
560
577
|
bottom: parseInset(style2?.paddingBottom),
|
|
561
578
|
left: parseInset(style2?.paddingLeft)
|
|
@@ -918,7 +935,8 @@ function mountQuotaCard({
|
|
|
918
935
|
if (left + width > viewWidth - 8) left = rect.left - width - 8;
|
|
919
936
|
if (left + width > viewWidth - 8) left = viewWidth - width - 8;
|
|
920
937
|
left = Math.max(8, left);
|
|
921
|
-
const
|
|
938
|
+
const minTop = MARGIN + readInsets().top;
|
|
939
|
+
const top = Math.min(Math.max(rect.top, minTop), Math.max(minTop, viewHeight - height - 8));
|
|
922
940
|
panel.style.left = `${Math.round(left)}px`;
|
|
923
941
|
panel.style.top = `${Math.round(top)}px`;
|
|
924
942
|
};
|
package/package.json
CHANGED
package/src/client/prefs.mjs
CHANGED
|
@@ -31,6 +31,20 @@ export function clampPoint(point, viewport, size = 0, insets = ZERO_INSETS) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Windows Desktop shell titlebar contract (0.1.20): the shell runs
|
|
35
|
+
// `titleBarStyle:hidden + titleBarOverlay`, so the top strip of the window is a
|
|
36
|
+
// full-width `-webkit-app-region:drag` band that swallows pointer input by
|
|
37
|
+
// LAYOUT — z-index and paint order are irrelevant — and a capsule parked inside
|
|
38
|
+
// it can never be grabbed or clicked again. The shell flags the band on
|
|
39
|
+
// <html> with `data-windows-titlebar` + `--dsh-windows-titlebar-height` and
|
|
40
|
+
// clears it under `data-fullscreen`; its own floats clamp to exactly this
|
|
41
|
+
// value. Everything else (DSH Web, browsers, jsdom) yields 0 — zero drift.
|
|
42
|
+
export function titlebarTopInset({ titlebar = false, fullscreen = false, height } = {}) {
|
|
43
|
+
if (!titlebar || fullscreen) return 0
|
|
44
|
+
const value = Number.parseFloat(height)
|
|
45
|
+
return Number.isFinite(value) && value > 0 ? value : 0
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
export function loadFloatPosition(storage) {
|
|
35
49
|
try {
|
|
36
50
|
const raw = storage.getItem(FLOAT_POSITION_KEY)
|
package/src/client.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CLIENT_POLL_INTERVAL_MS, CLIENT_ROUTES } from './shared.mjs'
|
|
2
2
|
import { remainingToUsed } from './core/adapters.mjs'
|
|
3
|
-
import { MARGIN, clampPoint, loadFloatPosition, loadSurfaceFlags, saveFloatPosition, saveSurfaceFlags } from './client/prefs.mjs'
|
|
3
|
+
import { MARGIN, clampPoint, loadFloatPosition, loadSurfaceFlags, saveFloatPosition, saveSurfaceFlags, titlebarTopInset } from './client/prefs.mjs'
|
|
4
4
|
import { DRAG_SLOP_MOUSE, DRAG_SLOP_TOUCH, parseInset, clampFrame, dragSlop, grabOffset } from './client/drag.mjs'
|
|
5
5
|
|
|
6
6
|
export const name = 'quota-watch-client'
|
|
@@ -450,7 +450,8 @@ export function mountQuotaCard({
|
|
|
450
450
|
const rect = surface.getBoundingClientRect()
|
|
451
451
|
return rect.width > 0 ? { width: rect.width, height: rect.height } : FALLBACK_SIZE
|
|
452
452
|
}
|
|
453
|
-
/** Position = user preferred {x, y} clamped to viewport
|
|
453
|
+
/** Position = user preferred {x, y} clamped to viewport, safe-area insets
|
|
454
|
+
* and the Windows shell titlebar band. */
|
|
454
455
|
const applyPosition = () => {
|
|
455
456
|
const viewWidth = win.innerWidth ?? 1024
|
|
456
457
|
const viewHeight = win.innerHeight ?? 768
|
|
@@ -471,13 +472,37 @@ export function mountQuotaCard({
|
|
|
471
472
|
const safeAreaProbe = text(doc, 'div', '', '')
|
|
472
473
|
safeAreaProbe.style.cssText = 'position:fixed;visibility:hidden;pointer-events:none;width:0;height:0;overflow:hidden;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);'
|
|
473
474
|
floatRoot.append(safeAreaProbe)
|
|
475
|
+
// Windows Desktop titlebar band (0.1.20): with `titleBarStyle:hidden +
|
|
476
|
+
// titleBarOverlay` the top strip of the window is the shell's full-width
|
|
477
|
+
// `-webkit-app-region:drag` band — it wins pointer input by layout, ignoring
|
|
478
|
+
// z-index, so a capsule dropped there is stuck for good (un-grabbable,
|
|
479
|
+
// un-clickable, and the persisted position kept re-parking it). Folding the
|
|
480
|
+
// band into the top inset routes it through every clamp path at once: live
|
|
481
|
+
// drag frames, release docking, persisted-position restore and the default
|
|
482
|
+
// dock — and mount-time clamping also self-heals positions saved by 0.1.19.
|
|
483
|
+
// Same contract the shell's own floats consume: `data-windows-titlebar` +
|
|
484
|
+
// `--dsh-windows-titlebar-height` on <html>, dropped under `data-fullscreen`.
|
|
485
|
+
// DSH Web / browsers have neither attribute nor variable → 0, zero drift.
|
|
486
|
+
const readTitlebarInset = () => {
|
|
487
|
+
try {
|
|
488
|
+
const html = doc.documentElement
|
|
489
|
+
return titlebarTopInset({
|
|
490
|
+
titlebar: html.hasAttribute('data-windows-titlebar'),
|
|
491
|
+
fullscreen: html.hasAttribute('data-fullscreen'),
|
|
492
|
+
height: parseInset(win.getComputedStyle(html)?.getPropertyValue('--dsh-windows-titlebar-height')),
|
|
493
|
+
})
|
|
494
|
+
} catch {
|
|
495
|
+
return 0
|
|
496
|
+
}
|
|
497
|
+
}
|
|
474
498
|
/** Live env(safe-area-inset-*) reading — re-probed on every use, so window
|
|
475
|
-
* chrome changes (rotation, URL-bar collapse) need no explicit invalidation.
|
|
499
|
+
* chrome changes (rotation, URL-bar collapse) need no explicit invalidation.
|
|
500
|
+
* `top` is the larger of the safe-area inset and the shell titlebar band. */
|
|
476
501
|
const readInsets = () => {
|
|
477
502
|
try {
|
|
478
503
|
const style = win.getComputedStyle(safeAreaProbe)
|
|
479
504
|
return {
|
|
480
|
-
top: parseInset(style?.paddingTop),
|
|
505
|
+
top: Math.max(parseInset(style?.paddingTop), readTitlebarInset()),
|
|
481
506
|
right: parseInset(style?.paddingRight),
|
|
482
507
|
bottom: parseInset(style?.paddingBottom),
|
|
483
508
|
left: parseInset(style?.paddingLeft),
|
|
@@ -892,7 +917,11 @@ export function mountQuotaCard({
|
|
|
892
917
|
if (left + width > viewWidth - 8) left = rect.left - width - 8
|
|
893
918
|
if (left + width > viewWidth - 8) left = viewWidth - width - 8
|
|
894
919
|
left = Math.max(8, left)
|
|
895
|
-
|
|
920
|
+
// The panel header carries the close button: keep its top edge below the
|
|
921
|
+
// shell titlebar band (and safe-area top) for the same reason as the
|
|
922
|
+
// capsule — layout-level drag hits ignore z-index. Web fallback: 8px, as before.
|
|
923
|
+
const minTop = MARGIN + readInsets().top
|
|
924
|
+
const top = Math.min(Math.max(rect.top, minTop), Math.max(minTop, viewHeight - height - 8))
|
|
896
925
|
panel.style.left = `${Math.round(left)}px`
|
|
897
926
|
panel.style.top = `${Math.round(top)}px`
|
|
898
927
|
}
|