dsh-niuma 0.2.1 → 0.2.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/CHANGELOG.md +16 -0
- package/README.md +3 -0
- package/docs/preview-light-dark.png +0 -0
- package/lib/client.js +251 -18
- package/lib/client.js.map +2 -2
- package/package.json +1 -1
- package/src/client/index.js +212 -16
- package/src/core.mjs +23 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录用户可感知的变化。版本号遵循语义化版本。
|
|
4
4
|
|
|
5
|
+
## 0.2.2 — 2026-10-22
|
|
6
|
+
|
|
7
|
+
### 新增:主金额小数位 + 刷新频率 + 滚动数字/加速
|
|
8
|
+
|
|
9
|
+
- **主金额改连续「里程表」滚动**:每一位按位权连续转动——最后一位小数转得最快(0-9 快速循环),
|
|
10
|
+
前一位慢 10 倍,整数位几乎不动;不是整块金额跳变,而是数字持续往上滚
|
|
11
|
+
- **加速开关**:主金额右侧「加速」勾选后强制毫秒级刷新(`requestAnimationFrame`,≈16ms),
|
|
12
|
+
滚轮每帧更新,低位小数飞快转动、高位慢慢进位
|
|
13
|
+
- **小数位数可选(0–6,默认 4)**:主卡金额右侧「小数 [n] 位」,改完即时保存、立即生效
|
|
14
|
+
- **刷新频率可调**:设置区新增「刷新频率」行
|
|
15
|
+
- 按秒(1s,默认,最省电)
|
|
16
|
+
- 250 ms / 100 ms / 50 ms(毫秒级滚动)
|
|
17
|
+
- 刷新率(≈16 ms,requestAnimationFrame,跟随屏幕刷新连续跳动)
|
|
18
|
+
- core 新增配置 `decimals`(0–6 整数)、`updateMs`(16–1000 整数)与 `turbo`(布尔),宿主写接口同样校验
|
|
19
|
+
- 刷新由 `useClock(updateMs)` 驱动:≥50ms 用 setInterval,≤16ms 用 rAF
|
|
20
|
+
|
|
5
21
|
## 0.2.1 — 2026-10-21
|
|
6
22
|
|
|
7
23
|
### 新增:本月工作天数进度
|
package/README.md
CHANGED
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
- 节假日数据内置在插件中(2026 官方 / 2027 预估),设置区不再需要手工维护一长串日期;2027 官方安排公布后随插件更新
|
|
33
33
|
- 午休可设置起止,**可勾选午休是否算工时**;午休不算工时则午休期间金额冻结
|
|
34
34
|
- 状态徽标:休息日 / 未上班 / 搬砖中 / 午休中 / 已下班
|
|
35
|
+
- 主金额是**连续里程表滚动**:小数末位飞快转、前一位慢 10 倍、整数位慢慢进位;勾选「加速」按屏幕刷新率每帧滚动
|
|
36
|
+
- 主金额**小数位可选(0–6)**,右侧下拉即时生效
|
|
37
|
+
- **刷新频率可选**:按秒 / 250ms / 100ms / 50ms / 屏幕刷新率(≈16ms 连续滚动)
|
|
35
38
|
- 今日进度条、距上班/距下班倒计时
|
|
36
39
|
- **本月已工作 / 剩余工作日**(与工资口径一致:节假日、周末、调休都算进去)
|
|
37
40
|
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -187,7 +187,13 @@ var DEFAULTS = Object.freeze({
|
|
|
187
187
|
lunchEnd: "13:00",
|
|
188
188
|
lunchPaid: false,
|
|
189
189
|
holidays: DEFAULT_HOLIDAYS,
|
|
190
|
-
extraWorkdays: DEFAULT_EXTRA_WORKDAYS
|
|
190
|
+
extraWorkdays: DEFAULT_EXTRA_WORKDAYS,
|
|
191
|
+
/** 主金额小数位(0-6) */
|
|
192
|
+
decimals: 4,
|
|
193
|
+
/** 面板刷新间隔 ms:1000=按秒;16≈屏幕刷新率(连续滚动);中间值为毫秒级 */
|
|
194
|
+
updateMs: 1e3,
|
|
195
|
+
/** 加速(滚动模式):true 时强制毫秒级刷新(≈屏幕刷新率),数字连续滚动 */
|
|
196
|
+
turbo: false
|
|
191
197
|
});
|
|
192
198
|
var SALARY_MAX = 1e7;
|
|
193
199
|
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -266,6 +272,21 @@ function normalizeConfig(patch) {
|
|
|
266
272
|
}
|
|
267
273
|
merged.lunchEnabled = merged.lunchEnabled !== false;
|
|
268
274
|
merged.lunchPaid = merged.lunchPaid === true;
|
|
275
|
+
merged.turbo = merged.turbo === true;
|
|
276
|
+
const decimals = Number(merged.decimals);
|
|
277
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 6) {
|
|
278
|
+
errors.push("\u5C0F\u6570\u4F4D\u6570\u9700\u8981\u662F 0~6 \u7684\u6574\u6570");
|
|
279
|
+
merged.decimals = Number.isInteger(decimals) ? decimals : DEFAULTS.decimals;
|
|
280
|
+
} else {
|
|
281
|
+
merged.decimals = decimals;
|
|
282
|
+
}
|
|
283
|
+
const updateMs = Number(merged.updateMs);
|
|
284
|
+
if (!Number.isInteger(updateMs) || updateMs < 16 || updateMs > 1e3) {
|
|
285
|
+
errors.push("\u5237\u65B0\u95F4\u9694\u9700\u8981\u662F 16~1000ms \u7684\u6574\u6570");
|
|
286
|
+
merged.updateMs = Number.isInteger(updateMs) ? updateMs : DEFAULTS.updateMs;
|
|
287
|
+
} else {
|
|
288
|
+
merged.updateMs = updateMs;
|
|
289
|
+
}
|
|
269
290
|
const holidays = normalizeDateList(merged.holidays);
|
|
270
291
|
const extraWorkdays = normalizeDateList(merged.extraWorkdays);
|
|
271
292
|
errors.push(...holidays.errors, ...extraWorkdays.errors);
|
|
@@ -588,6 +609,12 @@ var zh = {
|
|
|
588
609
|
"configFailed": "\u8BBE\u7F6E\u8BFB\u53D6\u5931\u8D25\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C",
|
|
589
610
|
"retry": "\u91CD\u8BD5",
|
|
590
611
|
"settings": "\u8BBE\u7F6E",
|
|
612
|
+
"decimalsLabel": "\u5C0F\u6570",
|
|
613
|
+
"decimalsUnit": "\u4F4D",
|
|
614
|
+
"turboLabel": "\u52A0\u901F",
|
|
615
|
+
"refreshRate": "\u5237\u65B0\u9891\u7387",
|
|
616
|
+
"rateSecond": "\u6309\u79D2\uFF081s\uFF09",
|
|
617
|
+
"rateFrame": "\u5237\u65B0\u7387\uFF08\u224816ms\uFF09",
|
|
591
618
|
"settingsHint": "\u6539\u5B8C\u70B9\u300C\u4FDD\u5B58\u300D\u751F\u6548\uFF1B\u683C\u5F0F\u4E0D\u5BF9\u4F1A\u5728\u4E0B\u65B9\u7EA2\u5B57\u63D0\u793A\uFF0C\u4E0D\u5F71\u54CD\u8BA1\u65F6\u3002",
|
|
592
619
|
"salaryField": "\u5B9E\u6536\u6708\u85AA",
|
|
593
620
|
"groupSalary": "\u5DE5\u8D44",
|
|
@@ -686,6 +713,12 @@ var en = {
|
|
|
686
713
|
"configFailed": "Failed to load settings, using defaults",
|
|
687
714
|
"retry": "Retry",
|
|
688
715
|
"settings": "Settings",
|
|
716
|
+
"decimalsLabel": "Decimals",
|
|
717
|
+
"decimalsUnit": "dp",
|
|
718
|
+
"turboLabel": "Turbo",
|
|
719
|
+
"refreshRate": "Refresh rate",
|
|
720
|
+
"rateSecond": "Per second (1s)",
|
|
721
|
+
"rateFrame": "Display rate (~16ms)",
|
|
689
722
|
"settingsHint": "Click Save to apply. Invalid input is shown in red below; the ticker keeps running.",
|
|
690
723
|
"salaryField": "Net monthly salary",
|
|
691
724
|
"groupSalary": "Salary",
|
|
@@ -763,7 +796,8 @@ function formFromConfig(config) {
|
|
|
763
796
|
lunchEnabled: config.lunchEnabled !== false,
|
|
764
797
|
lunchStart: String(config.lunchStart == null ? "" : config.lunchStart),
|
|
765
798
|
lunchEnd: String(config.lunchEnd == null ? "" : config.lunchEnd),
|
|
766
|
-
lunchPaid: config.lunchPaid === true
|
|
799
|
+
lunchPaid: config.lunchPaid === true,
|
|
800
|
+
updateMs: String(config.updateMs == null ? 1e3 : config.updateMs)
|
|
767
801
|
};
|
|
768
802
|
}
|
|
769
803
|
function patchFromForm(form) {
|
|
@@ -775,7 +809,8 @@ function patchFromForm(form) {
|
|
|
775
809
|
lunchEnabled: Boolean(form.lunchEnabled),
|
|
776
810
|
lunchStart: String(form.lunchStart || "").trim(),
|
|
777
811
|
lunchEnd: String(form.lunchEnd || "").trim(),
|
|
778
|
-
lunchPaid: Boolean(form.lunchPaid)
|
|
812
|
+
lunchPaid: Boolean(form.lunchPaid),
|
|
813
|
+
updateMs: Number(form.updateMs)
|
|
779
814
|
};
|
|
780
815
|
}
|
|
781
816
|
function useRemoteConfig() {
|
|
@@ -811,18 +846,21 @@ function useRemoteConfig() {
|
|
|
811
846
|
const refresh = import_react.default.useCallback(() => setTick((n) => n + 1), []);
|
|
812
847
|
return { ...state, refresh };
|
|
813
848
|
}
|
|
814
|
-
function
|
|
849
|
+
function useClock(updateMs) {
|
|
815
850
|
const [now, setNow] = import_react.default.useState(() => /* @__PURE__ */ new Date());
|
|
816
851
|
import_react.default.useEffect(() => {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
852
|
+
if (updateMs <= 16) {
|
|
853
|
+
let raf = 0;
|
|
854
|
+
const loop = () => {
|
|
855
|
+
setNow(/* @__PURE__ */ new Date());
|
|
856
|
+
raf = requestAnimationFrame(loop);
|
|
857
|
+
};
|
|
858
|
+
raf = requestAnimationFrame(loop);
|
|
859
|
+
return () => cancelAnimationFrame(raf);
|
|
860
|
+
}
|
|
861
|
+
const timer = setInterval(() => setNow(/* @__PURE__ */ new Date()), updateMs);
|
|
824
862
|
return () => clearInterval(timer);
|
|
825
|
-
}, []);
|
|
863
|
+
}, [updateMs]);
|
|
826
864
|
return now;
|
|
827
865
|
}
|
|
828
866
|
function NiumaIcon(props) {
|
|
@@ -898,10 +936,106 @@ function SectionTitle(props) {
|
|
|
898
936
|
props.hint ? import_react.default.createElement("span", { key: "h", style: { fontSize: 10.5, color: C.label3, ...NUM } }, props.hint) : null
|
|
899
937
|
]);
|
|
900
938
|
}
|
|
939
|
+
var ROLL_BLOCKS = Array.from({ length: 11 }, (_, i) => String(i % 10));
|
|
940
|
+
function RollingDigit(props) {
|
|
941
|
+
const heightEm = 1.22;
|
|
942
|
+
const widthEm = 0.62;
|
|
943
|
+
const pos = Number.isFinite(props.position) ? props.position : 0;
|
|
944
|
+
const wheel = (pos % 10 + 10) % 10;
|
|
945
|
+
return import_react.default.createElement("span", {
|
|
946
|
+
"aria-hidden": "true",
|
|
947
|
+
style: {
|
|
948
|
+
display: "inline-block",
|
|
949
|
+
position: "relative",
|
|
950
|
+
overflow: "hidden",
|
|
951
|
+
width: `${widthEm}em`,
|
|
952
|
+
height: `${heightEm}em`,
|
|
953
|
+
verticalAlign: "baseline"
|
|
954
|
+
}
|
|
955
|
+
}, [
|
|
956
|
+
import_react.default.createElement("span", {
|
|
957
|
+
key: "ghost",
|
|
958
|
+
style: { visibility: "hidden", display: "block", height: `${heightEm}em`, lineHeight: `${heightEm}em` }
|
|
959
|
+
}, "0"),
|
|
960
|
+
import_react.default.createElement("span", {
|
|
961
|
+
key: "strip",
|
|
962
|
+
style: {
|
|
963
|
+
position: "absolute",
|
|
964
|
+
left: 0,
|
|
965
|
+
top: 0,
|
|
966
|
+
display: "flex",
|
|
967
|
+
flexDirection: "column",
|
|
968
|
+
transform: `translateY(${-(wheel * heightEm)}em)`,
|
|
969
|
+
willChange: "transform"
|
|
970
|
+
}
|
|
971
|
+
}, ROLL_BLOCKS.map((value, i) => import_react.default.createElement("span", {
|
|
972
|
+
key: i,
|
|
973
|
+
style: { display: "block", height: `${heightEm}em`, lineHeight: `${heightEm}em` }
|
|
974
|
+
}, value)))
|
|
975
|
+
]);
|
|
976
|
+
}
|
|
977
|
+
function RollingMoney(props) {
|
|
978
|
+
const decimals = Math.max(0, Math.min(6, Number.isInteger(props.decimals) ? props.decimals : 4));
|
|
979
|
+
const text = formatMoney(props.value, decimals);
|
|
980
|
+
const scale = 10 ** decimals;
|
|
981
|
+
const chars = text.split("");
|
|
982
|
+
const significances = new Array(chars.length).fill(-1);
|
|
983
|
+
let seen = 0;
|
|
984
|
+
for (let i = chars.length - 1; i >= 0; i -= 1) {
|
|
985
|
+
if (/[0-9]/.test(chars[i])) {
|
|
986
|
+
significances[i] = seen;
|
|
987
|
+
seen += 1;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return import_react.default.createElement("span", {
|
|
991
|
+
style: {
|
|
992
|
+
display: "inline-flex",
|
|
993
|
+
alignItems: "baseline",
|
|
994
|
+
letterSpacing: 0,
|
|
995
|
+
position: "relative",
|
|
996
|
+
// 等宽字体保证每个数字轮宽度稳定(不裁字、不抖动)
|
|
997
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
998
|
+
...props.style || {}
|
|
999
|
+
}
|
|
1000
|
+
}, [
|
|
1001
|
+
import_react.default.createElement("span", {
|
|
1002
|
+
key: "sr",
|
|
1003
|
+
style: {
|
|
1004
|
+
position: "absolute",
|
|
1005
|
+
width: 1,
|
|
1006
|
+
height: 1,
|
|
1007
|
+
padding: 0,
|
|
1008
|
+
margin: -1,
|
|
1009
|
+
overflow: "hidden",
|
|
1010
|
+
clip: "rect(0,0,0,0)",
|
|
1011
|
+
whiteSpace: "nowrap",
|
|
1012
|
+
border: 0
|
|
1013
|
+
}
|
|
1014
|
+
}, text),
|
|
1015
|
+
import_react.default.createElement("span", {
|
|
1016
|
+
key: "visual",
|
|
1017
|
+
"aria-hidden": "true",
|
|
1018
|
+
style: { display: "inline-flex", alignItems: "baseline" }
|
|
1019
|
+
}, chars.map((char, i) => {
|
|
1020
|
+
if (!/[0-9]/.test(char)) {
|
|
1021
|
+
return import_react.default.createElement("span", {
|
|
1022
|
+
key: `s${i}`,
|
|
1023
|
+
style: { display: "inline-block", lineHeight: "1.22em" }
|
|
1024
|
+
}, char);
|
|
1025
|
+
}
|
|
1026
|
+
const position = (Number(props.value) || 0) * scale / 10 ** significances[i];
|
|
1027
|
+
return import_react.default.createElement(RollingDigit, { key: `d${i}`, position });
|
|
1028
|
+
}))
|
|
1029
|
+
]);
|
|
1030
|
+
}
|
|
901
1031
|
function HeroCard(props) {
|
|
902
1032
|
const t = props.t;
|
|
903
1033
|
const snap = props.snap;
|
|
904
1034
|
const rates = snap.rates;
|
|
1035
|
+
const decimals = Number.isInteger(props.decimals) ? props.decimals : 4;
|
|
1036
|
+
const onDecimalsChange = props.onDecimalsChange;
|
|
1037
|
+
const turbo = props.turbo === true;
|
|
1038
|
+
const onTurboChange = props.onTurboChange;
|
|
905
1039
|
const progress = Math.max(0, Math.min(1, Number(snap.todayProgress) || 0));
|
|
906
1040
|
const percentText = `${(progress * 100).toFixed(1)}%`;
|
|
907
1041
|
const live = snap.status === "working";
|
|
@@ -959,22 +1093,83 @@ function HeroCard(props) {
|
|
|
959
1093
|
key: "amt",
|
|
960
1094
|
style: { display: "flex", alignItems: "baseline", gap: 7, marginTop: 4, flexWrap: "wrap" }
|
|
961
1095
|
}, [
|
|
962
|
-
import_react.default.createElement(
|
|
1096
|
+
import_react.default.createElement(RollingMoney, {
|
|
963
1097
|
key: "val",
|
|
1098
|
+
value: snap.todayEarned,
|
|
1099
|
+
decimals,
|
|
964
1100
|
style: {
|
|
965
1101
|
fontSize: 46,
|
|
966
1102
|
lineHeight: 1.12,
|
|
967
1103
|
fontWeight: 800,
|
|
968
|
-
letterSpacing: "-1.2px",
|
|
969
1104
|
...GOLD,
|
|
970
1105
|
...NUM,
|
|
971
1106
|
...rest ? { opacity: 0.62 } : {}
|
|
972
1107
|
}
|
|
973
|
-
}
|
|
1108
|
+
}),
|
|
974
1109
|
import_react.default.createElement("span", {
|
|
975
1110
|
key: "unit",
|
|
976
1111
|
style: { fontSize: 18, fontWeight: 700, color: C.label2, ...NUM }
|
|
977
|
-
}, "\u5143")
|
|
1112
|
+
}, "\u5143"),
|
|
1113
|
+
import_react.default.createElement("label", {
|
|
1114
|
+
key: "dec",
|
|
1115
|
+
title: t("decimalsLabel"),
|
|
1116
|
+
style: {
|
|
1117
|
+
marginLeft: "auto",
|
|
1118
|
+
display: "inline-flex",
|
|
1119
|
+
alignItems: "center",
|
|
1120
|
+
gap: 3,
|
|
1121
|
+
fontSize: 10,
|
|
1122
|
+
color: C.label3,
|
|
1123
|
+
...NUM
|
|
1124
|
+
}
|
|
1125
|
+
}, [
|
|
1126
|
+
import_react.default.createElement("span", { key: "l" }, t("decimalsLabel")),
|
|
1127
|
+
import_react.default.createElement("select", {
|
|
1128
|
+
key: "s",
|
|
1129
|
+
value: String(decimals),
|
|
1130
|
+
"aria-label": t("decimalsLabel"),
|
|
1131
|
+
onChange: (event) => onDecimalsChange && onDecimalsChange(Number(event.target.value)),
|
|
1132
|
+
style: {
|
|
1133
|
+
height: 22,
|
|
1134
|
+
padding: "0 2px",
|
|
1135
|
+
borderRadius: 6,
|
|
1136
|
+
border: `1px solid ${C.border1}`,
|
|
1137
|
+
background: C.layer2,
|
|
1138
|
+
color: C.label2,
|
|
1139
|
+
fontSize: 10.5,
|
|
1140
|
+
fontVariantNumeric: "tabular-nums",
|
|
1141
|
+
outline: "none",
|
|
1142
|
+
cursor: "pointer"
|
|
1143
|
+
}
|
|
1144
|
+
}, [0, 1, 2, 3, 4, 5, 6].map((n) => import_react.default.createElement("option", { key: n, value: String(n) }, String(n)))),
|
|
1145
|
+
import_react.default.createElement("span", { key: "u" }, t("decimalsUnit"))
|
|
1146
|
+
]),
|
|
1147
|
+
import_react.default.createElement("label", {
|
|
1148
|
+
key: "turbo",
|
|
1149
|
+
title: t("turboLabel"),
|
|
1150
|
+
style: {
|
|
1151
|
+
display: "inline-flex",
|
|
1152
|
+
alignItems: "center",
|
|
1153
|
+
gap: 3,
|
|
1154
|
+
fontSize: 10,
|
|
1155
|
+
padding: "1px 6px",
|
|
1156
|
+
borderRadius: 999,
|
|
1157
|
+
border: `1px solid ${turbo ? `color-mix(in srgb, ${GOLD_TINT} 60%, ${C.border1})` : C.border1}`,
|
|
1158
|
+
background: turbo ? `color-mix(in srgb, ${GOLD_TINT} 16%, transparent)` : "transparent",
|
|
1159
|
+
color: turbo ? C.label2 : C.label3,
|
|
1160
|
+
cursor: "pointer",
|
|
1161
|
+
...NUM
|
|
1162
|
+
}
|
|
1163
|
+
}, [
|
|
1164
|
+
import_react.default.createElement("input", {
|
|
1165
|
+
key: "i",
|
|
1166
|
+
type: "checkbox",
|
|
1167
|
+
checked: turbo,
|
|
1168
|
+
onChange: (event) => onTurboChange && onTurboChange(event.target.checked),
|
|
1169
|
+
style: { accentColor: GOLD_TINT, width: 12, height: 12, margin: 0, cursor: "inherit" }
|
|
1170
|
+
}),
|
|
1171
|
+
import_react.default.createElement("span", { key: "l" }, t("turboLabel"))
|
|
1172
|
+
])
|
|
978
1173
|
]),
|
|
979
1174
|
import_react.default.createElement("div", {
|
|
980
1175
|
key: "chips",
|
|
@@ -1439,6 +1634,13 @@ function SettingsSection(props) {
|
|
|
1439
1634
|
import_react.default.createElement("span", { key: "l" }, t(labelKey))
|
|
1440
1635
|
]);
|
|
1441
1636
|
const lunchDisabled = !form.lunchEnabled;
|
|
1637
|
+
const RATE_OPTIONS = [
|
|
1638
|
+
["1000", t("rateSecond")],
|
|
1639
|
+
["250", "250 ms"],
|
|
1640
|
+
["100", "100 ms"],
|
|
1641
|
+
["50", "50 ms"],
|
|
1642
|
+
["16", t("rateFrame")]
|
|
1643
|
+
];
|
|
1442
1644
|
return import_react.default.createElement("details", {
|
|
1443
1645
|
open: true,
|
|
1444
1646
|
style: { background: C.layer1, border: `1px solid ${C.border1}`, borderRadius: 12, padding: "10px 12px" }
|
|
@@ -1468,6 +1670,14 @@ function SettingsSection(props) {
|
|
|
1468
1670
|
timeRange("lunchStart", "lunchEnd", t("groupLunch"), lunchDisabled),
|
|
1469
1671
|
toggle("lunchEnabled", "lunchEnabledShort", false),
|
|
1470
1672
|
toggle("lunchPaid", "lunchPaidShort", lunchDisabled)
|
|
1673
|
+
]),
|
|
1674
|
+
row("refresh", t("refreshRate"), [
|
|
1675
|
+
import_react.default.createElement("select", {
|
|
1676
|
+
key: "r",
|
|
1677
|
+
value: String(form.updateMs),
|
|
1678
|
+
onChange: (event) => setField("updateMs", event.target.value),
|
|
1679
|
+
style: { ...selectStyle, width: 128, padding: "4px 6px" }
|
|
1680
|
+
}, RATE_OPTIONS.map(([value, label]) => import_react.default.createElement("option", { key: value, value }, label)))
|
|
1471
1681
|
])
|
|
1472
1682
|
]),
|
|
1473
1683
|
import_react.default.createElement("div", {
|
|
@@ -1505,7 +1715,6 @@ function SettingsSection(props) {
|
|
|
1505
1715
|
function NiumaPanel(props) {
|
|
1506
1716
|
const t = props && typeof props.t === "function" ? props.t : fallbackTranslate;
|
|
1507
1717
|
const remote = useRemoteConfig();
|
|
1508
|
-
const now = useSecondClock();
|
|
1509
1718
|
const cfg = import_react.default.useMemo(() => {
|
|
1510
1719
|
const raw = remote.data && remote.data.config ? remote.data.config : null;
|
|
1511
1720
|
if (!raw) return DEFAULTS;
|
|
@@ -1516,6 +1725,8 @@ function NiumaPanel(props) {
|
|
|
1516
1725
|
return DEFAULTS;
|
|
1517
1726
|
}
|
|
1518
1727
|
}, [remote.data]);
|
|
1728
|
+
const updateMs = cfg.turbo === true ? 16 : Number.isInteger(cfg.updateMs) ? cfg.updateMs : 1e3;
|
|
1729
|
+
const now = useClock(updateMs);
|
|
1519
1730
|
const snap = import_react.default.useMemo(() => earnedToday(cfg, now), [cfg, now]);
|
|
1520
1731
|
const rates = snap && snap.rates ? snap.rates : computeRates(cfg, snap && snap.dateKey);
|
|
1521
1732
|
const baseForm = import_react.default.useMemo(() => formFromConfig(cfg), [cfg]);
|
|
@@ -1605,6 +1816,28 @@ function NiumaPanel(props) {
|
|
|
1605
1816
|
setSave({ status: "error", errors: [String(error && error.message || error)], message: "" });
|
|
1606
1817
|
});
|
|
1607
1818
|
};
|
|
1819
|
+
const [quickError, setQuickError] = import_react.default.useState("");
|
|
1820
|
+
const postConfig = import_react.default.useCallback((patch) => {
|
|
1821
|
+
if (typeof fetch !== "function") return;
|
|
1822
|
+
fetch(`${ROUTE_BASE}/settings`, {
|
|
1823
|
+
method: "POST",
|
|
1824
|
+
headers: { "content-type": "application/json" },
|
|
1825
|
+
credentials: "same-origin",
|
|
1826
|
+
body: JSON.stringify(patch)
|
|
1827
|
+
}).then(async (response) => {
|
|
1828
|
+
let body = null;
|
|
1829
|
+
try {
|
|
1830
|
+
body = await response.json();
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
body = null;
|
|
1833
|
+
}
|
|
1834
|
+
if (!response.ok || !body || body.ok !== true) throw new Error(body && body.error || `HTTP ${response.status}`);
|
|
1835
|
+
setQuickError("");
|
|
1836
|
+
remote.refresh();
|
|
1837
|
+
}).catch((error) => setQuickError(String(error && error.message || error)));
|
|
1838
|
+
}, [remote.refresh]);
|
|
1839
|
+
const onDecimalsChange = (value) => postConfig({ decimals: value });
|
|
1840
|
+
const onTurboChange = (checked) => postConfig({ turbo: Boolean(checked) });
|
|
1608
1841
|
const holidaySource = remote.data && remote.data.holidaySource || HOLIDAY_SOURCE;
|
|
1609
1842
|
const banner = remote.status === "failed" ? {
|
|
1610
1843
|
color: C.warn,
|
|
@@ -1707,7 +1940,7 @@ function NiumaPanel(props) {
|
|
|
1707
1940
|
}
|
|
1708
1941
|
}, t("retry")) : null
|
|
1709
1942
|
]) : null,
|
|
1710
|
-
import_react.default.createElement(HeroCard, { key: "hero", t, snap }),
|
|
1943
|
+
import_react.default.createElement(HeroCard, { key: "hero", t, snap, decimals: cfg.decimals, onDecimalsChange, turbo: cfg.turbo, onTurboChange }),
|
|
1711
1944
|
import_react.default.createElement(CountdownRow, { key: "countdown", t, snap }),
|
|
1712
1945
|
import_react.default.createElement("div", {
|
|
1713
1946
|
key: "ratesHead",
|