@kidli1412/dsh-token-heatmap 0.4.1 → 0.4.2
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 +4 -3
- package/docs//345/215/241/347/211/207/350/256/276/347/275/256/351/235/242/346/235/277.jpg +0 -0
- package/docs//345/271/264/350/247/206/345/233/276.jpg +0 -0
- package/docs//346/234/210/350/247/206/345/233/276.jpg +0 -0
- package/docs//351/242/204/350/247/210-/346/226/260/347/211/210/344/274/232/350/257/235/351/241/265.jpg +0 -0
- package/lib/client.js +171 -55
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -31,10 +31,10 @@ GitHub 风格自然年热力图:覆盖所选自然年 1月–12月(`‹ 2026
|
|
|
31
31
|
|
|
32
32
|
### 卡片设置 / In-card settings
|
|
33
33
|
|
|
34
|
-

|
|
35
35
|
|
|
36
|
-
- ⚙️ **设置就在卡片上**:点标题行最右端的 **⚙**(`刷新 [⚙]
|
|
37
|
-
- **配色方案**:六个色板按钮,**点击即时生效**(不需要"保存");**默认视图**:年 /
|
|
36
|
+
- ⚙️ **设置就在卡片上**:点标题行最右端的 **⚙**(`刷新 [⚙]`)弹出**悬浮设置面板** —— 位置在 ⚙ 正上方 8px、水平居中对齐、贴边留 12px,超出视口会自动钳制;点面板外的任意位置、按 **Esc**、或再点一次 ⚙ 都会收起。**插件不再往 DSH 设置(设置 → 插件 → 插件配置)里注册任何卡片**,所以那里看不到本插件。
|
|
37
|
+
- **配色方案**:六个色板按钮,**点击即时生效**(不需要"保存");**默认视图**:年 / 月,决定新会话页面首次打开时显示哪个视图(当次会话手动切换只影响当前页面)。面板底部是阈值图例(悬停看各档范围)与一行说明。
|
|
38
38
|
- 写入失败时面板底部会红字提示"保存失败,已回到服务端的值"(settings scope 复核后回滚乐观值)。
|
|
39
39
|
- 配置经 `token-heatmap` settings namespace 持久化到 `<DSH_HOME>/settings.yaml`(0.1.1 及更早版本存在 `<DSH_HOME>/storages/token-heatmap-config.json` 的旧配置会在启动时自动迁移)。
|
|
40
40
|
|
|
@@ -91,6 +91,7 @@ dsh plugin --profile web remove @kidli1412/dsh-token-heatmap
|
|
|
91
91
|
- **0.4.0(设置搬进卡片)**:不再注册官方 `settings.plugin.item` 插槽——设置页(设置 → 插件 → 插件配置)里不再有本插件的卡片,配色与默认视图改在卡片自己的 ⚙ 面板里改,**点击即时生效**(去掉了草稿/保存/放弃那套)。Host 侧 `settings.register("token-heatmap", schema)` 保留:它是 settings.yaml 的校验与持久化管道,与 UI 卡片无关(官方 `settings` 服务的 `get`/`update` 只对已注册 namespace 生效)。升级只影响设置入口位置,已有配置不动。
|
|
92
92
|
|
|
93
93
|
- **0.4.1(fork 会话不再重复计入父会话用量)**:DSH 的 fork 子会话(header `isSeeded=true`)日志以父会话事件的完整复制开头,其前 `inheritedEventCount` 个事件是**父会话**的 usage(父会话折叠时已计入)。此前折叠从 seq 0 读整份日志,同一批 token 被计两次——实测 2026-09-14 由 8.34 亿虚增到 13.15 亿。现在三条折叠路径(`collectUsage` 的 live 折叠、`session/event` 实时监听、stored 日志读取)都从 fork 切点开始:live 会话用官方 `session.inheritedEventCount`;stored 日志用最后一个带 `data.inherited === true` 的 `session/end-seed` 的 `seq + 1`(未打标记的 `session/end-seed` 是 compaction 边界,不算切点,与 `dsh-session-format-v2-to-v3` 的切点推导一致)。**resume 不是 fork**:`isSeeded=false` 的会话种子是它自己的历史,仍整份折叠。缓存格式版本提升到 2,旧缓存(可能含重复计入的天数)会被丢弃重建;父会话日志不可得的极端情况下会少计而非多计。
|
|
94
|
+
- **0.4.2(设置改成悬浮面板)**:⚙ 面板从"卡片底部的内嵌条"改为**悬浮面板**——portal 到 `document.body`、`position:fixed`,锚在 ⚙ 上方 8px 且水平居中对齐,按视口钳制(12px 边距),关闭方式为点外部/Esc/再点 ⚙;表面沿用 DSH 原生弹层 token(`--dsw-specific-menu` + `--dsw-elevation-prominent`,配合 `--dsw-elevation-stroke-color` 的发丝边),与底部统计 pill 的弹层一致。为此客户端 bundle 新增 `require("react-dom")`(原生的 `createPortal`),DSH 的模块图里 react-dom 一直存在,旧宿主不受影响。
|
|
94
95
|
|
|
95
96
|
## License
|
|
96
97
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -37,6 +37,9 @@ window.__ModuleLoader__.load({
|
|
|
37
37
|
|
|
38
38
|
let react = require("react");
|
|
39
39
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
40
|
+
// Only for the settings panel, which is PORTALED to document.body so the
|
|
41
|
+
// card's own box can never clip it (the pattern the stat pills use).
|
|
42
|
+
let reactDom = require("react-dom");
|
|
40
43
|
|
|
41
44
|
//#region css
|
|
42
45
|
const css = [
|
|
@@ -88,23 +91,23 @@ window.__ModuleLoader__.load({
|
|
|
88
91
|
".thm_error{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger);border-radius:8px;padding:7px 8px;font-size:11px;line-height:16px;display:flex;justify-content:space-between;align-items:center;gap:8px}",
|
|
89
92
|
".thm_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0}",
|
|
90
93
|
".thm_loading{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;padding:6px 2px}",
|
|
91
|
-
//
|
|
92
|
-
// configures itself; nothing registers into 设置 → 插件 → 插件配置.
|
|
94
|
+
// The ⚙ at the row's right end opens a floating pill panel — the
|
|
95
|
+
// card configures itself; nothing registers into 设置 → 插件 → 插件配置.
|
|
93
96
|
".thm_gear{cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:6px;align-items:center;padding:2px 4px;display:inline-flex}",
|
|
94
97
|
".thm_gear:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
95
98
|
".thm_gear[data-active=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
96
|
-
// The panel
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
".
|
|
101
|
-
".
|
|
102
|
-
".
|
|
103
|
-
".
|
|
99
|
+
// The settings panel mirrors DSH's own pill dialogs: portaled to
|
|
100
|
+
// document.body, position:fixed (so the card can never clip it),
|
|
101
|
+
// measure-then-place above the ⚙ and clamped to the viewport, with
|
|
102
|
+
// the shipped dialog surface + elevation tokens.
|
|
103
|
+
".thm_panel{position:fixed;z-index:1100;box-sizing:border-box;background:var(--dsw-specific-menu,#fff);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);width:400px;max-width:calc(100vw - 24px);box-shadow:var(--dsw-elevation-prominent,0 8px 32px rgba(0,0,0,.16));color:var(--dsw-alias-label-secondary);cursor:default;border:0;border-radius:12px;padding:16px;font-size:12px;line-height:18px;text-align:left;font-weight:400}",
|
|
104
|
+
".thm_panelHead{color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:center;gap:16px;margin-bottom:8px;font-weight:500;display:flex}",
|
|
105
|
+
".thm_panelTitleLabel{align-items:center;gap:6px;display:inline-flex}",
|
|
106
|
+
".thm_panelTitleLabel svg{flex:none;width:14px;height:14px}",
|
|
107
|
+
".thm_panelRule{border-top:.5px solid var(--dsw-alias-border-l2);margin-bottom:12px}",
|
|
104
108
|
".thm_group{flex-direction:column;gap:5px;display:flex}",
|
|
109
|
+
".thm_group+.thm_group{margin-top:14px}",
|
|
105
110
|
".thm_groupLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}",
|
|
106
|
-
".thm_hint{color:var(--dsw-alias-label-caption);font-size:10px;line-height:14px}",
|
|
107
|
-
".thm_hintError{color:var(--dsw-alias-state-error-primary)}",
|
|
108
111
|
".thm_swatches{flex-wrap:wrap;gap:6px;display:flex}",
|
|
109
112
|
".thm_swatch{cursor:pointer;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l2);background:0 0;border-radius:6px;padding:3px 8px;font:inherit;display:flex}",
|
|
110
113
|
".thm_swatch:hover{border-color:var(--dsw-alias-label-dimmed)}",
|
|
@@ -115,7 +118,11 @@ window.__ModuleLoader__.load({
|
|
|
115
118
|
".thm_seg{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;align-items:center;display:inline-flex;overflow:hidden;width:max-content}",
|
|
116
119
|
".thm_segBtn{cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;padding:1px 10px;font:inherit;font-size:11px;line-height:18px}",
|
|
117
120
|
".thm_segBtn:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
118
|
-
".thm_segBtn[data-active=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);font-weight:600}"
|
|
121
|
+
".thm_segBtn[data-active=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);font-weight:600}",
|
|
122
|
+
".thm_panelFoot{border-top:.5px solid var(--dsw-alias-border-l2);margin-top:14px;padding-top:10px}",
|
|
123
|
+
".thm_footLegend{align-items:center;gap:3px;font-size:10px;line-height:14px;display:flex;color:var(--dsw-alias-label-tertiary)}",
|
|
124
|
+
".thm_footRule{color:var(--dsw-alias-label-caption);margin-top:6px;font-size:10px;line-height:14px}",
|
|
125
|
+
".thm_footError{color:var(--dsw-alias-state-error-primary);margin-top:6px;font-size:10px;line-height:14px}"
|
|
119
126
|
].join("");
|
|
120
127
|
const tagId = "@kidli1412/dsh-token-heatmap/TokenHeatmap.module.css";
|
|
121
128
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
@@ -162,12 +169,14 @@ window.__ModuleLoader__.load({
|
|
|
162
169
|
gear: "thm_gear",
|
|
163
170
|
settingsPanel: "thm_panel",
|
|
164
171
|
panelHead: "thm_panelHead",
|
|
165
|
-
|
|
166
|
-
|
|
172
|
+
panelTitleLabel: "thm_panelTitleLabel",
|
|
173
|
+
panelRule: "thm_panelRule",
|
|
174
|
+
panelFoot: "thm_panelFoot",
|
|
175
|
+
footLegend: "thm_footLegend",
|
|
176
|
+
footRule: "thm_footRule",
|
|
177
|
+
footError: "thm_footError",
|
|
167
178
|
settingsGroup: "thm_group",
|
|
168
179
|
settingsGroupLabel: "thm_groupLabel",
|
|
169
|
-
settingsHint: "thm_hint",
|
|
170
|
-
settingsHintError: "thm_hintError",
|
|
171
180
|
settingsSwatches: "thm_swatches",
|
|
172
181
|
settingsSwatch: "thm_swatch",
|
|
173
182
|
settingsSwatchCells: "thm_swatchCells",
|
|
@@ -217,7 +226,7 @@ window.__ModuleLoader__.load({
|
|
|
217
226
|
settingsRed: "红色",
|
|
218
227
|
settingsPurple: "紫色",
|
|
219
228
|
settingsTeal: "青色",
|
|
220
|
-
|
|
229
|
+
settingsThresholdNote: "颜色按每天 token 数的绝对阈值分档(悬停图例看各档范围)。",
|
|
221
230
|
settingsSaveFailed: "保存失败,已回到服务端的值,请重试。",
|
|
222
231
|
settingsExpand: "展开设置",
|
|
223
232
|
settingsCollapse: "收起设置"
|
|
@@ -258,7 +267,7 @@ window.__ModuleLoader__.load({
|
|
|
258
267
|
settingsRed: "Red",
|
|
259
268
|
settingsPurple: "Purple",
|
|
260
269
|
settingsTeal: "Teal",
|
|
261
|
-
|
|
270
|
+
settingsThresholdNote: "Colors follow absolute per-day token thresholds (hover the legend for each range).",
|
|
262
271
|
settingsSaveFailed: "Save failed; the server value was restored.",
|
|
263
272
|
settingsExpand: "Show settings",
|
|
264
273
|
settingsCollapse: "Hide settings"
|
|
@@ -606,16 +615,30 @@ window.__ModuleLoader__.load({
|
|
|
606
615
|
// Month-view cursor (`YYYY-MM`); initialised from the current month
|
|
607
616
|
// and re-anchored when it would fall outside the navigable bounds.
|
|
608
617
|
const [monthCursor, setMonthCursor] = react.useState(() => monthKey(Date.now()));
|
|
609
|
-
//
|
|
610
|
-
// configures itself here instead of living in
|
|
618
|
+
// The ⚙ at the row's right end opens the floating settings panel;
|
|
619
|
+
// the card configures itself here instead of living in
|
|
620
|
+
// 设置 → 插件 → 插件配置. The panel is PORTALED to document.body and
|
|
621
|
+
// anchored to this button, so the open state lives here with it.
|
|
622
|
+
const gearRef = react.useRef(null);
|
|
611
623
|
const [settingsOpen, setSettingsOpen] = react.useState(false);
|
|
612
624
|
react.useEffect(() => {
|
|
613
625
|
if (!settingsOpen) return;
|
|
614
626
|
const onKeyDown = (event) => {
|
|
615
627
|
if (event.key === "Escape") setSettingsOpen(false);
|
|
616
628
|
};
|
|
629
|
+
const onPointerDown = (event) => {
|
|
630
|
+
const gear = gearRef.current;
|
|
631
|
+
if (gear !== null && gear.contains(event.target)) return;
|
|
632
|
+
const panel = document.querySelector(`.${S.settingsPanel}`);
|
|
633
|
+
if (panel !== null && panel.contains(event.target)) return;
|
|
634
|
+
setSettingsOpen(false);
|
|
635
|
+
};
|
|
617
636
|
document.addEventListener("keydown", onKeyDown);
|
|
618
|
-
|
|
637
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
638
|
+
return () => {
|
|
639
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
640
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
641
|
+
};
|
|
619
642
|
}, [settingsOpen]);
|
|
620
643
|
const loaderRef = react.useRef(null);
|
|
621
644
|
if (loaderRef.current === null) loaderRef.current = createLoader();
|
|
@@ -805,16 +828,19 @@ window.__ModuleLoader__.load({
|
|
|
805
828
|
onClick: load,
|
|
806
829
|
children: dict("refresh")
|
|
807
830
|
}),
|
|
808
|
-
// The
|
|
809
|
-
//
|
|
810
|
-
//
|
|
831
|
+
// The ⚙ owns the row's right end; the ‹ › stepper
|
|
832
|
+
// and the view switch sit left of it, the stepper
|
|
833
|
+
// next to the stats it moves through. This button is
|
|
834
|
+
// also the settings panel's anchor.
|
|
811
835
|
react_jsx_runtime.jsx("button", {
|
|
812
836
|
type: "button",
|
|
837
|
+
ref: gearRef,
|
|
813
838
|
className: S.gear,
|
|
814
839
|
"data-active": settingsOpen,
|
|
840
|
+
"aria-haspopup": "dialog",
|
|
815
841
|
"aria-expanded": settingsOpen,
|
|
816
|
-
"aria-label": dict("
|
|
817
|
-
title: dict("
|
|
842
|
+
"aria-label": dict(settingsOpen ? "settingsCollapse" : "settingsExpand"),
|
|
843
|
+
title: dict(settingsOpen ? "settingsCollapse" : "settingsExpand"),
|
|
818
844
|
onClick: () => setSettingsOpen(!settingsOpen),
|
|
819
845
|
children: react_jsx_runtime.jsx("svg", {
|
|
820
846
|
width: 14,
|
|
@@ -933,6 +959,8 @@ window.__ModuleLoader__.load({
|
|
|
933
959
|
store: configStore,
|
|
934
960
|
palette,
|
|
935
961
|
translate,
|
|
962
|
+
legend: levelLegend,
|
|
963
|
+
anchor: gearRef.current,
|
|
936
964
|
onClose: () => setSettingsOpen(false)
|
|
937
965
|
}) : null
|
|
938
966
|
]
|
|
@@ -948,10 +976,45 @@ window.__ModuleLoader__.load({
|
|
|
948
976
|
//#endregion
|
|
949
977
|
|
|
950
978
|
//#region TokenHeatmapInlineSettings
|
|
979
|
+
/** Distance between the ⚙ and the panel's bottom edge (native dialog spec). */
|
|
980
|
+
const PANEL_GAP = 8;
|
|
981
|
+
/** Viewport margin the placement clamp keeps (native dialog spec). */
|
|
982
|
+
const PANEL_MARGIN = 12;
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Place the floating panel above its ⚙ trigger, clamped to the viewport —
|
|
986
|
+
* the same measure-then-place flow (and the same 8px gap / 12px margin)
|
|
987
|
+
* the built-in stat dialogs use. The panel is `position:fixed` and lives
|
|
988
|
+
* in `document.body`, so the card can never clip it.
|
|
989
|
+
* @param trigger - the ⚙ button the panel is anchored to.
|
|
990
|
+
* @param panel - the `position:fixed` panel element.
|
|
991
|
+
*/
|
|
992
|
+
function placePanel(trigger, panel) {
|
|
993
|
+
const anchor = trigger.getBoundingClientRect();
|
|
994
|
+
const width = panel.offsetWidth;
|
|
995
|
+
const height = panel.offsetHeight;
|
|
996
|
+
if (width === 0 || height === 0) return;
|
|
997
|
+
const viewportWidth = window.innerWidth;
|
|
998
|
+
const viewportHeight = window.innerHeight;
|
|
999
|
+
const left = Math.min(
|
|
1000
|
+
Math.max(PANEL_MARGIN, anchor.left + anchor.width / 2 - width / 2),
|
|
1001
|
+
Math.max(PANEL_MARGIN, viewportWidth - width - PANEL_MARGIN)
|
|
1002
|
+
);
|
|
1003
|
+
let top = anchor.top - height - PANEL_GAP;
|
|
1004
|
+
if (top < PANEL_MARGIN) top = Math.min(anchor.bottom + PANEL_GAP, viewportHeight - height - PANEL_MARGIN);
|
|
1005
|
+
panel.style.left = `${Math.round(left)}px`;
|
|
1006
|
+
panel.style.top = `${Math.round(Math.max(PANEL_MARGIN, top))}px`;
|
|
1007
|
+
panel.style.visibility = "visible";
|
|
1008
|
+
}
|
|
1009
|
+
|
|
951
1010
|
/**
|
|
952
|
-
* The card's
|
|
953
|
-
*
|
|
954
|
-
*
|
|
1011
|
+
* The card's settings panel: 配色方案 and 默认视图, edited from the card
|
|
1012
|
+
* itself instead of inside 设置 → 插件 → 插件配置 (this plugin no longer
|
|
1013
|
+
* registers into the `settings.plugin.item` seat).
|
|
1014
|
+
*
|
|
1015
|
+
* It is PORTALED to `document.body` (the card's own box can never clip
|
|
1016
|
+
* it) and anchored to the ⚙ through `anchor`; the owner closes it on
|
|
1017
|
+
* outside pointerdown and Escape, so this component only draws.
|
|
955
1018
|
*
|
|
956
1019
|
* Every control applies immediately through the bound settings scope —
|
|
957
1020
|
* there is no draft/save step, so the component owns only a local save
|
|
@@ -964,9 +1027,12 @@ window.__ModuleLoader__.load({
|
|
|
964
1027
|
* @param props.store - config store (set writes through the scope).
|
|
965
1028
|
* @param props.palette - active palette (level 0..4 → css color).
|
|
966
1029
|
* @param props.translate - interpolation helper for translated labels.
|
|
967
|
-
* @param props.
|
|
1030
|
+
* @param props.legend - level labels for the palette footnote.
|
|
1031
|
+
* @param props.anchor - the ⚙ button the panel is placed against.
|
|
1032
|
+
* @param props.onClose - collapse the panel.
|
|
968
1033
|
*/
|
|
969
|
-
function TokenHeatmapInlineSettings({ dict, config, store, palette, translate, onClose }) {
|
|
1034
|
+
function TokenHeatmapInlineSettings({ dict, config, store, palette, translate, legend, anchor, onClose }) {
|
|
1035
|
+
const panelRef = react.useRef(null);
|
|
970
1036
|
const [saveFailed, setSaveFailed] = react.useState(false);
|
|
971
1037
|
const schemes = Object.keys(COLOR_SCHEMES);
|
|
972
1038
|
// Optimistic local echo: the settings scope only reports the new value
|
|
@@ -974,6 +1040,26 @@ window.__ModuleLoader__.load({
|
|
|
974
1040
|
const [pending, setPending] = react.useState({});
|
|
975
1041
|
const scheme = pending.colorScheme ?? config.colorScheme;
|
|
976
1042
|
const defaultView = pending.defaultView ?? config.defaultView;
|
|
1043
|
+
// Place before paint (and again when the palette reflows the panel):
|
|
1044
|
+
// the trigger's box is only meaningful once the card is laid out.
|
|
1045
|
+
react.useLayoutEffect(() => {
|
|
1046
|
+
const panel = panelRef.current;
|
|
1047
|
+
if (panel === null || anchor === null || anchor === void 0) return;
|
|
1048
|
+
placePanel(anchor, panel);
|
|
1049
|
+
});
|
|
1050
|
+
react.useEffect(() => {
|
|
1051
|
+
const replace = () => {
|
|
1052
|
+
const panel = panelRef.current;
|
|
1053
|
+
if (panel === null || anchor === null || anchor === void 0) return;
|
|
1054
|
+
placePanel(anchor, panel);
|
|
1055
|
+
};
|
|
1056
|
+
window.addEventListener("resize", replace);
|
|
1057
|
+
window.addEventListener("scroll", replace, true);
|
|
1058
|
+
return () => {
|
|
1059
|
+
window.removeEventListener("resize", replace);
|
|
1060
|
+
window.removeEventListener("scroll", replace, true);
|
|
1061
|
+
};
|
|
1062
|
+
}, [anchor]);
|
|
977
1063
|
const commit = async (patch) => {
|
|
978
1064
|
setPending((current) => ({ ...current, ...patch }));
|
|
979
1065
|
setSaveFailed(false);
|
|
@@ -990,28 +1076,43 @@ window.__ModuleLoader__.load({
|
|
|
990
1076
|
if (value !== defaultView) commit({ defaultView: value });
|
|
991
1077
|
};
|
|
992
1078
|
const labelOf = (name) => dict(`settings${name[0].toUpperCase()}${name.slice(1)}`);
|
|
993
|
-
|
|
1079
|
+
const title = dict("settingsTitle");
|
|
1080
|
+
const panel = react_jsx_runtime.jsxs("div", {
|
|
1081
|
+
ref: panelRef,
|
|
994
1082
|
className: S.settingsPanel,
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
},
|
|
1083
|
+
role: "dialog",
|
|
1084
|
+
"aria-label": title,
|
|
1085
|
+
// Nothing may paint at the viewport origin before placement runs.
|
|
1086
|
+
style: { visibility: "hidden" },
|
|
1000
1087
|
children: [
|
|
1001
1088
|
react_jsx_runtime.jsxs("div", {
|
|
1002
1089
|
className: S.panelHead,
|
|
1003
1090
|
children: [
|
|
1004
|
-
react_jsx_runtime.
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1091
|
+
react_jsx_runtime.jsxs("span", {
|
|
1092
|
+
className: S.panelTitleLabel,
|
|
1093
|
+
children: [
|
|
1094
|
+
react_jsx_runtime.jsxs("svg", {
|
|
1095
|
+
width: 14,
|
|
1096
|
+
height: 14,
|
|
1097
|
+
viewBox: "0 0 24 24",
|
|
1098
|
+
fill: "none",
|
|
1099
|
+
stroke: "currentColor",
|
|
1100
|
+
"stroke-width": 2,
|
|
1101
|
+
"stroke-linecap": "round",
|
|
1102
|
+
"stroke-linejoin": "round",
|
|
1103
|
+
"aria-hidden": "true",
|
|
1104
|
+
children: [
|
|
1105
|
+
react_jsx_runtime.jsx("circle", { cx: 12, cy: 12, r: 9 }),
|
|
1106
|
+
react_jsx_runtime.jsx("path", { d: "M12 11v5" }),
|
|
1107
|
+
react_jsx_runtime.jsx("path", { d: "M12 8h.01" })
|
|
1108
|
+
]
|
|
1109
|
+
}),
|
|
1110
|
+
react_jsx_runtime.jsx("span", { children: title })
|
|
1111
|
+
]
|
|
1012
1112
|
})
|
|
1013
1113
|
]
|
|
1014
1114
|
}),
|
|
1115
|
+
react_jsx_runtime.jsx("div", { className: S.panelRule, "aria-hidden": "true" }),
|
|
1015
1116
|
react_jsx_runtime.jsxs("div", {
|
|
1016
1117
|
className: S.settingsGroup,
|
|
1017
1118
|
children: [
|
|
@@ -1058,21 +1159,36 @@ window.__ModuleLoader__.load({
|
|
|
1058
1159
|
onClick: () => chooseView(mode),
|
|
1059
1160
|
children: dict(mode === "month" ? "viewMonth" : "viewYear")
|
|
1060
1161
|
}, mode))
|
|
1061
|
-
}),
|
|
1062
|
-
react_jsx_runtime.jsx("span", {
|
|
1063
|
-
className: S.settingsHint,
|
|
1064
|
-
children: dict("settingsDefaultViewHint")
|
|
1065
1162
|
})
|
|
1066
1163
|
]
|
|
1067
1164
|
}),
|
|
1068
|
-
react_jsx_runtime.
|
|
1069
|
-
className:
|
|
1070
|
-
children:
|
|
1071
|
-
|
|
1072
|
-
|
|
1165
|
+
react_jsx_runtime.jsxs("div", {
|
|
1166
|
+
className: S.panelFoot,
|
|
1167
|
+
children: [
|
|
1168
|
+
react_jsx_runtime.jsxs("div", {
|
|
1169
|
+
className: S.footLegend,
|
|
1170
|
+
children: [
|
|
1171
|
+
dict("less"),
|
|
1172
|
+
...palette.map((color, level) => react_jsx_runtime.jsx("span", {
|
|
1173
|
+
className: S.legendCell,
|
|
1174
|
+
style: { background: color },
|
|
1175
|
+
title: legend[level] ?? ""
|
|
1176
|
+
}, `foot-${level}`)),
|
|
1177
|
+
dict("more")
|
|
1178
|
+
]
|
|
1179
|
+
}),
|
|
1180
|
+
saveFailed ? react_jsx_runtime.jsx("div", {
|
|
1181
|
+
className: S.footError,
|
|
1182
|
+
children: dict("settingsSaveFailed")
|
|
1183
|
+
}) : react_jsx_runtime.jsx("div", {
|
|
1184
|
+
className: S.footRule,
|
|
1185
|
+
children: dict("settingsThresholdNote")
|
|
1186
|
+
})
|
|
1187
|
+
]
|
|
1073
1188
|
})
|
|
1074
1189
|
]
|
|
1075
1190
|
});
|
|
1191
|
+
return reactDom.createPortal(panel, document.body);
|
|
1076
1192
|
}
|
|
1077
1193
|
//#endregion
|
|
1078
1194
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kidli1412/dsh-token-heatmap",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "DSH web plugin: GitHub-style daily token-usage heatmap on the new-session screen with switchable year/month views and six color schemes. The card configures itself
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "DSH web plugin: GitHub-style daily token-usage heatmap on the new-session screen with switchable year/month views and six color schemes. The card configures itself through a floating ⚙ panel (palette + default view) — no DSH settings card — and shows today / this-month / all-time totals.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|