dsh-offwork 1.1.0 → 1.1.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 +21 -0
- package/README.md +50 -32
- package/assets/animations/NOTICE.md +15 -0
- package/assets/animations/alpha-probe.webm +0 -0
- package/assets/animations/ornate-drink.webm +0 -0
- package/assets/animations/ornate-greet.webm +0 -0
- package/assets/animations/ornate-idle.webm +0 -0
- package/assets/animations/whale-drink.webm +0 -0
- package/assets/animations/whale-greet.webm +0 -0
- package/assets/characters/NOTICE.md +4 -0
- package/assets/characters/whale-actions.webp +0 -0
- package/assets/characters/whale-ornate-actions.webp +0 -0
- package/client.js +2970 -770
- package/client.source.js +1668 -444
- package/companion.js +6 -9
- package/countdown.js +83 -0
- package/hydration-store.js +55 -16
- package/hydration.js +231 -250
- package/index.js +2 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/media-manifest.js +29 -0
- package/media-server.js +123 -0
- package/package.json +9 -2
- package/pet-interactions.js +78 -7
- package/pet-motion.js +111 -0
- package/pet-reminder.js +20 -0
- package/pet-surfaces.js +130 -6
- package/pet-video.js +380 -0
- package/pet-view.js +129 -13
- package/tests/classic-overview.test.mjs +77 -0
- package/tests/companion.test.mjs +12 -12
- package/tests/countdown.test.mjs +115 -0
- package/tests/host.test.mjs +6 -0
- package/tests/hydration-extras.test.mjs +168 -204
- package/tests/hydration-store.test.mjs +199 -213
- package/tests/hydration.test.mjs +116 -186
- package/tests/media-server.test.mjs +179 -0
- package/tests/pet-character.test.mjs +8 -2
- package/tests/pet-motion.test.mjs +139 -0
- package/tests/pet-reminder.test.mjs +71 -0
- package/tests/pet-video.test.mjs +503 -0
- package/tools/build.mjs +62 -13
- package/tools/check-package.mjs +19 -0
- package/ui-utils.js +51 -1
package/client.source.js
CHANGED
|
@@ -6,13 +6,49 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
const h = React.createElement;
|
|
7
7
|
/* PURE_MODULES */
|
|
8
8
|
/* PET_SPRITES */
|
|
9
|
+
/* PET_CLIPS */
|
|
9
10
|
const messages = {
|
|
10
11
|
zh: {
|
|
11
12
|
...COMPANION_MESSAGES.zh,
|
|
12
13
|
...PET_MESSAGES.zh,
|
|
13
14
|
...PET_INTERACTION_MESSAGES.zh,
|
|
14
15
|
...PET_SURFACE_MESSAGES.zh,
|
|
16
|
+
detailHelp: '使用说明',
|
|
17
|
+
detailApply: '应用',
|
|
18
|
+
detailShift: '班次详情',
|
|
19
|
+
detailWaterRule: '全天按自然时间提醒,需保持页面打开。',
|
|
20
|
+
detailSavedInterval: '已生效间隔',
|
|
21
|
+
detailDraftHint: '保存后生效',
|
|
22
|
+
detailNextInterval: '用于下一轮计时',
|
|
15
23
|
title: '下班计时器',
|
|
24
|
+
classicMiniWorking: '距下班',
|
|
25
|
+
classicMiniBefore: '距开工',
|
|
26
|
+
classicMiniLunch: '午休中',
|
|
27
|
+
classicMiniStatus: '本班状态',
|
|
28
|
+
classicMiniWaiting: '待开启',
|
|
29
|
+
classicMiniFinished: '已结束',
|
|
30
|
+
classicOverview: '今日概览',
|
|
31
|
+
classicReturn: '返回概览',
|
|
32
|
+
classicMore: '展开作息详情',
|
|
33
|
+
classicLess: '收起作息详情',
|
|
34
|
+
classicNoSession: '等待会话',
|
|
35
|
+
classicUnavailable: '暂时无法计时',
|
|
36
|
+
classicRetryHint: '重新读取作息后恢复计时。',
|
|
37
|
+
classicLoading: '正在准备计时',
|
|
38
|
+
classicLoadingHint: '正在读取已保存的作息。',
|
|
39
|
+
classicBeforeHint: '班次尚未开始,显示距离开工的时间。',
|
|
40
|
+
classicWorkingHint: '本班工作进行中。',
|
|
41
|
+
classicLunchRemaining: '距离午休结束',
|
|
42
|
+
classicLunchHint: '午休结束后继续工作;喝水提醒照常计时。',
|
|
43
|
+
classicDoneHint: '本班计时已结束,下一班开始后继续。',
|
|
44
|
+
classicRestHint: '今天不计工作时长,计时会在工作日恢复。',
|
|
45
|
+
classicWaterHint: '自然时间计时',
|
|
46
|
+
classicSmokeHint: '自然时间计时',
|
|
47
|
+
classicConfigure: '设置作息与薪资',
|
|
48
|
+
classicIncome: '本班收入',
|
|
49
|
+
classicIncomeEntry: '查看收入详情',
|
|
50
|
+
classicShift: '本班作息',
|
|
51
|
+
classicEnd: '下班时间',
|
|
16
52
|
working: '距离下班',
|
|
17
53
|
before: '距离开工',
|
|
18
54
|
lunch: '午休中',
|
|
@@ -73,7 +109,7 @@ window.__ModuleLoader__.load({
|
|
|
73
109
|
opacityHint: '只影响背景,不影响文字。',
|
|
74
110
|
waterTitle: '喝水提醒',
|
|
75
111
|
waterEnable: '开启喝水提醒',
|
|
76
|
-
waterInterval: '
|
|
112
|
+
waterInterval: '提醒间隔(分钟)',
|
|
77
113
|
waterSave: '保存提醒设置',
|
|
78
114
|
waterEdit: '设置喝水提醒',
|
|
79
115
|
waterBack: '返回',
|
|
@@ -83,21 +119,21 @@ window.__ModuleLoader__.load({
|
|
|
83
119
|
waterEffective: '当前间隔',
|
|
84
120
|
waterManage: '数据管理',
|
|
85
121
|
waterSaveHint: '修改后点击保存,按新间隔重新计时。',
|
|
86
|
-
waterBrief: '
|
|
122
|
+
waterBrief: '按自然时间计时,午休、下班和休息日均可提醒。需要保持页面打开。',
|
|
87
123
|
waterApply: '应用间隔',
|
|
88
124
|
waterInvalid: '请输入 5–240 的整数。',
|
|
89
125
|
waterNext: '下次喝水',
|
|
90
126
|
waterDue: '该喝水啦',
|
|
91
127
|
waterDrink: '喝了',
|
|
92
|
-
waterSnooze: '
|
|
93
|
-
waterPause: '
|
|
94
|
-
waterResume: '
|
|
95
|
-
waterCount: '
|
|
128
|
+
waterSnooze: '10 分钟后',
|
|
129
|
+
waterPause: '暂停提醒',
|
|
130
|
+
waterResume: '恢复提醒',
|
|
131
|
+
waterCount: '饮水计数',
|
|
96
132
|
waterManual: '喝了一杯',
|
|
97
133
|
waterQuick: '喝水快捷操作',
|
|
98
134
|
waterQuickClose: '收起喝水快捷操作',
|
|
99
135
|
waterRecorded: '这杯记下啦。',
|
|
100
|
-
waterRecordHint: '
|
|
136
|
+
waterRecordHint: '随时可以打卡,关闭提醒也能记录;计数可手动清零。',
|
|
101
137
|
waterActionStale: '状态已变化,请再试一次。',
|
|
102
138
|
waterQuiet: '免打扰',
|
|
103
139
|
waterQuietAdjust: '调整时长',
|
|
@@ -108,32 +144,68 @@ window.__ModuleLoader__.load({
|
|
|
108
144
|
waterQuietEnded: '免打扰已结束。',
|
|
109
145
|
waterQuietHint: '按自然时间,到期自动恢复;已到期的提醒只提示一次。',
|
|
110
146
|
waterOff: '提醒已关闭',
|
|
111
|
-
waterWaiting: '
|
|
112
|
-
waterBreak: '
|
|
113
|
-
waterPaused: '
|
|
114
|
-
waterFinished: '
|
|
147
|
+
waterWaiting: '等待开启提醒',
|
|
148
|
+
waterBreak: '提醒已暂停',
|
|
149
|
+
waterPaused: '提醒已暂停',
|
|
150
|
+
waterFinished: '提醒已结束',
|
|
115
151
|
waterOther: '提醒已到期,等待可见标签页处理',
|
|
116
|
-
waterClear: '
|
|
152
|
+
waterClear: '清除饮水计数',
|
|
117
153
|
waterReset: '重置喝水提醒',
|
|
118
154
|
waterConfirm: '确认操作',
|
|
119
|
-
waterClearHint: '
|
|
120
|
-
waterResetHint: '
|
|
155
|
+
waterClearHint: '仅清零饮水计数,保留下一次提醒和暂停状态。',
|
|
156
|
+
waterResetHint: '清除当前喝水提醒设置和计数,恢复默认关闭。',
|
|
121
157
|
waterLocal: '提醒设置和打卡仅保存在当前浏览器,不进入会话。与薪资配置分别保存。',
|
|
122
|
-
waterRules:
|
|
158
|
+
waterRules:
|
|
159
|
+
'按自然分钟计时,与作息、薪资和会话无关。开启、修改间隔或手动恢复后重新计时;关闭页面后不提醒。',
|
|
123
160
|
waterUnavailable:
|
|
124
|
-
'
|
|
161
|
+
'喝水提醒存储不可用或数据无效,自动提醒已停止。可重试;重置将清除当前提醒设置和计数。',
|
|
125
162
|
waterRetry: '重试读取',
|
|
126
|
-
waterSetup: '
|
|
163
|
+
waterSetup: '开启后按所选间隔提醒。',
|
|
127
164
|
waterLoading: '正在读取喝水提醒…',
|
|
128
|
-
waterChanged: '
|
|
129
|
-
waterStale: '
|
|
165
|
+
waterChanged: '提醒设置已保存',
|
|
166
|
+
waterStale: '记录已更新,请重试读取。',
|
|
130
167
|
},
|
|
131
168
|
en: {
|
|
132
169
|
...COMPANION_MESSAGES.en,
|
|
133
170
|
...PET_MESSAGES.en,
|
|
134
171
|
...PET_INTERACTION_MESSAGES.en,
|
|
135
172
|
...PET_SURFACE_MESSAGES.en,
|
|
173
|
+
detailHelp: 'How it works',
|
|
174
|
+
detailApply: 'Apply',
|
|
175
|
+
detailShift: 'Shift details',
|
|
176
|
+
detailWaterRule: 'Reminds you throughout the day. Keep the page open.',
|
|
177
|
+
detailSavedInterval: 'Saved interval',
|
|
178
|
+
detailDraftHint: 'Applies when saved',
|
|
179
|
+
detailNextInterval: 'Applies to the next timer',
|
|
136
180
|
title: 'Offwork Clock',
|
|
181
|
+
classicMiniWorking: 'To end',
|
|
182
|
+
classicMiniBefore: 'To start',
|
|
183
|
+
classicMiniLunch: 'Break',
|
|
184
|
+
classicMiniStatus: 'Status',
|
|
185
|
+
classicMiniWaiting: 'Waiting',
|
|
186
|
+
classicMiniFinished: 'Done',
|
|
187
|
+
classicOverview: 'Today at a glance',
|
|
188
|
+
classicReturn: 'Back to overview',
|
|
189
|
+
classicMore: 'Show schedule details',
|
|
190
|
+
classicLess: 'Hide schedule details',
|
|
191
|
+
classicNoSession: 'Waiting for a session',
|
|
192
|
+
classicUnavailable: 'Timer unavailable',
|
|
193
|
+
classicRetryHint: 'Reload your schedule to restore the timer.',
|
|
194
|
+
classicLoading: 'Preparing your timer',
|
|
195
|
+
classicLoadingHint: 'Loading your saved schedule.',
|
|
196
|
+
classicBeforeHint: 'Your shift has not started. This is the time until work begins.',
|
|
197
|
+
classicWorkingHint: 'Your shift is in progress.',
|
|
198
|
+
classicLunchRemaining: 'Until lunch ends',
|
|
199
|
+
classicLunchHint: 'Work resumes after lunch. Water reminders continue as usual.',
|
|
200
|
+
classicDoneHint: 'This shift has ended. Timing resumes with your next shift.',
|
|
201
|
+
classicRestHint: 'No work time is counted today. Timing resumes on a scheduled workday.',
|
|
202
|
+
classicWaterHint: 'Clock time',
|
|
203
|
+
classicSmokeHint: 'Clock time',
|
|
204
|
+
classicConfigure: 'Set schedule and pay',
|
|
205
|
+
classicIncome: 'Shift earnings',
|
|
206
|
+
classicIncomeEntry: 'View earnings',
|
|
207
|
+
classicShift: 'Shift schedule',
|
|
208
|
+
classicEnd: 'Shift ends',
|
|
137
209
|
working: 'Until clock-out',
|
|
138
210
|
before: 'Until work starts',
|
|
139
211
|
lunch: 'On break',
|
|
@@ -195,7 +267,7 @@ window.__ModuleLoader__.load({
|
|
|
195
267
|
opacityHint: 'Affects the background only.',
|
|
196
268
|
waterTitle: 'Water reminder',
|
|
197
269
|
waterEnable: 'Enable water reminders',
|
|
198
|
-
waterInterval: '
|
|
270
|
+
waterInterval: 'Interval (minutes)',
|
|
199
271
|
waterSave: 'Save reminder settings',
|
|
200
272
|
waterEdit: 'Reminder settings',
|
|
201
273
|
waterBack: 'Back',
|
|
@@ -205,21 +277,23 @@ window.__ModuleLoader__.load({
|
|
|
205
277
|
waterEffective: 'Current interval',
|
|
206
278
|
waterManage: 'Manage data',
|
|
207
279
|
waterSaveHint: 'Save changes to restart with the new interval.',
|
|
208
|
-
waterBrief:
|
|
280
|
+
waterBrief:
|
|
281
|
+
'Uses clock time, including breaks, off-hours and days off. Keep the page open.',
|
|
209
282
|
waterApply: 'Apply interval',
|
|
210
283
|
waterInvalid: 'Enter a whole number from 5 to 240.',
|
|
211
284
|
waterNext: 'Next water break',
|
|
212
285
|
waterDue: 'Time for some water',
|
|
213
286
|
waterDrink: 'Drank water',
|
|
214
|
-
waterSnooze: 'In 10
|
|
215
|
-
waterPause: 'Pause
|
|
216
|
-
waterResume: 'Resume
|
|
217
|
-
waterCount: 'Drinks
|
|
287
|
+
waterSnooze: 'In 10 minutes',
|
|
288
|
+
waterPause: 'Pause reminders',
|
|
289
|
+
waterResume: 'Resume reminders',
|
|
290
|
+
waterCount: 'Drinks recorded',
|
|
218
291
|
waterManual: 'Had a glass',
|
|
219
292
|
waterQuick: 'Quick water actions',
|
|
220
293
|
waterQuickClose: 'Close quick water actions',
|
|
221
294
|
waterRecorded: 'Got it, your drink is recorded.',
|
|
222
|
-
waterRecordHint:
|
|
295
|
+
waterRecordHint:
|
|
296
|
+
'Check in at any time, even with reminders off. Clear the count manually when needed.',
|
|
223
297
|
waterActionStale: 'The state changed. Please try again.',
|
|
224
298
|
waterQuiet: 'Quiet time',
|
|
225
299
|
waterQuietAdjust: 'Change duration',
|
|
@@ -231,27 +305,27 @@ window.__ModuleLoader__.load({
|
|
|
231
305
|
waterQuietHint:
|
|
232
306
|
'Uses clock time. Resumes automatically, with just one reminder if overdue.',
|
|
233
307
|
waterOff: 'Reminders off',
|
|
234
|
-
waterWaiting: 'Waiting for
|
|
235
|
-
waterBreak: '
|
|
236
|
-
waterPaused: '
|
|
237
|
-
waterFinished: '
|
|
308
|
+
waterWaiting: 'Waiting for reminders to be enabled',
|
|
309
|
+
waterBreak: 'Reminders paused',
|
|
310
|
+
waterPaused: 'Reminders paused',
|
|
311
|
+
waterFinished: 'Reminders ended',
|
|
238
312
|
waterOther: 'Reminder due; waiting for a visible tab',
|
|
239
|
-
waterClear: 'Clear
|
|
313
|
+
waterClear: 'Clear drink count',
|
|
240
314
|
waterReset: 'Reset water reminders',
|
|
241
315
|
waterConfirm: 'Confirm',
|
|
242
316
|
waterClearHint: 'Clear the drink count only. Keep the next reminder and pause state.',
|
|
243
|
-
waterResetHint: '
|
|
317
|
+
waterResetHint: 'Clear current water preferences and count, then turn reminders off.',
|
|
244
318
|
waterLocal:
|
|
245
319
|
'Reminder preferences and check-ins stay in this browser, outside conversations. Saved separately from salary settings.',
|
|
246
320
|
waterRules:
|
|
247
|
-
'
|
|
321
|
+
'Uses clock time, independently of work, salary and sessions. Enabling, changing the interval or manually resuming starts a new interval. Keep the page open.',
|
|
248
322
|
waterUnavailable:
|
|
249
|
-
'Water storage is unavailable or invalid; automatic reminders stopped. Retry, or reset
|
|
323
|
+
'Water storage is unavailable or invalid; automatic reminders stopped. Retry, or reset current water preferences and count.',
|
|
250
324
|
waterRetry: 'Retry loading',
|
|
251
|
-
waterSetup: '
|
|
325
|
+
waterSetup: 'Enable reminders to start the selected interval.',
|
|
252
326
|
waterLoading: 'Loading water reminders…',
|
|
253
|
-
waterChanged: 'Reminder
|
|
254
|
-
waterStale: '
|
|
327
|
+
waterChanged: 'Reminder settings saved',
|
|
328
|
+
waterStale: 'Records changed. Retry loading to continue.',
|
|
255
329
|
},
|
|
256
330
|
};
|
|
257
331
|
const STYLE =
|
|
@@ -383,6 +457,15 @@ window.__ModuleLoader__.load({
|
|
|
383
457
|
water: [
|
|
384
458
|
['path', { d: 'M8 1.5C6.6 4 3.5 7.1 3.5 10a4.5 4.5 0 0 0 9 0C12.5 7.1 9.4 4 8 1.5Z' }],
|
|
385
459
|
],
|
|
460
|
+
smoking: [
|
|
461
|
+
['rect', { x: 1.5, y: 10, width: 11, height: 3, rx: 0.5 }],
|
|
462
|
+
[
|
|
463
|
+
'path',
|
|
464
|
+
{
|
|
465
|
+
d: 'M9.5 10v3M14.5 10v3M10 7.5c1-1 .8-1.8 0-2.5s-1-1.5 0-2.5M13 6c1-1 .8-1.8 0-2.5s-1-1.5 0-2.5',
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
],
|
|
386
469
|
eye: [
|
|
387
470
|
['path', { d: 'M1.7 8S4.3 3.9 8 3.9 14.3 8 14.3 8 11.7 12.1 8 12.1 1.7 8 1.7 8Z' }],
|
|
388
471
|
['circle', { cx: 8, cy: 8, r: 2.05 }],
|
|
@@ -409,6 +492,7 @@ window.__ModuleLoader__.load({
|
|
|
409
492
|
['path', { d: 'M13.1 3v3.2H9.9' }],
|
|
410
493
|
],
|
|
411
494
|
};
|
|
495
|
+
const LOCAL_SCREENS = [null, 'appearance', 'smoking', 'water-countdown', 'water', 'home'];
|
|
412
496
|
const iconSvg = (name, size = 15) =>
|
|
413
497
|
h(
|
|
414
498
|
'svg',
|
|
@@ -432,8 +516,10 @@ window.__ModuleLoader__.load({
|
|
|
432
516
|
apply(ctx) {
|
|
433
517
|
for (const [lang, dict] of Object.entries(messages))
|
|
434
518
|
ctx.effect(() => ctx.locale.register('offwork-clock', lang, dict));
|
|
435
|
-
const viewKey = 'dsh.offwork-clock.view.
|
|
436
|
-
const previousViewKey = 'dsh.offwork-clock.view.
|
|
519
|
+
const viewKey = 'dsh.offwork-clock.view.v7';
|
|
520
|
+
const previousViewKey = 'dsh.offwork-clock.view.v6';
|
|
521
|
+
const olderViewKey = 'dsh.offwork-clock.view.v5';
|
|
522
|
+
const oldestViewKey = 'dsh.offwork-clock.view.v4';
|
|
437
523
|
const legacyKey = 'dsh.offwork-clock.view.v3';
|
|
438
524
|
|
|
439
525
|
function Clock(props) {
|
|
@@ -454,12 +540,20 @@ window.__ModuleLoader__.load({
|
|
|
454
540
|
const waterEditing = ui.screen === 'water';
|
|
455
541
|
const [appearanceDraft, setAppearanceDraft] = React.useState(null);
|
|
456
542
|
const appearanceBase = React.useRef(null);
|
|
543
|
+
const previewBox = React.useRef(null);
|
|
544
|
+
const previewMotion = React.useRef(null);
|
|
545
|
+
const [previewState, setPreviewState] = React.useState({ phase: 'idle', action: null });
|
|
546
|
+
const [previewReduced, setPreviewReduced] = React.useState(
|
|
547
|
+
() => window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false,
|
|
548
|
+
);
|
|
457
549
|
const appearanceWasPet = React.useRef(false);
|
|
458
550
|
const salaryBase = React.useRef(null);
|
|
459
551
|
const menuBox = React.useRef(null);
|
|
460
552
|
const menuTrigger = React.useRef(null);
|
|
461
553
|
const [dragPosition, setDragPosition] = React.useState(null);
|
|
462
|
-
const
|
|
554
|
+
const requestBusy = React.useRef(false);
|
|
555
|
+
const [hostBusy, setBusy] = React.useState(false);
|
|
556
|
+
const busy = hostBusy && (requestBusy.current || !LOCAL_SCREENS.includes(ui.screen));
|
|
463
557
|
const [error, setError] = React.useState('');
|
|
464
558
|
const [notice, setNotice] = React.useState('');
|
|
465
559
|
const [storageError, setStorageError] = React.useState(false);
|
|
@@ -485,8 +579,117 @@ window.__ModuleLoader__.load({
|
|
|
485
579
|
const dragState = React.useRef(null);
|
|
486
580
|
const lastPosition = React.useRef(null);
|
|
487
581
|
const generation = React.useRef(0);
|
|
488
|
-
const requestBusy = React.useRef(false);
|
|
489
582
|
const focusReturn = React.useRef(null);
|
|
583
|
+
const [smoking, setSmoking] = React.useState(defaultCountdown);
|
|
584
|
+
const [smokingReady, setSmokingReady] = React.useState(false);
|
|
585
|
+
const [smokingBusy, setSmokingBusy] = React.useState(false);
|
|
586
|
+
const [smokingError, setSmokingError] = React.useState('');
|
|
587
|
+
const smokingRuntime = React.useRef(null);
|
|
588
|
+
React.useEffect(() => {
|
|
589
|
+
let alive = true,
|
|
590
|
+
reading = false,
|
|
591
|
+
writing = false,
|
|
592
|
+
issued = 0,
|
|
593
|
+
accepted = 0,
|
|
594
|
+
channel;
|
|
595
|
+
const makeStore = () => {
|
|
596
|
+
try {
|
|
597
|
+
return createStateStore(
|
|
598
|
+
window.indexedDB,
|
|
599
|
+
SMOKING_TIMER_DB,
|
|
600
|
+
defaultCountdown,
|
|
601
|
+
validateCountdown,
|
|
602
|
+
);
|
|
603
|
+
} catch {
|
|
604
|
+
return createStateStore(
|
|
605
|
+
null,
|
|
606
|
+
SMOKING_TIMER_DB,
|
|
607
|
+
defaultCountdown,
|
|
608
|
+
validateCountdown,
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
let store = makeStore();
|
|
613
|
+
const accept = (next, order) => {
|
|
614
|
+
if (!alive || order < accepted) return;
|
|
615
|
+
accepted = order;
|
|
616
|
+
setSmoking(next);
|
|
617
|
+
setNow(Date.now());
|
|
618
|
+
setSmokingReady(true);
|
|
619
|
+
};
|
|
620
|
+
const refresh = async () => {
|
|
621
|
+
if (!alive || reading || writing) return;
|
|
622
|
+
reading = true;
|
|
623
|
+
const source = store,
|
|
624
|
+
order = ++issued;
|
|
625
|
+
try {
|
|
626
|
+
const next = await source.update((current) => current);
|
|
627
|
+
if (source === store) accept(next, order);
|
|
628
|
+
} catch {
|
|
629
|
+
if (alive && source === store && order >= accepted)
|
|
630
|
+
setSmokingError('timerStorageError');
|
|
631
|
+
} finally {
|
|
632
|
+
reading = false;
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
const run = async (action) => {
|
|
636
|
+
if (!alive || writing) return;
|
|
637
|
+
writing = true;
|
|
638
|
+
const order = ++issued;
|
|
639
|
+
setSmokingBusy(true);
|
|
640
|
+
try {
|
|
641
|
+
if (action.type === 'retry') {
|
|
642
|
+
store.close();
|
|
643
|
+
store = makeStore();
|
|
644
|
+
}
|
|
645
|
+
let changedElsewhere = false;
|
|
646
|
+
const next = await store.update((current) => {
|
|
647
|
+
if (action.type === 'retry') return current;
|
|
648
|
+
if (current.revision !== action.revision) {
|
|
649
|
+
changedElsewhere = true;
|
|
650
|
+
return current;
|
|
651
|
+
}
|
|
652
|
+
return transitionCountdown(current, action, Date.now());
|
|
653
|
+
});
|
|
654
|
+
accept(next, order);
|
|
655
|
+
if (alive) setSmokingError(changedElsewhere ? 'timerChanged' : '');
|
|
656
|
+
try {
|
|
657
|
+
channel?.postMessage(next.revision);
|
|
658
|
+
} catch {
|
|
659
|
+
/* Polling also synchronizes. */
|
|
660
|
+
}
|
|
661
|
+
} catch {
|
|
662
|
+
if (alive) setSmokingError('timerStorageError');
|
|
663
|
+
} finally {
|
|
664
|
+
writing = false;
|
|
665
|
+
if (alive) setSmokingBusy(false);
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
smokingRuntime.current = { run };
|
|
669
|
+
try {
|
|
670
|
+
channel = new BroadcastChannel(SMOKING_TIMER_DB);
|
|
671
|
+
channel.onmessage = refresh;
|
|
672
|
+
} catch {
|
|
673
|
+
/* Polling is sufficient. */
|
|
674
|
+
}
|
|
675
|
+
void refresh();
|
|
676
|
+
const timer = window.setInterval(refresh, 2000);
|
|
677
|
+
window.addEventListener('focus', refresh);
|
|
678
|
+
document.addEventListener('visibilitychange', refresh);
|
|
679
|
+
return () => {
|
|
680
|
+
alive = false;
|
|
681
|
+
smokingRuntime.current = null;
|
|
682
|
+
window.clearInterval(timer);
|
|
683
|
+
window.removeEventListener('focus', refresh);
|
|
684
|
+
document.removeEventListener('visibilitychange', refresh);
|
|
685
|
+
channel?.close();
|
|
686
|
+
store.close();
|
|
687
|
+
};
|
|
688
|
+
}, []);
|
|
689
|
+
const smokingState = countdownView(smoking, now);
|
|
690
|
+
const smokingUnavailable = !smokingReady || smokingError === 'timerStorageError';
|
|
691
|
+
const smokingAction = (action) =>
|
|
692
|
+
smokingRuntime.current?.run({ ...action, revision: smoking.revision });
|
|
490
693
|
const [water, setWater] = React.useState(defaultHydration);
|
|
491
694
|
const [waterReady, setWaterReady] = React.useState(false);
|
|
492
695
|
const [waterError, setWaterError] = React.useState(false);
|
|
@@ -504,29 +707,33 @@ window.__ModuleLoader__.load({
|
|
|
504
707
|
const [waterInvalid, setWaterInvalid] = React.useState(false);
|
|
505
708
|
const [waterConfirm, setWaterConfirm] = React.useState(null);
|
|
506
709
|
const [waterNotice, setWaterNotice] = React.useState(false);
|
|
710
|
+
const [reminderGate, setReminderGate] = React.useState(initialPetReminder);
|
|
507
711
|
const reactionState = React.useRef(null);
|
|
712
|
+
const petMotion = React.useRef(null);
|
|
713
|
+
const petReactionToken = React.useRef(null);
|
|
508
714
|
const waterRuntime = React.useRef(null);
|
|
509
715
|
const waterOwner = React.useRef(null);
|
|
510
716
|
if (!waterOwner.current)
|
|
511
717
|
waterOwner.current =
|
|
512
718
|
window.crypto?.randomUUID?.() || String(Math.random()) + Date.now();
|
|
513
719
|
const waterInputs = React.useRef(null);
|
|
514
|
-
|
|
515
|
-
waterInputs.current = {
|
|
516
|
-
settings: data?.configured && !error ? data.settings : null,
|
|
517
|
-
editing: editing || waterEditing,
|
|
518
|
-
};
|
|
720
|
+
waterInputs.current = { editing: editing || waterEditing };
|
|
519
721
|
|
|
520
722
|
const settings = data?.settings;
|
|
521
723
|
const workingDraft = draft || settings;
|
|
522
724
|
const state = settings ? safeCalculate(settings) : null;
|
|
523
725
|
const preview = workingDraft ? safeCalculate(workingDraft) : null;
|
|
524
726
|
const safeState = state && !state.error ? state : null;
|
|
525
|
-
const petMode =
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
727
|
+
const petMode = ready && (appearanceOnly ? appearanceWasPet.current : view.companion);
|
|
728
|
+
const petClockStatus = !sessionId
|
|
729
|
+
? 'petNeedsSession'
|
|
730
|
+
: error || state?.error
|
|
731
|
+
? 'petLoadFailed'
|
|
732
|
+
: !data
|
|
733
|
+
? 'loading'
|
|
734
|
+
: !data.configured
|
|
735
|
+
? 'petNeedsSetup'
|
|
736
|
+
: safeState?.status || 'loading';
|
|
530
737
|
const stageModel = appearanceOnly ? savedView.petModel : view.petModel;
|
|
531
738
|
const petGeometry = petLayout(
|
|
532
739
|
dragPosition ?? view.petPosition,
|
|
@@ -534,6 +741,8 @@ window.__ModuleLoader__.load({
|
|
|
534
741
|
appearanceOnly ? savedView.petSize : view.petSize,
|
|
535
742
|
420,
|
|
536
743
|
stageModel,
|
|
744
|
+
72,
|
|
745
|
+
196,
|
|
537
746
|
);
|
|
538
747
|
const surfaceScreen = ui.screen || 'reminder';
|
|
539
748
|
const surfaceWidth = petSurfaceWidth(surfaceScreen, viewport);
|
|
@@ -577,7 +786,7 @@ window.__ModuleLoader__.load({
|
|
|
577
786
|
);
|
|
578
787
|
const waterAlertLayout = placePetSurface(anchor, viewport, {
|
|
579
788
|
width: Math.min(140, Math.max(110, alertRoom)),
|
|
580
|
-
height:
|
|
789
|
+
height: 44,
|
|
581
790
|
kind: 'menu',
|
|
582
791
|
obstacles: [...(petOpen ? [floatingPanel] : []), ...(ui.menu ? [menuLayout] : [])],
|
|
583
792
|
});
|
|
@@ -586,14 +795,15 @@ window.__ModuleLoader__.load({
|
|
|
586
795
|
height: toastHeight,
|
|
587
796
|
kind: 'menu',
|
|
588
797
|
});
|
|
589
|
-
const collapsed =
|
|
590
|
-
|
|
591
|
-
!!
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
798
|
+
const collapsed = !petMode && ui.screen === null && view.mini;
|
|
799
|
+
const classicClock = classicClockPresentation({
|
|
800
|
+
hasSession: !!sessionId,
|
|
801
|
+
loaded: !!data,
|
|
802
|
+
configured: !!data?.configured,
|
|
803
|
+
failed: !!error || !!state?.error,
|
|
804
|
+
state: safeState,
|
|
805
|
+
now,
|
|
806
|
+
});
|
|
597
807
|
const seconds = safeState
|
|
598
808
|
? safeState.status === 'before'
|
|
599
809
|
? safeState.startRemaining
|
|
@@ -621,6 +831,9 @@ window.__ModuleLoader__.load({
|
|
|
621
831
|
classicSize,
|
|
622
832
|
);
|
|
623
833
|
const cardStyle = {
|
|
834
|
+
width: ui.screen
|
|
835
|
+
? petSurfaceWidth(ui.screen, viewport)
|
|
836
|
+
: Math.min(collapsed ? 224 : 320, Math.max(1, viewport.width - 24)),
|
|
624
837
|
'--owc-alpha': String(view.opacity),
|
|
625
838
|
'--owc-viewport-height': Math.max(1, viewport.height - 24) + 'px',
|
|
626
839
|
left: classicPosition.x,
|
|
@@ -655,7 +868,11 @@ window.__ModuleLoader__.load({
|
|
|
655
868
|
|
|
656
869
|
React.useEffect(() => {
|
|
657
870
|
try {
|
|
658
|
-
const saved =
|
|
871
|
+
const saved =
|
|
872
|
+
localStorage.getItem(viewKey) ??
|
|
873
|
+
localStorage.getItem(previousViewKey) ??
|
|
874
|
+
localStorage.getItem(olderViewKey) ??
|
|
875
|
+
localStorage.getItem(oldestViewKey);
|
|
659
876
|
const legacy = !saved ? JSON.parse(localStorage.getItem(legacyKey) || 'null') : null;
|
|
660
877
|
setView(
|
|
661
878
|
normalizeView(
|
|
@@ -699,17 +916,8 @@ window.__ModuleLoader__.load({
|
|
|
699
916
|
store = createHydrationStore(null);
|
|
700
917
|
}
|
|
701
918
|
try {
|
|
702
|
-
channel = new BroadcastChannel('dsh.offwork-clock.hydration.
|
|
919
|
+
channel = new BroadcastChannel('dsh.offwork-clock.hydration.wall-clock.v3');
|
|
703
920
|
} catch {}
|
|
704
|
-
const contextAt = (time) => {
|
|
705
|
-
const current = waterInputs.current.settings;
|
|
706
|
-
if (!current) return null;
|
|
707
|
-
try {
|
|
708
|
-
return hydrationContext(current, calculate(current, time));
|
|
709
|
-
} catch {
|
|
710
|
-
return null;
|
|
711
|
-
}
|
|
712
|
-
};
|
|
713
921
|
const update = async (action = { type: 'tick' }, automatic = false) => {
|
|
714
922
|
if (!active || (automatic && (running || failed))) return;
|
|
715
923
|
running++;
|
|
@@ -725,59 +933,31 @@ window.__ModuleLoader__.load({
|
|
|
725
933
|
let changed = false;
|
|
726
934
|
let recorded = false;
|
|
727
935
|
let quietChanged = false;
|
|
728
|
-
let usedPermit = null;
|
|
729
936
|
const result = await store.update(
|
|
730
937
|
(current) => {
|
|
731
938
|
const time = Date.now();
|
|
732
|
-
const context = contextAt(time);
|
|
733
939
|
if (action.type === 'sync') return current;
|
|
734
|
-
|
|
735
|
-
const operation =
|
|
736
|
-
permit && context && permit.schedule === context.schedule
|
|
737
|
-
? { ...action, baseSchedule: permit.baseSchedule }
|
|
738
|
-
: action;
|
|
739
|
-
let next = transitionHydration(
|
|
740
|
-
current,
|
|
741
|
-
context,
|
|
742
|
-
operation,
|
|
743
|
-
time,
|
|
744
|
-
waterOwner.current,
|
|
745
|
-
);
|
|
940
|
+
let next = transitionHydration(current, action, time, waterOwner.current);
|
|
746
941
|
recorded =
|
|
747
|
-
['drink', 'manual-drink'].includes(action.type) &&
|
|
748
|
-
next.shift?.count >
|
|
749
|
-
(current.shift?.id === next.shift?.id ? (current.shift?.count ?? 0) : 0);
|
|
942
|
+
['drink', 'manual-drink'].includes(action.type) && next.count > current.count;
|
|
750
943
|
quietChanged =
|
|
751
944
|
['quiet', 'end-quiet'].includes(action.type) &&
|
|
752
|
-
next.
|
|
945
|
+
next.quietUntil !== current.quietUntil;
|
|
753
946
|
if (automatic) {
|
|
754
947
|
const allow = !document.hidden && !waterInputs.current.editing;
|
|
755
948
|
next = transitionHydration(
|
|
756
949
|
next,
|
|
757
|
-
context,
|
|
758
950
|
{ type: allow ? 'claim' : 'release' },
|
|
759
951
|
time,
|
|
760
952
|
waterOwner.current,
|
|
761
953
|
);
|
|
762
954
|
}
|
|
763
|
-
if (
|
|
764
|
-
permit &&
|
|
765
|
-
context &&
|
|
766
|
-
permit.schedule === context.schedule &&
|
|
767
|
-
next.shift?.schedule === context.schedule
|
|
768
|
-
)
|
|
769
|
-
usedPermit = permit;
|
|
770
955
|
changed = JSON.stringify(next) !== JSON.stringify(current);
|
|
771
956
|
return next;
|
|
772
957
|
},
|
|
773
958
|
{ reset: action.type === 'reset' },
|
|
774
959
|
);
|
|
775
960
|
if (!active) return;
|
|
776
|
-
if (
|
|
777
|
-
action.type === 'reset' ||
|
|
778
|
-
(usedPermit && waterScheduleChange.current === usedPermit)
|
|
779
|
-
)
|
|
780
|
-
waterScheduleChange.current = null;
|
|
781
961
|
failed = false;
|
|
782
962
|
setWater(result);
|
|
783
963
|
setWaterReady(true);
|
|
@@ -809,11 +989,14 @@ window.__ModuleLoader__.load({
|
|
|
809
989
|
const visibility = () => {
|
|
810
990
|
setWaterVisible(!document.hidden);
|
|
811
991
|
setNow(Date.now());
|
|
812
|
-
if (document.hidden)
|
|
813
|
-
|
|
992
|
+
if (document.hidden) {
|
|
993
|
+
setReminderGate(initialPetReminder);
|
|
994
|
+
void update({ type: 'release' });
|
|
995
|
+
} else tick();
|
|
814
996
|
};
|
|
815
997
|
const pageHide = () => {
|
|
816
998
|
setWaterVisible(false);
|
|
999
|
+
setReminderGate(initialPetReminder);
|
|
817
1000
|
void update({ type: 'release' });
|
|
818
1001
|
};
|
|
819
1002
|
waterRuntime.current = { update, tick };
|
|
@@ -838,8 +1021,7 @@ window.__ModuleLoader__.load({
|
|
|
838
1021
|
channel?.close();
|
|
839
1022
|
void store
|
|
840
1023
|
.update((current) => {
|
|
841
|
-
if (current.
|
|
842
|
-
current.shift.claim = null;
|
|
1024
|
+
if (current.claim?.owner === waterOwner.current) current.claim = null;
|
|
843
1025
|
return current;
|
|
844
1026
|
})
|
|
845
1027
|
.catch(() => {})
|
|
@@ -849,7 +1031,7 @@ window.__ModuleLoader__.load({
|
|
|
849
1031
|
|
|
850
1032
|
React.useEffect(() => {
|
|
851
1033
|
waterRuntime.current?.tick();
|
|
852
|
-
}, [
|
|
1034
|
+
}, [editing, waterEditing]);
|
|
853
1035
|
|
|
854
1036
|
React.useEffect(() => {
|
|
855
1037
|
if (!waterNotice) return;
|
|
@@ -895,6 +1077,7 @@ window.__ModuleLoader__.load({
|
|
|
895
1077
|
const interval = Number(waterInterval);
|
|
896
1078
|
if (!Number.isInteger(interval) || interval < 5 || interval > 240) {
|
|
897
1079
|
setWaterInvalid(true);
|
|
1080
|
+
box.current?.querySelector('[name=waterInterval]')?.focus();
|
|
898
1081
|
return;
|
|
899
1082
|
}
|
|
900
1083
|
const result = await waterAction({
|
|
@@ -927,7 +1110,7 @@ window.__ModuleLoader__.load({
|
|
|
927
1110
|
setWaterNotice({ key: 'waterRecorded', kind: 'drink', token: result.revision });
|
|
928
1111
|
if (action.type === 'manual-drink') {
|
|
929
1112
|
setWaterNotice({
|
|
930
|
-
key: result.
|
|
1113
|
+
key: result.recentActions.includes(action.actionId)
|
|
931
1114
|
? 'waterRecorded'
|
|
932
1115
|
: 'waterActionStale',
|
|
933
1116
|
kind: outcome.recorded ? 'drink' : 'notice',
|
|
@@ -938,7 +1121,7 @@ window.__ModuleLoader__.load({
|
|
|
938
1121
|
if (action.type === 'quiet')
|
|
939
1122
|
setWaterNotice({
|
|
940
1123
|
key:
|
|
941
|
-
outcome.quietChanged && result.
|
|
1124
|
+
outcome.quietChanged && result.quietUntil > Date.now()
|
|
942
1125
|
? 'waterQuietStarted'
|
|
943
1126
|
: 'waterActionStale',
|
|
944
1127
|
kind: 'notice',
|
|
@@ -966,13 +1149,9 @@ window.__ModuleLoader__.load({
|
|
|
966
1149
|
? '.owc-pet-handle'
|
|
967
1150
|
: '.owc-grip,.owc-title'
|
|
968
1151
|
: action.type === 'manual-drink'
|
|
969
|
-
?
|
|
970
|
-
? '.owc-pet-water-row .owc-primary'
|
|
971
|
-
: '.owc-water-actions .owc-primary'
|
|
1152
|
+
? '.owc-water-record,.owc-water-actions .owc-primary'
|
|
972
1153
|
: action.type === 'end-quiet'
|
|
973
|
-
?
|
|
974
|
-
? '.owc-pet-quiet-row button'
|
|
975
|
-
: '.owc-water-quiet summary'
|
|
1154
|
+
? '.owc-detail-more summary,.owc-water-quiet summary'
|
|
976
1155
|
: '.owc-water-form details > summary';
|
|
977
1156
|
box.current?.querySelector(target)?.focus();
|
|
978
1157
|
});
|
|
@@ -1041,7 +1220,7 @@ window.__ModuleLoader__.load({
|
|
|
1041
1220
|
window.visualViewport?.removeEventListener('resize', resize);
|
|
1042
1221
|
window.visualViewport?.removeEventListener('scroll', resize);
|
|
1043
1222
|
};
|
|
1044
|
-
}, [clamp, petMode]);
|
|
1223
|
+
}, [clamp, petMode, ready]);
|
|
1045
1224
|
|
|
1046
1225
|
React.useEffect(() => {
|
|
1047
1226
|
if (ui.pending) {
|
|
@@ -1093,21 +1272,11 @@ window.__ModuleLoader__.load({
|
|
|
1093
1272
|
setBusy(true);
|
|
1094
1273
|
setError('');
|
|
1095
1274
|
try {
|
|
1096
|
-
const baseSchedule = (await waterRuntime.current?.update({ type: 'sync' }))?.state
|
|
1097
|
-
?.shift?.schedule;
|
|
1098
|
-
if (token !== generation.current) return;
|
|
1099
1275
|
const next = await request('get');
|
|
1100
1276
|
if (token !== generation.current) return;
|
|
1101
|
-
if (next.configured)
|
|
1102
|
-
waterScheduleChange.current = {
|
|
1103
|
-
baseSchedule,
|
|
1104
|
-
schedule: hydrationContext(next.settings, calculate(next.settings, Date.now()))
|
|
1105
|
-
.schedule,
|
|
1106
|
-
};
|
|
1107
1277
|
setData(next);
|
|
1108
1278
|
if (!next.configured) {
|
|
1109
1279
|
setDraft({ ...next.settings });
|
|
1110
|
-
dispatchUI({ type: 'navigate', screen: 'schedule', discard: true });
|
|
1111
1280
|
salaryBase.current = next.settings;
|
|
1112
1281
|
}
|
|
1113
1282
|
if (explicit) setNotice(t('synced'));
|
|
@@ -1126,19 +1295,11 @@ window.__ModuleLoader__.load({
|
|
|
1126
1295
|
setError('');
|
|
1127
1296
|
const token = ++generation.current;
|
|
1128
1297
|
try {
|
|
1129
|
-
const baseSchedule = (await waterRuntime.current?.update({ type: 'sync' }))?.state
|
|
1130
|
-
?.shift?.schedule;
|
|
1131
|
-
if (token !== generation.current) return;
|
|
1132
1298
|
const next = await request(
|
|
1133
1299
|
'set',
|
|
1134
1300
|
changedSettings(salaryBase.current ?? settings, draft),
|
|
1135
1301
|
);
|
|
1136
1302
|
if (token === generation.current) {
|
|
1137
|
-
waterScheduleChange.current = {
|
|
1138
|
-
baseSchedule,
|
|
1139
|
-
schedule: hydrationContext(next.settings, calculate(next.settings, Date.now()))
|
|
1140
|
-
.schedule,
|
|
1141
|
-
};
|
|
1142
1303
|
setData(next);
|
|
1143
1304
|
dispatchUI({ type: 'navigate', screen: ui.back, discard: true });
|
|
1144
1305
|
setDraft(null);
|
|
@@ -1191,7 +1352,10 @@ window.__ModuleLoader__.load({
|
|
|
1191
1352
|
d.moved = true;
|
|
1192
1353
|
petDragged.current = true;
|
|
1193
1354
|
const next = clamp({ x: e.clientX - d.dx, y: e.clientY - d.dy });
|
|
1194
|
-
if (petMode)
|
|
1355
|
+
if (petMode) {
|
|
1356
|
+
setDragPosition(next);
|
|
1357
|
+
petMotion.current?.drag(e.clientX - d.x0);
|
|
1358
|
+
}
|
|
1195
1359
|
lastPosition.current = next;
|
|
1196
1360
|
if (box.current) {
|
|
1197
1361
|
box.current.style.left = next.x + 'px';
|
|
@@ -1203,6 +1367,7 @@ window.__ModuleLoader__.load({
|
|
|
1203
1367
|
const d = dragState.current;
|
|
1204
1368
|
if (!d) return;
|
|
1205
1369
|
dragState.current = null;
|
|
1370
|
+
if (petMode && d.moved) petMotion.current?.release(e.type !== 'pointerup');
|
|
1206
1371
|
if (e.type === 'pointercancel' || e.type === 'lostpointercapture')
|
|
1207
1372
|
petDragged.current = false;
|
|
1208
1373
|
if (d.moved && lastPosition.current) commitPosition(lastPosition.current);
|
|
@@ -1240,7 +1405,7 @@ window.__ModuleLoader__.load({
|
|
|
1240
1405
|
petDragged.current = false;
|
|
1241
1406
|
if (ui.pending) return true;
|
|
1242
1407
|
if (!petMode) {
|
|
1243
|
-
if (!
|
|
1408
|
+
if (!requestBusy.current && !waterBusy) navigateScreen('appearance', event);
|
|
1244
1409
|
return true;
|
|
1245
1410
|
}
|
|
1246
1411
|
const trigger =
|
|
@@ -1256,7 +1421,7 @@ window.__ModuleLoader__.load({
|
|
|
1256
1421
|
: trigger;
|
|
1257
1422
|
const rect = trigger?.getBoundingClientRect();
|
|
1258
1423
|
const point =
|
|
1259
|
-
event.type === 'keydown'
|
|
1424
|
+
event.type === 'keydown' || (event.type === 'click' && event.detail === 0)
|
|
1260
1425
|
? { x: rect?.right ?? petGeometry.x, y: rect?.top ?? petGeometry.y }
|
|
1261
1426
|
: { x: event.clientX, y: event.clientY };
|
|
1262
1427
|
dispatchUI({ type: 'menu', position: point });
|
|
@@ -1297,8 +1462,19 @@ window.__ModuleLoader__.load({
|
|
|
1297
1462
|
setWaterConfirm(null);
|
|
1298
1463
|
}
|
|
1299
1464
|
function navigateScreen(screen, event) {
|
|
1300
|
-
if (
|
|
1301
|
-
|
|
1465
|
+
if (
|
|
1466
|
+
screen !== null &&
|
|
1467
|
+
!LOCAL_SCREENS.includes(screen) &&
|
|
1468
|
+
(!data || (!data.configured && screen !== 'schedule'))
|
|
1469
|
+
)
|
|
1470
|
+
return;
|
|
1471
|
+
if (
|
|
1472
|
+
ui.pending ||
|
|
1473
|
+
waterBusy ||
|
|
1474
|
+
requestBusy.current ||
|
|
1475
|
+
waterPending.current ||
|
|
1476
|
+
(hostBusy && !LOCAL_SCREENS.includes(screen))
|
|
1477
|
+
)
|
|
1302
1478
|
return;
|
|
1303
1479
|
const root = event?.currentTarget?.getAttribute?.('role') === 'menuitem';
|
|
1304
1480
|
if (event?.currentTarget) focusReturn.current = event.currentTarget;
|
|
@@ -1350,7 +1526,7 @@ window.__ModuleLoader__.load({
|
|
|
1350
1526
|
navigateScreen('schedule', event);
|
|
1351
1527
|
}
|
|
1352
1528
|
function closeEditor() {
|
|
1353
|
-
if (
|
|
1529
|
+
if (busy) return;
|
|
1354
1530
|
performNavigation(ui.back);
|
|
1355
1531
|
}
|
|
1356
1532
|
function update(key, value) {
|
|
@@ -1419,19 +1595,17 @@ window.__ModuleLoader__.load({
|
|
|
1419
1595
|
'button',
|
|
1420
1596
|
{
|
|
1421
1597
|
type: 'button',
|
|
1422
|
-
className: 'owc-
|
|
1598
|
+
className: 'owc-icon',
|
|
1423
1599
|
'aria-pressed': view.masked,
|
|
1600
|
+
'aria-label': t(view.masked ? 'show' : 'hide'),
|
|
1424
1601
|
title: t(view.masked ? 'show' : 'hide'),
|
|
1425
1602
|
onClick: () => setPreference({ masked: !view.masked }),
|
|
1426
1603
|
},
|
|
1427
1604
|
iconSvg(view.masked ? 'eyeOff' : 'eye', 13),
|
|
1428
|
-
t(view.masked ? 'show' : 'hide'),
|
|
1429
1605
|
);
|
|
1430
1606
|
|
|
1431
|
-
const
|
|
1432
|
-
|
|
1433
|
-
const waterState = hydrationView(water, waterContext, now);
|
|
1434
|
-
const waterClaim = water.shift?.claim;
|
|
1607
|
+
const waterState = hydrationView(water, now);
|
|
1608
|
+
const waterClaim = water.claim;
|
|
1435
1609
|
const waterPrompt =
|
|
1436
1610
|
waterReady &&
|
|
1437
1611
|
!waterError &&
|
|
@@ -1441,9 +1615,59 @@ window.__ModuleLoader__.load({
|
|
|
1441
1615
|
!document.hidden &&
|
|
1442
1616
|
waterState.phase === 'due' &&
|
|
1443
1617
|
waterClaim?.owner === waterOwner.current &&
|
|
1444
|
-
waterClaim.sequence === water.
|
|
1618
|
+
waterClaim.sequence === water.sequence &&
|
|
1445
1619
|
waterClaim.until > Date.now() &&
|
|
1446
1620
|
waterClaim.until <= Date.now() + HYDRATION_LEASE_MS;
|
|
1621
|
+
const reminderReady = !!(petMode && waterReady && !waterError);
|
|
1622
|
+
const reminderVisible = waterVisible && !document.hidden;
|
|
1623
|
+
const reminderAvailable = !petOpen && !ui.menu && !ui.pending && !busy;
|
|
1624
|
+
React.useEffect(() => {
|
|
1625
|
+
setReminderGate((current) =>
|
|
1626
|
+
nextPetReminder(current, {
|
|
1627
|
+
ready: reminderReady,
|
|
1628
|
+
eventId: waterState.eventId,
|
|
1629
|
+
phase: waterState.phase,
|
|
1630
|
+
visible: reminderVisible,
|
|
1631
|
+
available: reminderAvailable,
|
|
1632
|
+
ownsPrompt: waterPrompt,
|
|
1633
|
+
}),
|
|
1634
|
+
);
|
|
1635
|
+
}, [
|
|
1636
|
+
reminderReady,
|
|
1637
|
+
waterState.eventId,
|
|
1638
|
+
waterState.phase,
|
|
1639
|
+
reminderVisible,
|
|
1640
|
+
reminderAvailable,
|
|
1641
|
+
waterPrompt,
|
|
1642
|
+
]);
|
|
1643
|
+
const autoWaterPrompt =
|
|
1644
|
+
reminderReady &&
|
|
1645
|
+
reminderVisible &&
|
|
1646
|
+
reminderAvailable &&
|
|
1647
|
+
waterPrompt &&
|
|
1648
|
+
reminderGate.open &&
|
|
1649
|
+
reminderGate.eventId === waterState.eventId;
|
|
1650
|
+
function openWaterPopup(event) {
|
|
1651
|
+
if (!waterPrompt || waterBusy) return;
|
|
1652
|
+
if (!reminderAvailable) {
|
|
1653
|
+
navigateScreen('home', event);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
const trigger = event.currentTarget;
|
|
1657
|
+
setReminderGate({ eventId: waterState.eventId, armed: false, open: true });
|
|
1658
|
+
requestAnimationFrame(() => {
|
|
1659
|
+
const active = document.activeElement;
|
|
1660
|
+
if (active !== trigger && active?.tagName !== 'BODY') return;
|
|
1661
|
+
if (petPanel.current?.dataset.petScreen === 'reminder')
|
|
1662
|
+
petPanel.current.querySelector('.owc-water-actions button')?.focus();
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
function dismissWaterPopup() {
|
|
1666
|
+
const hadFocus = petPanel.current?.contains(document.activeElement);
|
|
1667
|
+
setReminderGate(dismissPetReminder);
|
|
1668
|
+
if (hadFocus)
|
|
1669
|
+
requestAnimationFrame(() => box.current?.querySelector('.owc-pet-handle')?.focus());
|
|
1670
|
+
}
|
|
1447
1671
|
const reactionSurface = [
|
|
1448
1672
|
collapsed,
|
|
1449
1673
|
editing,
|
|
@@ -1465,13 +1689,116 @@ window.__ModuleLoader__.load({
|
|
|
1465
1689
|
workStatus: safeState?.status,
|
|
1466
1690
|
phase: waterState.phase,
|
|
1467
1691
|
ownsPrompt: waterPrompt,
|
|
1468
|
-
ready: waterReady
|
|
1469
|
-
failed: waterError
|
|
1692
|
+
ready: waterReady,
|
|
1693
|
+
failed: waterError,
|
|
1470
1694
|
feedback: waterNotice || null,
|
|
1471
1695
|
visible: view.companion,
|
|
1472
1696
|
motion: view.companionMotion && !reactionState.current?.stopped,
|
|
1473
1697
|
pageVisible: waterVisible && !document.hidden && !editing && !waterEditing,
|
|
1474
1698
|
});
|
|
1699
|
+
React.useLayoutEffect(() => {
|
|
1700
|
+
if (!petMode) return;
|
|
1701
|
+
const artwork = box.current?.querySelector('.owc-pet-handle > .owc-pet-art');
|
|
1702
|
+
if (!artwork) return;
|
|
1703
|
+
const base = createPetMotion(artwork, window, { enabled: false });
|
|
1704
|
+
const controller = createPetVideoMotion(
|
|
1705
|
+
artwork,
|
|
1706
|
+
window,
|
|
1707
|
+
base,
|
|
1708
|
+
PET_CLIPS.models[stageModel],
|
|
1709
|
+
PET_CLIPS.probe,
|
|
1710
|
+
);
|
|
1711
|
+
petMotion.current = controller;
|
|
1712
|
+
return () => {
|
|
1713
|
+
controller.destroy();
|
|
1714
|
+
petMotion.current = null;
|
|
1715
|
+
};
|
|
1716
|
+
}, [petMode, stageModel]);
|
|
1717
|
+
React.useLayoutEffect(() => {
|
|
1718
|
+
petMotion.current?.configure({
|
|
1719
|
+
enabled:
|
|
1720
|
+
savedView.companionMotion &&
|
|
1721
|
+
waterVisible &&
|
|
1722
|
+
waterReady &&
|
|
1723
|
+
!waterError &&
|
|
1724
|
+
!editing &&
|
|
1725
|
+
!waterEditing &&
|
|
1726
|
+
!ui.menu,
|
|
1727
|
+
resting: ['quiet', 'offline'].includes(companion.pose),
|
|
1728
|
+
});
|
|
1729
|
+
}, [
|
|
1730
|
+
petMode,
|
|
1731
|
+
stageModel,
|
|
1732
|
+
savedView.companionMotion,
|
|
1733
|
+
waterVisible,
|
|
1734
|
+
waterReady,
|
|
1735
|
+
waterError,
|
|
1736
|
+
error,
|
|
1737
|
+
editing,
|
|
1738
|
+
waterEditing,
|
|
1739
|
+
!!ui.menu,
|
|
1740
|
+
companion.pose,
|
|
1741
|
+
]);
|
|
1742
|
+
React.useLayoutEffect(() => {
|
|
1743
|
+
const token = waterNotice?.token;
|
|
1744
|
+
if (petReactionToken.current === token) return;
|
|
1745
|
+
petReactionToken.current = token;
|
|
1746
|
+
if (petMode && companion.animate) petMotion.current?.play('celebrate');
|
|
1747
|
+
}, [petMode, waterNotice?.token, companion.animate]);
|
|
1748
|
+
React.useEffect(() => {
|
|
1749
|
+
const media = window.matchMedia?.('(prefers-reduced-motion: reduce)');
|
|
1750
|
+
const update = () => setPreviewReduced(!!media?.matches);
|
|
1751
|
+
media?.addEventListener?.('change', update);
|
|
1752
|
+
return () => media?.removeEventListener?.('change', update);
|
|
1753
|
+
}, []);
|
|
1754
|
+
React.useLayoutEffect(() => {
|
|
1755
|
+
if (!appearanceOnly || ui.pending) return;
|
|
1756
|
+
setPreviewState({ phase: 'idle', action: null });
|
|
1757
|
+
if (!view.companion || petModel(view.petModel).renderer !== 'video') return;
|
|
1758
|
+
const artwork = previewBox.current?.querySelector('.owc-pet-art');
|
|
1759
|
+
if (!artwork) return;
|
|
1760
|
+
const base = createPetMotion(artwork, window, { enabled: false, ambient: false });
|
|
1761
|
+
const controller = createPetVideoMotion(
|
|
1762
|
+
artwork,
|
|
1763
|
+
window,
|
|
1764
|
+
base,
|
|
1765
|
+
PET_CLIPS.models[view.petModel],
|
|
1766
|
+
PET_CLIPS.probe,
|
|
1767
|
+
{
|
|
1768
|
+
autoIdle: false,
|
|
1769
|
+
fallback: false,
|
|
1770
|
+
interruptible: true,
|
|
1771
|
+
onStateChange: setPreviewState,
|
|
1772
|
+
},
|
|
1773
|
+
);
|
|
1774
|
+
previewMotion.current = controller;
|
|
1775
|
+
return () => {
|
|
1776
|
+
controller.destroy();
|
|
1777
|
+
previewMotion.current = null;
|
|
1778
|
+
};
|
|
1779
|
+
}, [appearanceOnly, view.companion, view.petModel, !!ui.pending]);
|
|
1780
|
+
React.useLayoutEffect(() => {
|
|
1781
|
+
previewMotion.current?.configure({
|
|
1782
|
+
enabled:
|
|
1783
|
+
appearanceOnly &&
|
|
1784
|
+
view.companion &&
|
|
1785
|
+
view.companionMotion &&
|
|
1786
|
+
waterVisible &&
|
|
1787
|
+
!previewReduced &&
|
|
1788
|
+
!ui.menu &&
|
|
1789
|
+
!ui.pending,
|
|
1790
|
+
resting: false,
|
|
1791
|
+
});
|
|
1792
|
+
}, [
|
|
1793
|
+
appearanceOnly,
|
|
1794
|
+
view.companion,
|
|
1795
|
+
view.petModel,
|
|
1796
|
+
view.companionMotion,
|
|
1797
|
+
waterVisible,
|
|
1798
|
+
previewReduced,
|
|
1799
|
+
!!ui.menu,
|
|
1800
|
+
!!ui.pending,
|
|
1801
|
+
]);
|
|
1475
1802
|
const companionCard = (compact = false) =>
|
|
1476
1803
|
companion.visible &&
|
|
1477
1804
|
h(
|
|
@@ -1502,13 +1829,6 @@ window.__ModuleLoader__.load({
|
|
|
1502
1829
|
const waterSummary = () => {
|
|
1503
1830
|
if (!waterReady) return t('waterLoading');
|
|
1504
1831
|
if (waterError) return t('waterUnavailable');
|
|
1505
|
-
if (
|
|
1506
|
-
water.enabled &&
|
|
1507
|
-
waterContext &&
|
|
1508
|
-
water.shift &&
|
|
1509
|
-
water.shift.config !== waterContext.config
|
|
1510
|
-
)
|
|
1511
|
-
return t('waterStale');
|
|
1512
1832
|
const labels = {
|
|
1513
1833
|
off: 'waterOff',
|
|
1514
1834
|
waiting: 'waterWaiting',
|
|
@@ -1523,19 +1843,258 @@ window.__ModuleLoader__.load({
|
|
|
1523
1843
|
? t('waterNext') + ' · ' + formatDuration(waterState.remaining / 1000)
|
|
1524
1844
|
: t(labels[waterState.phase]);
|
|
1525
1845
|
};
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
!
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
(
|
|
1532
|
-
|
|
1846
|
+
const waterCountdownText = () => {
|
|
1847
|
+
if (waterError) return t('timerUnavailable');
|
|
1848
|
+
if (!waterReady) return t('timerLoading');
|
|
1849
|
+
if (['countdown', 'due', 'break'].includes(waterState.phase))
|
|
1850
|
+
return formatDuration(Math.ceil(waterState.remaining / 1000));
|
|
1851
|
+
if (waterState.phase === 'quiet')
|
|
1852
|
+
return formatDuration(Math.ceil(waterState.quietRemaining / 1000));
|
|
1853
|
+
return t(
|
|
1854
|
+
{
|
|
1855
|
+
off: 'timerOff',
|
|
1856
|
+
waiting: 'timerWaiting',
|
|
1857
|
+
paused: 'timerPaused',
|
|
1858
|
+
finished: 'timerFinished',
|
|
1859
|
+
}[waterState.phase] || 'timerUnavailable',
|
|
1860
|
+
);
|
|
1861
|
+
};
|
|
1862
|
+
const smokingCountdownText = () =>
|
|
1863
|
+
smokingUnavailable
|
|
1864
|
+
? t(smokingError ? 'timerUnavailable' : 'timerLoading')
|
|
1865
|
+
: smokingState.phase === 'idle'
|
|
1866
|
+
? t('timerIdle')
|
|
1867
|
+
: formatDuration(Math.ceil(smokingState.remaining / 1000));
|
|
1868
|
+
const timerRow = (kind, value, due, title) => {
|
|
1869
|
+
const phase = kind === 'water' ? waterState.phase : smokingState.phase;
|
|
1870
|
+
const extra = {
|
|
1871
|
+
paused: 'timerPaused',
|
|
1872
|
+
break: 'timerBreak',
|
|
1873
|
+
quiet: 'timerQuiet',
|
|
1874
|
+
due: 'timerDue',
|
|
1875
|
+
}[phase];
|
|
1876
|
+
return h(
|
|
1877
|
+
'div',
|
|
1878
|
+
{
|
|
1879
|
+
className: 'owc-pet-timer-row',
|
|
1880
|
+
'data-countdown': kind,
|
|
1881
|
+
'data-countdown-due': due,
|
|
1882
|
+
'data-countdown-phase': phase,
|
|
1883
|
+
role: 'timer',
|
|
1884
|
+
'aria-label': t(kind === 'water' ? 'waterCountdown' : 'smokingCountdown'),
|
|
1885
|
+
title,
|
|
1886
|
+
},
|
|
1887
|
+
h(
|
|
1888
|
+
'span',
|
|
1889
|
+
{ className: 'owc-pet-timer-label' },
|
|
1890
|
+
iconSvg(kind, 14),
|
|
1891
|
+
extra && h('span', { className: 'owc-sr-only' }, t(extra)),
|
|
1892
|
+
),
|
|
1893
|
+
h(
|
|
1894
|
+
'strong',
|
|
1895
|
+
null,
|
|
1896
|
+
['paused', 'break'].includes(phase) &&
|
|
1897
|
+
/^\d/.test(value) &&
|
|
1898
|
+
h('span', { className: 'owc-timer-pause', 'aria-hidden': true }, 'Ⅱ'),
|
|
1899
|
+
phase === 'quiet' &&
|
|
1900
|
+
h('span', { className: 'owc-timer-pause', 'aria-hidden': true }, '☾'),
|
|
1901
|
+
value.startsWith('00:') ? value.slice(3) : value.replace(/^0(?=\d:)/, ''),
|
|
1902
|
+
),
|
|
1903
|
+
);
|
|
1904
|
+
};
|
|
1905
|
+
const waterHero = () => {
|
|
1906
|
+
const ticking =
|
|
1907
|
+
waterReady && !waterError && ['countdown', 'due', 'quiet'].includes(waterState.phase);
|
|
1908
|
+
return h(
|
|
1909
|
+
'div',
|
|
1910
|
+
{ className: 'owc-detail-hero' },
|
|
1911
|
+
h(
|
|
1912
|
+
'p',
|
|
1913
|
+
{ className: 'owc-detail-kicker', 'data-water-status': true },
|
|
1914
|
+
t(
|
|
1915
|
+
ticking
|
|
1916
|
+
? waterState.phase === 'quiet'
|
|
1917
|
+
? 'waterQuiet'
|
|
1918
|
+
: waterState.phase === 'due'
|
|
1919
|
+
? 'waterDue'
|
|
1920
|
+
: 'waterNext'
|
|
1921
|
+
: 'waterTitle',
|
|
1922
|
+
),
|
|
1923
|
+
),
|
|
1924
|
+
h(
|
|
1925
|
+
'div',
|
|
1926
|
+
{
|
|
1927
|
+
className: 'owc-detail-value' + (ticking ? '' : ' owc-detail-state'),
|
|
1928
|
+
role: 'timer',
|
|
1929
|
+
'aria-label': t('waterCountdown'),
|
|
1930
|
+
},
|
|
1931
|
+
waterCountdownText(),
|
|
1932
|
+
),
|
|
1933
|
+
h(
|
|
1934
|
+
'div',
|
|
1935
|
+
{ className: 'owc-detail-meta' },
|
|
1936
|
+
h(
|
|
1937
|
+
'span',
|
|
1938
|
+
{ 'data-water-count': true },
|
|
1939
|
+
t('waterCount') + ' · ' + (waterReady && !waterError ? waterState.count : '—'),
|
|
1940
|
+
),
|
|
1941
|
+
h(
|
|
1942
|
+
'span',
|
|
1943
|
+
{ className: 'owc-detail-feedback' },
|
|
1944
|
+
waterNotice ? t(waterNotice.key) : '',
|
|
1945
|
+
),
|
|
1946
|
+
),
|
|
1947
|
+
);
|
|
1948
|
+
};
|
|
1949
|
+
const waterCountdownCard = () =>
|
|
1950
|
+
h(
|
|
1951
|
+
'div',
|
|
1952
|
+
{ className: 'owc-countdown-card', 'data-water-countdown-card': true },
|
|
1953
|
+
waterHero(),
|
|
1954
|
+
h(
|
|
1955
|
+
'div',
|
|
1956
|
+
{ className: 'owc-countdown-actions' },
|
|
1957
|
+
h(
|
|
1958
|
+
'button',
|
|
1959
|
+
{
|
|
1960
|
+
type: 'button',
|
|
1961
|
+
className: 'owc-primary owc-water-record',
|
|
1962
|
+
disabled: !waterCanRecord || waterBusy,
|
|
1963
|
+
onClick: recordWater,
|
|
1964
|
+
},
|
|
1965
|
+
t('petRecord'),
|
|
1966
|
+
),
|
|
1967
|
+
h(
|
|
1968
|
+
'button',
|
|
1969
|
+
{
|
|
1970
|
+
type: 'button',
|
|
1971
|
+
className: 'owc-text',
|
|
1972
|
+
onClick: (event) => navigateScreen('home', event),
|
|
1973
|
+
},
|
|
1974
|
+
t('timerWaterDetails'),
|
|
1975
|
+
),
|
|
1976
|
+
),
|
|
1977
|
+
);
|
|
1978
|
+
const smokingCountdownCard = () =>
|
|
1979
|
+
h(
|
|
1980
|
+
'div',
|
|
1981
|
+
{ className: 'owc-countdown-card', 'data-smoking-countdown-card': true },
|
|
1982
|
+
h(
|
|
1983
|
+
'div',
|
|
1984
|
+
{
|
|
1985
|
+
className: 'owc-countdown-value',
|
|
1986
|
+
role: 'timer',
|
|
1987
|
+
'aria-label': t('smokingCountdown'),
|
|
1988
|
+
},
|
|
1989
|
+
!smokingUnavailable && smokingState.phase === 'idle'
|
|
1990
|
+
? formatDuration(smoking.minutes * 60)
|
|
1991
|
+
: smokingCountdownText(),
|
|
1992
|
+
),
|
|
1993
|
+
h(
|
|
1994
|
+
'p',
|
|
1995
|
+
{ role: 'status' },
|
|
1996
|
+
smokingUnavailable
|
|
1997
|
+
? t(smokingError ? 'timerUnavailable' : 'timerLoading')
|
|
1998
|
+
: t(
|
|
1999
|
+
{
|
|
2000
|
+
idle: 'timerIdle',
|
|
2001
|
+
running: 'timerRunning',
|
|
2002
|
+
paused: 'timerPaused',
|
|
2003
|
+
due: 'timerDue',
|
|
2004
|
+
}[smokingState.phase],
|
|
2005
|
+
),
|
|
2006
|
+
),
|
|
2007
|
+
h(
|
|
2008
|
+
'label',
|
|
2009
|
+
null,
|
|
2010
|
+
t('timerInterval'),
|
|
2011
|
+
h(
|
|
2012
|
+
'select',
|
|
2013
|
+
{
|
|
2014
|
+
value: smoking.minutes,
|
|
2015
|
+
disabled: smokingUnavailable || smokingBusy,
|
|
2016
|
+
onChange: (event) =>
|
|
2017
|
+
smokingAction({ type: 'interval', minutes: Number(event.target.value) }),
|
|
2018
|
+
},
|
|
2019
|
+
COUNTDOWN_INTERVALS.map((minutes) =>
|
|
2020
|
+
h('option', { key: minutes, value: minutes }, String(minutes)),
|
|
2021
|
+
),
|
|
2022
|
+
),
|
|
2023
|
+
),
|
|
2024
|
+
h(
|
|
2025
|
+
'div',
|
|
2026
|
+
{ className: 'owc-countdown-actions' },
|
|
2027
|
+
h(
|
|
2028
|
+
'button',
|
|
2029
|
+
{
|
|
2030
|
+
type: 'button',
|
|
2031
|
+
className: 'owc-primary',
|
|
2032
|
+
disabled: smokingUnavailable || smokingBusy,
|
|
2033
|
+
onClick: () =>
|
|
2034
|
+
smokingAction({
|
|
2035
|
+
type:
|
|
2036
|
+
smokingState.phase === 'running'
|
|
2037
|
+
? 'pause'
|
|
2038
|
+
: smokingState.phase === 'paused'
|
|
2039
|
+
? 'resume'
|
|
2040
|
+
: 'start',
|
|
2041
|
+
}),
|
|
2042
|
+
},
|
|
2043
|
+
t(
|
|
2044
|
+
smokingState.phase === 'running'
|
|
2045
|
+
? 'timerPause'
|
|
2046
|
+
: smokingState.phase === 'paused'
|
|
2047
|
+
? 'timerResume'
|
|
2048
|
+
: 'timerStart',
|
|
2049
|
+
),
|
|
2050
|
+
),
|
|
2051
|
+
smokingState.phase !== 'idle' &&
|
|
2052
|
+
h(
|
|
2053
|
+
'button',
|
|
2054
|
+
{
|
|
2055
|
+
type: 'button',
|
|
2056
|
+
disabled: smokingUnavailable || smokingBusy,
|
|
2057
|
+
onClick: () => smokingAction({ type: 'restart' }),
|
|
2058
|
+
},
|
|
2059
|
+
t('timerRestart'),
|
|
2060
|
+
),
|
|
2061
|
+
smokingState.phase !== 'idle' &&
|
|
2062
|
+
h(
|
|
2063
|
+
'button',
|
|
2064
|
+
{
|
|
2065
|
+
type: 'button',
|
|
2066
|
+
disabled: smokingUnavailable || smokingBusy,
|
|
2067
|
+
onClick: () => smokingAction({ type: 'stop' }),
|
|
2068
|
+
},
|
|
2069
|
+
t('timerStop'),
|
|
2070
|
+
),
|
|
2071
|
+
),
|
|
2072
|
+
h('p', { className: 'owc-hint' }, t('detailNextInterval')),
|
|
2073
|
+
h(
|
|
2074
|
+
'details',
|
|
2075
|
+
{ className: 'owc-detail-more' },
|
|
2076
|
+
h('summary', null, t('detailHelp')),
|
|
2077
|
+
h('p', null, t('timerHint')),
|
|
2078
|
+
),
|
|
2079
|
+
smokingError && h('p', { className: 'owc-error', role: 'alert' }, t(smokingError)),
|
|
2080
|
+
smokingError === 'timerStorageError' &&
|
|
2081
|
+
h(
|
|
2082
|
+
'button',
|
|
2083
|
+
{
|
|
2084
|
+
type: 'button',
|
|
2085
|
+
disabled: smokingBusy,
|
|
2086
|
+
onClick: () => smokingAction({ type: 'retry' }),
|
|
2087
|
+
},
|
|
2088
|
+
t('timerRetry'),
|
|
2089
|
+
),
|
|
2090
|
+
);
|
|
2091
|
+
const waterCanRecord = waterReady && !waterError;
|
|
2092
|
+
const waterCanQuiet = waterCanRecord && water.enabled && !water.paused;
|
|
1533
2093
|
const recordWater = (event) => {
|
|
1534
2094
|
if (event.detail > 1) return;
|
|
1535
2095
|
if (!manualAttempt.current)
|
|
1536
2096
|
manualAttempt.current = {
|
|
1537
2097
|
type: 'manual-drink',
|
|
1538
|
-
shiftId: waterContext.id,
|
|
1539
2098
|
actionId: window.crypto?.randomUUID?.() || String(Math.random()) + Date.now(),
|
|
1540
2099
|
...(waterState.phase === 'due' ? { eventId: waterState.eventId } : {}),
|
|
1541
2100
|
};
|
|
@@ -1604,14 +2163,10 @@ window.__ModuleLoader__.load({
|
|
|
1604
2163
|
'button',
|
|
1605
2164
|
{
|
|
1606
2165
|
type: 'button',
|
|
1607
|
-
disabled:
|
|
1608
|
-
|
|
1609
|
-
waterBusy ||
|
|
1610
|
-
!waterContext ||
|
|
1611
|
-
water.shift?.id !== waterContext.id,
|
|
1612
|
-
onClick: () => waterAction({ type: water.shift?.paused ? 'resume' : 'pause' }),
|
|
2166
|
+
disabled: waterError || waterBusy || !waterReady,
|
|
2167
|
+
onClick: () => waterAction({ type: water.paused ? 'resume' : 'pause' }),
|
|
1613
2168
|
},
|
|
1614
|
-
t(water.
|
|
2169
|
+
t(water.paused ? 'waterResume' : 'waterPause'),
|
|
1615
2170
|
),
|
|
1616
2171
|
waterError &&
|
|
1617
2172
|
h(
|
|
@@ -1661,7 +2216,7 @@ window.__ModuleLoader__.load({
|
|
|
1661
2216
|
className: 'owc-text',
|
|
1662
2217
|
style: { marginLeft: 'auto' },
|
|
1663
2218
|
'aria-label': t('waterEdit'),
|
|
1664
|
-
disabled: !waterReady
|
|
2219
|
+
disabled: !waterReady,
|
|
1665
2220
|
onClick: openWaterEditor,
|
|
1666
2221
|
},
|
|
1667
2222
|
t('settings'),
|
|
@@ -1694,16 +2249,59 @@ window.__ModuleLoader__.load({
|
|
|
1694
2249
|
{ 'data-water-effective': water.interval },
|
|
1695
2250
|
t('waterEvery') + ' ' + water.interval + ' ' + t('waterMinutes'),
|
|
1696
2251
|
),
|
|
1697
|
-
h(
|
|
2252
|
+
h(
|
|
2253
|
+
'span',
|
|
2254
|
+
{ 'data-water-count': true },
|
|
2255
|
+
t('waterCount') + ': ' + (waterReady && !waterError ? waterState.count : '—'),
|
|
2256
|
+
),
|
|
1698
2257
|
),
|
|
1699
2258
|
waterControls(),
|
|
1700
|
-
|
|
1701
|
-
h('p', { className: 'owc-hint' }, t('waterRecordHint')),
|
|
2259
|
+
h('p', { className: 'owc-hint' }, t('waterRecordHint')),
|
|
1702
2260
|
waterState.phase === 'quiet' &&
|
|
1703
2261
|
h('p', { className: 'owc-hint' }, t('waterQuietHint')),
|
|
1704
2262
|
waterNotice && !view.companion && h('p', null, t(waterNotice.key)),
|
|
1705
2263
|
);
|
|
1706
2264
|
|
|
2265
|
+
const waterIntervalField = () =>
|
|
2266
|
+
h(
|
|
2267
|
+
'label',
|
|
2268
|
+
{ className: 'owc-water-interval' },
|
|
2269
|
+
h('span', null, t('waterInterval')),
|
|
2270
|
+
h('input', {
|
|
2271
|
+
name: 'waterInterval',
|
|
2272
|
+
type: 'number',
|
|
2273
|
+
inputMode: 'numeric',
|
|
2274
|
+
min: '5',
|
|
2275
|
+
max: '240',
|
|
2276
|
+
step: '1',
|
|
2277
|
+
value: waterInterval,
|
|
2278
|
+
'aria-invalid': waterInvalid,
|
|
2279
|
+
onChange: (event) => {
|
|
2280
|
+
setWaterInterval(event.target.value);
|
|
2281
|
+
setWaterInvalid(false);
|
|
2282
|
+
},
|
|
2283
|
+
}),
|
|
2284
|
+
);
|
|
2285
|
+
const waterSettingsActions = () =>
|
|
2286
|
+
h(
|
|
2287
|
+
'div',
|
|
2288
|
+
{ className: 'owc-save' },
|
|
2289
|
+
h(
|
|
2290
|
+
'button',
|
|
2291
|
+
{ type: 'button', disabled: waterBusy, onClick: closeWaterEditor },
|
|
2292
|
+
t('cancel'),
|
|
2293
|
+
),
|
|
2294
|
+
h(
|
|
2295
|
+
'button',
|
|
2296
|
+
{
|
|
2297
|
+
type: 'submit',
|
|
2298
|
+
className: 'owc-primary',
|
|
2299
|
+
'aria-label': t('waterSave'),
|
|
2300
|
+
disabled: !waterReady || waterError || waterBusy,
|
|
2301
|
+
},
|
|
2302
|
+
t(waterBusy ? 'busy' : 'save'),
|
|
2303
|
+
),
|
|
2304
|
+
);
|
|
1707
2305
|
const waterSettings = () =>
|
|
1708
2306
|
h(
|
|
1709
2307
|
'form',
|
|
@@ -1726,7 +2324,7 @@ window.__ModuleLoader__.load({
|
|
|
1726
2324
|
h('input', {
|
|
1727
2325
|
type: 'checkbox',
|
|
1728
2326
|
checked: waterEnabledDraft,
|
|
1729
|
-
disabled: !waterReady || waterError || waterBusy
|
|
2327
|
+
disabled: !waterReady || waterError || waterBusy,
|
|
1730
2328
|
onChange: (event) => {
|
|
1731
2329
|
setWaterEnabledDraft(event.target.checked);
|
|
1732
2330
|
setWaterNotice(false);
|
|
@@ -1735,8 +2333,9 @@ window.__ModuleLoader__.load({
|
|
|
1735
2333
|
),
|
|
1736
2334
|
h(
|
|
1737
2335
|
'fieldset',
|
|
1738
|
-
{ disabled: !waterReady || waterError || waterBusy
|
|
1739
|
-
h('legend', { className: 'owc-
|
|
2336
|
+
{ disabled: !waterReady || waterError || waterBusy },
|
|
2337
|
+
h('legend', { className: 'owc-sr-only' }, t('waterPresets')),
|
|
2338
|
+
waterIntervalField(),
|
|
1740
2339
|
h(
|
|
1741
2340
|
'div',
|
|
1742
2341
|
{ className: 'owc-water-presets' },
|
|
@@ -1747,64 +2346,34 @@ window.__ModuleLoader__.load({
|
|
|
1747
2346
|
type: 'button',
|
|
1748
2347
|
key: minutes,
|
|
1749
2348
|
'aria-pressed': Number(waterInterval) === minutes,
|
|
2349
|
+
'aria-label': minutes + ' ' + t('waterMinutes'),
|
|
1750
2350
|
onClick: () => {
|
|
1751
2351
|
setWaterInterval(String(minutes));
|
|
1752
2352
|
setWaterInvalid(false);
|
|
1753
2353
|
},
|
|
1754
2354
|
},
|
|
1755
|
-
minutes
|
|
2355
|
+
String(minutes),
|
|
1756
2356
|
),
|
|
1757
2357
|
),
|
|
1758
2358
|
),
|
|
1759
|
-
h(
|
|
1760
|
-
'label',
|
|
1761
|
-
{ className: 'owc-field' },
|
|
1762
|
-
h('span', null, t('waterInterval')),
|
|
1763
|
-
h('input', {
|
|
1764
|
-
name: 'waterInterval',
|
|
1765
|
-
type: 'number',
|
|
1766
|
-
inputMode: 'numeric',
|
|
1767
|
-
min: '5',
|
|
1768
|
-
max: '240',
|
|
1769
|
-
step: '1',
|
|
1770
|
-
value: waterInterval,
|
|
1771
|
-
'aria-invalid': waterInvalid,
|
|
1772
|
-
onChange: (event) => {
|
|
1773
|
-
setWaterInterval(event.target.value);
|
|
1774
|
-
setWaterInvalid(false);
|
|
1775
|
-
},
|
|
1776
|
-
}),
|
|
1777
|
-
),
|
|
1778
2359
|
),
|
|
1779
2360
|
waterInvalid && h('p', { role: 'alert' }, t('waterInvalid')),
|
|
1780
|
-
|
|
1781
|
-
'p',
|
|
1782
|
-
{ 'data-water-saved-interval': water.interval },
|
|
1783
|
-
t('waterEffective') + ': ' + water.interval + ' ' + t('waterMinutes'),
|
|
1784
|
-
),
|
|
1785
|
-
h('p', { className: 'owc-hint' }, t('waterSaveHint')),
|
|
1786
|
-
h('p', { className: 'owc-hint' }, t('waterBrief')),
|
|
1787
|
-
waterError && h('p', { role: 'alert' }, t('waterUnavailable')),
|
|
1788
|
-
!data?.configured && h('p', null, t('waterSetup')),
|
|
1789
|
-
h(
|
|
1790
|
-
'div',
|
|
1791
|
-
{ className: 'owc-save' },
|
|
2361
|
+
(Number(waterInterval) !== water.interval || waterEnabledDraft !== water.enabled) &&
|
|
1792
2362
|
h(
|
|
1793
|
-
'
|
|
1794
|
-
{
|
|
1795
|
-
t('
|
|
2363
|
+
'p',
|
|
2364
|
+
{ className: 'owc-hint', 'data-water-saved-interval': water.interval },
|
|
2365
|
+
t('detailSavedInterval') +
|
|
2366
|
+
': ' +
|
|
2367
|
+
water.interval +
|
|
2368
|
+
' ' +
|
|
2369
|
+
t('waterMinutes') +
|
|
2370
|
+
' · ' +
|
|
2371
|
+
t('detailDraftHint'),
|
|
1796
2372
|
),
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
className: 'owc-primary',
|
|
1802
|
-
disabled: !waterReady || waterError || waterBusy || !data?.configured,
|
|
1803
|
-
},
|
|
1804
|
-
t(waterBusy ? 'busy' : 'waterSave'),
|
|
1805
|
-
),
|
|
1806
|
-
),
|
|
1807
|
-
waterError &&
|
|
2373
|
+
h('p', { className: 'owc-hint' }, t('detailWaterRule')),
|
|
2374
|
+
waterError && h('p', { role: 'alert' }, t('waterUnavailable')),
|
|
2375
|
+
|
|
2376
|
+
waterError &&
|
|
1808
2377
|
h(
|
|
1809
2378
|
'button',
|
|
1810
2379
|
{
|
|
@@ -1866,6 +2435,7 @@ window.__ModuleLoader__.load({
|
|
|
1866
2435
|
),
|
|
1867
2436
|
),
|
|
1868
2437
|
),
|
|
2438
|
+
waterSettingsActions(),
|
|
1869
2439
|
);
|
|
1870
2440
|
const waterReminder = () =>
|
|
1871
2441
|
waterPrompt &&
|
|
@@ -1880,6 +2450,7 @@ window.__ModuleLoader__.load({
|
|
|
1880
2450
|
'button',
|
|
1881
2451
|
{
|
|
1882
2452
|
type: 'button',
|
|
2453
|
+
className: 'owc-primary',
|
|
1883
2454
|
disabled: waterBusy,
|
|
1884
2455
|
onClick: () => waterAction({ type: 'drink', eventId: waterState.eventId }),
|
|
1885
2456
|
},
|
|
@@ -1897,6 +2468,440 @@ window.__ModuleLoader__.load({
|
|
|
1897
2468
|
),
|
|
1898
2469
|
);
|
|
1899
2470
|
|
|
2471
|
+
const classicMini = () => {
|
|
2472
|
+
const countdown = classicClock.seconds !== null;
|
|
2473
|
+
const caption =
|
|
2474
|
+
{
|
|
2475
|
+
working: 'classicMiniWorking',
|
|
2476
|
+
before: 'classicMiniBefore',
|
|
2477
|
+
lunch: 'classicMiniLunch',
|
|
2478
|
+
}[classicClock.phase] || 'classicMiniStatus';
|
|
2479
|
+
const stateTitle =
|
|
2480
|
+
{ setup: 'petNeedsSetup', error: 'petLoadFailed', waiting: 'petNeedsSession' }[
|
|
2481
|
+
classicClock.phase
|
|
2482
|
+
] || classicClock.title;
|
|
2483
|
+
const valid = !!data?.configured && !!safeState && !error;
|
|
2484
|
+
const showIncome = valid && ['working', 'lunch', 'done'].includes(safeState.status);
|
|
2485
|
+
const compact = (value) => {
|
|
2486
|
+
if (value === t('timerUnavailable')) return '—';
|
|
2487
|
+
if (value === t('timerWaiting')) return t('classicMiniWaiting');
|
|
2488
|
+
if (value === t('timerFinished')) return t('classicMiniFinished');
|
|
2489
|
+
return value.startsWith('00:') ? value.slice(3) : value.replace(/^0(?=\d:)/, '');
|
|
2490
|
+
};
|
|
2491
|
+
const tile = (kind, screen, label, value, summary, due) =>
|
|
2492
|
+
h(
|
|
2493
|
+
'button',
|
|
2494
|
+
{
|
|
2495
|
+
type: 'button',
|
|
2496
|
+
className: 'owc-classic-mini-timer',
|
|
2497
|
+
'data-classic-timer': kind,
|
|
2498
|
+
'data-countdown-due': due,
|
|
2499
|
+
'aria-label': t(label),
|
|
2500
|
+
title: t(label) + ' · ' + summary,
|
|
2501
|
+
disabled: busy || waterBusy,
|
|
2502
|
+
onClick: (event) => navigateScreen(screen, event),
|
|
2503
|
+
},
|
|
2504
|
+
iconSvg(kind, 13),
|
|
2505
|
+
h('strong', null, compact(value)),
|
|
2506
|
+
);
|
|
2507
|
+
return h(
|
|
2508
|
+
'div',
|
|
2509
|
+
{
|
|
2510
|
+
className: 'owc-classic-mini',
|
|
2511
|
+
'data-classic-overview': true,
|
|
2512
|
+
'data-clock-phase': classicClock.phase,
|
|
2513
|
+
},
|
|
2514
|
+
h(
|
|
2515
|
+
'div',
|
|
2516
|
+
{ className: 'owc-classic-mini-top' },
|
|
2517
|
+
h(
|
|
2518
|
+
'div',
|
|
2519
|
+
{
|
|
2520
|
+
className: 'owc-grip',
|
|
2521
|
+
tabIndex: 0,
|
|
2522
|
+
title: t(classicClock.hint) + ' · ' + t('drag'),
|
|
2523
|
+
onKeyDown: gripKeys,
|
|
2524
|
+
},
|
|
2525
|
+
h('span', { className: 'owc-classic-mini-label' }, t(caption)),
|
|
2526
|
+
countdown
|
|
2527
|
+
? h(
|
|
2528
|
+
'strong',
|
|
2529
|
+
{
|
|
2530
|
+
className: 'owc-classic-mini-time',
|
|
2531
|
+
role: 'timer',
|
|
2532
|
+
'aria-label': t(classicClock.title),
|
|
2533
|
+
},
|
|
2534
|
+
formatDuration(classicClock.seconds),
|
|
2535
|
+
)
|
|
2536
|
+
: ['setup', 'error'].includes(classicClock.phase)
|
|
2537
|
+
? h(
|
|
2538
|
+
'button',
|
|
2539
|
+
{
|
|
2540
|
+
type: 'button',
|
|
2541
|
+
className: 'owc-classic-mini-state',
|
|
2542
|
+
disabled: hostBusy,
|
|
2543
|
+
'aria-label': t(
|
|
2544
|
+
classicClock.phase === 'setup' ? 'classicConfigure' : 'retry',
|
|
2545
|
+
),
|
|
2546
|
+
onClick:
|
|
2547
|
+
classicClock.phase === 'setup' ? openEditor : () => reload(true),
|
|
2548
|
+
},
|
|
2549
|
+
t(stateTitle),
|
|
2550
|
+
)
|
|
2551
|
+
: h(
|
|
2552
|
+
'strong',
|
|
2553
|
+
{ className: 'owc-classic-mini-state', role: 'status' },
|
|
2554
|
+
t(stateTitle),
|
|
2555
|
+
),
|
|
2556
|
+
),
|
|
2557
|
+
h(
|
|
2558
|
+
'button',
|
|
2559
|
+
{
|
|
2560
|
+
type: 'button',
|
|
2561
|
+
className: 'owc-icon',
|
|
2562
|
+
title: t('expand'),
|
|
2563
|
+
'aria-label': t('expand'),
|
|
2564
|
+
onClick: () => setPreference({ mini: false }),
|
|
2565
|
+
},
|
|
2566
|
+
iconSvg('chevronDown', 14),
|
|
2567
|
+
),
|
|
2568
|
+
),
|
|
2569
|
+
h(
|
|
2570
|
+
'div',
|
|
2571
|
+
{ className: 'owc-classic-mini-timers' },
|
|
2572
|
+
tile(
|
|
2573
|
+
'water',
|
|
2574
|
+
'water-countdown',
|
|
2575
|
+
'waterCountdown',
|
|
2576
|
+
waterCountdownText(),
|
|
2577
|
+
waterSummary(),
|
|
2578
|
+
waterState.phase === 'due',
|
|
2579
|
+
),
|
|
2580
|
+
tile(
|
|
2581
|
+
'smoking',
|
|
2582
|
+
'smoking',
|
|
2583
|
+
'smokingCountdown',
|
|
2584
|
+
smokingCountdownText(),
|
|
2585
|
+
t(
|
|
2586
|
+
smokingState.phase === 'paused'
|
|
2587
|
+
? 'timerPaused'
|
|
2588
|
+
: smokingState.phase === 'due'
|
|
2589
|
+
? 'timerDue'
|
|
2590
|
+
: smokingState.phase === 'idle'
|
|
2591
|
+
? 'timerIdle'
|
|
2592
|
+
: 'timerRunning',
|
|
2593
|
+
),
|
|
2594
|
+
smokingState.phase === 'due',
|
|
2595
|
+
),
|
|
2596
|
+
h(
|
|
2597
|
+
'div',
|
|
2598
|
+
{ className: 'owc-classic-mini-income' },
|
|
2599
|
+
showIncome &&
|
|
2600
|
+
!view.masked &&
|
|
2601
|
+
h(
|
|
2602
|
+
'button',
|
|
2603
|
+
{
|
|
2604
|
+
type: 'button',
|
|
2605
|
+
className: 'owc-classic-mini-amount',
|
|
2606
|
+
'aria-label': t('classicIncomeEntry'),
|
|
2607
|
+
'aria-description': moneyText,
|
|
2608
|
+
title: moneyText,
|
|
2609
|
+
onClick: (event) => navigateScreen('earnings', event),
|
|
2610
|
+
},
|
|
2611
|
+
moneyText,
|
|
2612
|
+
),
|
|
2613
|
+
h(
|
|
2614
|
+
'button',
|
|
2615
|
+
{
|
|
2616
|
+
type: 'button',
|
|
2617
|
+
className: 'owc-icon',
|
|
2618
|
+
disabled: !valid,
|
|
2619
|
+
'aria-label': t(
|
|
2620
|
+
showIncome ? (view.masked ? 'show' : 'hide') : 'classicIncomeEntry',
|
|
2621
|
+
),
|
|
2622
|
+
title: t(showIncome ? (view.masked ? 'show' : 'hide') : 'classicIncomeEntry'),
|
|
2623
|
+
'aria-pressed': showIncome ? !view.masked : undefined,
|
|
2624
|
+
onClick: (event) =>
|
|
2625
|
+
showIncome
|
|
2626
|
+
? setPreference({ masked: !view.masked })
|
|
2627
|
+
: navigateScreen('earnings', event),
|
|
2628
|
+
},
|
|
2629
|
+
iconSvg(view.masked || !showIncome ? 'eyeOff' : 'eye', 13),
|
|
2630
|
+
),
|
|
2631
|
+
),
|
|
2632
|
+
),
|
|
2633
|
+
);
|
|
2634
|
+
};
|
|
2635
|
+
|
|
2636
|
+
const classicHeader = () =>
|
|
2637
|
+
h(
|
|
2638
|
+
'div',
|
|
2639
|
+
{ className: 'owc-head owc-classic-head' },
|
|
2640
|
+
ui.screen &&
|
|
2641
|
+
h(
|
|
2642
|
+
'button',
|
|
2643
|
+
{
|
|
2644
|
+
type: 'button',
|
|
2645
|
+
className: 'owc-icon',
|
|
2646
|
+
'aria-label': t(
|
|
2647
|
+
PET_EDITOR_SCREENS.includes(ui.screen) && ui.back
|
|
2648
|
+
? 'petBack'
|
|
2649
|
+
: 'classicReturn',
|
|
2650
|
+
),
|
|
2651
|
+
title: t(
|
|
2652
|
+
PET_EDITOR_SCREENS.includes(ui.screen) && ui.back
|
|
2653
|
+
? 'petBack'
|
|
2654
|
+
: 'classicReturn',
|
|
2655
|
+
),
|
|
2656
|
+
disabled: busy || waterBusy || !!ui.pending,
|
|
2657
|
+
onClick: () =>
|
|
2658
|
+
navigateScreen(PET_EDITOR_SCREENS.includes(ui.screen) ? ui.back : null),
|
|
2659
|
+
},
|
|
2660
|
+
'←',
|
|
2661
|
+
),
|
|
2662
|
+
h(
|
|
2663
|
+
'strong',
|
|
2664
|
+
{ className: 'owc-title', tabIndex: 0, title: t('drag'), onKeyDown: gripKeys },
|
|
2665
|
+
t(ui.screen ? petPageTitle[ui.screen] || 'classicOverview' : 'classicOverview'),
|
|
2666
|
+
),
|
|
2667
|
+
['home', 'water-countdown'].includes(ui.screen) &&
|
|
2668
|
+
h(
|
|
2669
|
+
'button',
|
|
2670
|
+
{
|
|
2671
|
+
type: 'button',
|
|
2672
|
+
className: 'owc-icon',
|
|
2673
|
+
'aria-label': t('petWaterSettings'),
|
|
2674
|
+
title: t('petWaterSettings'),
|
|
2675
|
+
disabled: busy || waterBusy || !!ui.pending,
|
|
2676
|
+
onClick: openWaterEditor,
|
|
2677
|
+
},
|
|
2678
|
+
iconSvg('sliders', 14),
|
|
2679
|
+
),
|
|
2680
|
+
waterPrompt &&
|
|
2681
|
+
ui.screen &&
|
|
2682
|
+
!['home', 'water-countdown'].includes(ui.screen) &&
|
|
2683
|
+
h(
|
|
2684
|
+
'button',
|
|
2685
|
+
{
|
|
2686
|
+
type: 'button',
|
|
2687
|
+
className: 'owc-icon',
|
|
2688
|
+
'aria-label': t('waterDue'),
|
|
2689
|
+
title: t('waterDue'),
|
|
2690
|
+
onClick: (event) => navigateScreen('home', event),
|
|
2691
|
+
},
|
|
2692
|
+
iconSvg('water'),
|
|
2693
|
+
),
|
|
2694
|
+
!ui.screen &&
|
|
2695
|
+
h(
|
|
2696
|
+
React.Fragment,
|
|
2697
|
+
null,
|
|
2698
|
+
h(
|
|
2699
|
+
'button',
|
|
2700
|
+
{ type: 'button', className: 'owc-text', onClick: openAppearance },
|
|
2701
|
+
t('appearanceGroup'),
|
|
2702
|
+
),
|
|
2703
|
+
h(
|
|
2704
|
+
'button',
|
|
2705
|
+
{
|
|
2706
|
+
type: 'button',
|
|
2707
|
+
className: 'owc-icon',
|
|
2708
|
+
'aria-label': t('petWorkSettings'),
|
|
2709
|
+
title: t('petWorkSettings'),
|
|
2710
|
+
disabled: hostBusy || !data,
|
|
2711
|
+
onClick: openEditor,
|
|
2712
|
+
},
|
|
2713
|
+
iconSvg('sliders'),
|
|
2714
|
+
),
|
|
2715
|
+
h(
|
|
2716
|
+
'button',
|
|
2717
|
+
{
|
|
2718
|
+
type: 'button',
|
|
2719
|
+
className: 'owc-icon',
|
|
2720
|
+
'aria-label': t(collapsed ? 'classicMore' : 'classicLess'),
|
|
2721
|
+
title: t(collapsed ? 'classicMore' : 'classicLess'),
|
|
2722
|
+
onClick: () => setPreference({ mini: !view.mini }),
|
|
2723
|
+
},
|
|
2724
|
+
iconSvg(collapsed ? 'chevronDown' : 'chevronUp'),
|
|
2725
|
+
),
|
|
2726
|
+
),
|
|
2727
|
+
);
|
|
2728
|
+
const classicOverview = () => {
|
|
2729
|
+
const countdown = classicClock.seconds !== null;
|
|
2730
|
+
const valid = !!data?.configured && !!safeState && !error;
|
|
2731
|
+
const showIncome = valid && ['working', 'lunch', 'done'].includes(safeState.status);
|
|
2732
|
+
const tile = (kind, screen, label, value, summary) =>
|
|
2733
|
+
h(
|
|
2734
|
+
'button',
|
|
2735
|
+
{
|
|
2736
|
+
type: 'button',
|
|
2737
|
+
className: 'owc-classic-tile',
|
|
2738
|
+
'data-classic-timer': kind,
|
|
2739
|
+
'aria-label': t(label),
|
|
2740
|
+
disabled: busy || waterBusy,
|
|
2741
|
+
onClick: (event) => navigateScreen(screen, event),
|
|
2742
|
+
},
|
|
2743
|
+
h(
|
|
2744
|
+
'span',
|
|
2745
|
+
{ className: 'owc-classic-tile-title' },
|
|
2746
|
+
iconSvg(kind, 14),
|
|
2747
|
+
t(kind === 'water' ? 'waterTimerShort' : 'smokingTimerShort'),
|
|
2748
|
+
),
|
|
2749
|
+
h('strong', null, value),
|
|
2750
|
+
h('span', { className: 'owc-classic-tile-hint' }, summary),
|
|
2751
|
+
);
|
|
2752
|
+
return h(
|
|
2753
|
+
'div',
|
|
2754
|
+
{
|
|
2755
|
+
className: 'owc-classic-overview',
|
|
2756
|
+
'data-classic-overview': true,
|
|
2757
|
+
'data-clock-phase': classicClock.phase,
|
|
2758
|
+
},
|
|
2759
|
+
h(
|
|
2760
|
+
'div',
|
|
2761
|
+
{ className: 'owc-classic-hero' },
|
|
2762
|
+
h(
|
|
2763
|
+
'div',
|
|
2764
|
+
{ className: 'owc-classic-kicker' },
|
|
2765
|
+
iconSvg('alarm', 14),
|
|
2766
|
+
t(countdown ? classicClock.title : 'title'),
|
|
2767
|
+
),
|
|
2768
|
+
countdown
|
|
2769
|
+
? h(
|
|
2770
|
+
'div',
|
|
2771
|
+
{
|
|
2772
|
+
className: 'owc-classic-time',
|
|
2773
|
+
role: 'timer',
|
|
2774
|
+
'aria-label': t(classicClock.title),
|
|
2775
|
+
},
|
|
2776
|
+
formatDuration(classicClock.seconds),
|
|
2777
|
+
)
|
|
2778
|
+
: h(
|
|
2779
|
+
'h3',
|
|
2780
|
+
{ className: 'owc-classic-state', role: 'status' },
|
|
2781
|
+
t(classicClock.title),
|
|
2782
|
+
),
|
|
2783
|
+
h('p', { className: 'owc-classic-hint' }, t(classicClock.hint)),
|
|
2784
|
+
classicClock.phase === 'setup' &&
|
|
2785
|
+
h(
|
|
2786
|
+
'button',
|
|
2787
|
+
{ type: 'button', className: 'owc-primary', onClick: openEditor },
|
|
2788
|
+
t('classicConfigure'),
|
|
2789
|
+
),
|
|
2790
|
+
classicClock.phase === 'error' &&
|
|
2791
|
+
h(
|
|
2792
|
+
'button',
|
|
2793
|
+
{
|
|
2794
|
+
type: 'button',
|
|
2795
|
+
disabled: hostBusy || !sessionId,
|
|
2796
|
+
onClick: () => reload(true),
|
|
2797
|
+
},
|
|
2798
|
+
iconSvg('refresh', 12),
|
|
2799
|
+
t('retry'),
|
|
2800
|
+
),
|
|
2801
|
+
valid &&
|
|
2802
|
+
h(
|
|
2803
|
+
'div',
|
|
2804
|
+
{ className: 'owc-classic-schedule' },
|
|
2805
|
+
h(
|
|
2806
|
+
'span',
|
|
2807
|
+
null,
|
|
2808
|
+
settings.start + ' — ' + settings.end + (safeState.overnight ? ' +1' : ''),
|
|
2809
|
+
),
|
|
2810
|
+
h('span', null, t('classicEnd') + ' ' + settings.end),
|
|
2811
|
+
),
|
|
2812
|
+
),
|
|
2813
|
+
h(
|
|
2814
|
+
'div',
|
|
2815
|
+
{ className: 'owc-classic-timers' },
|
|
2816
|
+
tile(
|
|
2817
|
+
'water',
|
|
2818
|
+
'water-countdown',
|
|
2819
|
+
'waterCountdown',
|
|
2820
|
+
waterCountdownText(),
|
|
2821
|
+
waterState.phase === 'countdown' ? t('classicWaterHint') : waterSummary(),
|
|
2822
|
+
),
|
|
2823
|
+
tile(
|
|
2824
|
+
'smoking',
|
|
2825
|
+
'smoking',
|
|
2826
|
+
'smokingCountdown',
|
|
2827
|
+
smokingCountdownText(),
|
|
2828
|
+
smokingUnavailable
|
|
2829
|
+
? t(smokingError ? 'timerUnavailable' : 'timerLoading')
|
|
2830
|
+
: t(
|
|
2831
|
+
{
|
|
2832
|
+
idle: 'classicSmokeHint',
|
|
2833
|
+
running: 'classicSmokeHint',
|
|
2834
|
+
paused: 'timerPaused',
|
|
2835
|
+
due: 'timerDue',
|
|
2836
|
+
}[smokingState.phase],
|
|
2837
|
+
),
|
|
2838
|
+
),
|
|
2839
|
+
),
|
|
2840
|
+
h(
|
|
2841
|
+
'div',
|
|
2842
|
+
{ className: 'owc-classic-income' },
|
|
2843
|
+
h(
|
|
2844
|
+
'button',
|
|
2845
|
+
{
|
|
2846
|
+
type: 'button',
|
|
2847
|
+
className: 'owc-classic-income-link',
|
|
2848
|
+
disabled: !valid,
|
|
2849
|
+
onClick: (event) => navigateScreen('earnings', event),
|
|
2850
|
+
},
|
|
2851
|
+
h('span', null, t(showIncome ? 'classicIncome' : 'classicIncomeEntry')),
|
|
2852
|
+
showIncome &&
|
|
2853
|
+
h('strong', { 'aria-label': view.masked ? t('masked') : moneyText }, moneyText),
|
|
2854
|
+
h('span', { 'aria-hidden': true }, '›'),
|
|
2855
|
+
),
|
|
2856
|
+
showIncome && salaryToggle(),
|
|
2857
|
+
),
|
|
2858
|
+
!collapsed &&
|
|
2859
|
+
h(
|
|
2860
|
+
'div',
|
|
2861
|
+
{ className: 'owc-classic-details' },
|
|
2862
|
+
valid
|
|
2863
|
+
? h(
|
|
2864
|
+
React.Fragment,
|
|
2865
|
+
null,
|
|
2866
|
+
h(
|
|
2867
|
+
'div',
|
|
2868
|
+
{ className: 'owc-classic-detail-title' },
|
|
2869
|
+
t('classicShift'),
|
|
2870
|
+
h('span', null, Math.floor(safeState.progress * 100) + '%'),
|
|
2871
|
+
),
|
|
2872
|
+
h(
|
|
2873
|
+
'div',
|
|
2874
|
+
{
|
|
2875
|
+
className: 'owc-progress',
|
|
2876
|
+
role: 'progressbar',
|
|
2877
|
+
'aria-label': t('progress'),
|
|
2878
|
+
'aria-valuenow': Math.floor(safeState.progress * 100),
|
|
2879
|
+
'aria-valuemin': 0,
|
|
2880
|
+
'aria-valuemax': 100,
|
|
2881
|
+
},
|
|
2882
|
+
h('div', { style: { width: safeState.progress * 100 + '%' } }),
|
|
2883
|
+
),
|
|
2884
|
+
h(
|
|
2885
|
+
'p',
|
|
2886
|
+
null,
|
|
2887
|
+
t('shiftDate') + ' ' + safeState.shiftDate + ' · ' + settings.timezone,
|
|
2888
|
+
),
|
|
2889
|
+
h(
|
|
2890
|
+
'p',
|
|
2891
|
+
null,
|
|
2892
|
+
settings.breakStart === settings.breakEnd
|
|
2893
|
+
? t('noBreak')
|
|
2894
|
+
: t('lunch') + ' ' + settings.breakStart + '–' + settings.breakEnd,
|
|
2895
|
+
),
|
|
2896
|
+
h('button', { type: 'button', onClick: openEditor }, t('petWorkSettings')),
|
|
2897
|
+
)
|
|
2898
|
+
: h('p', null, t(classicClock.hint)),
|
|
2899
|
+
),
|
|
2900
|
+
waterNotice &&
|
|
2901
|
+
h('p', { className: 'owc-classic-feedback', role: 'status' }, t(waterNotice.key)),
|
|
2902
|
+
);
|
|
2903
|
+
};
|
|
2904
|
+
|
|
1900
2905
|
const strip = () =>
|
|
1901
2906
|
h(
|
|
1902
2907
|
'div',
|
|
@@ -2133,14 +3138,26 @@ window.__ModuleLoader__.load({
|
|
|
2133
3138
|
type: 'button',
|
|
2134
3139
|
className: 'owc-text',
|
|
2135
3140
|
onClick: () => reload(true),
|
|
2136
|
-
disabled:
|
|
3141
|
+
disabled: hostBusy || !sessionId,
|
|
2137
3142
|
},
|
|
2138
3143
|
iconSvg('refresh', 12),
|
|
2139
3144
|
t('retry'),
|
|
2140
3145
|
),
|
|
2141
3146
|
),
|
|
2142
3147
|
);
|
|
2143
|
-
const
|
|
3148
|
+
const clockSeconds = ['done', 'rest'].includes(safeState.status) ? null : seconds;
|
|
3149
|
+
const clockTitle =
|
|
3150
|
+
safeState.status === 'before'
|
|
3151
|
+
? 'before'
|
|
3152
|
+
: ['working', 'lunch'].includes(safeState.status)
|
|
3153
|
+
? 'working'
|
|
3154
|
+
: safeState.status;
|
|
3155
|
+
const status =
|
|
3156
|
+
clockSeconds === null
|
|
3157
|
+
? ''
|
|
3158
|
+
: safeState.status === 'lunch'
|
|
3159
|
+
? t('lunch') + ' · ' + t('working')
|
|
3160
|
+
: t(clockTitle);
|
|
2144
3161
|
return h(
|
|
2145
3162
|
'div',
|
|
2146
3163
|
{ className: 'owc-main' },
|
|
@@ -2150,10 +3167,15 @@ window.__ModuleLoader__.load({
|
|
|
2150
3167
|
section !== 'earnings' &&
|
|
2151
3168
|
h(
|
|
2152
3169
|
'div',
|
|
2153
|
-
{
|
|
2154
|
-
|
|
3170
|
+
{
|
|
3171
|
+
className: 'owc-time' + (clockSeconds === null ? ' owc-detail-state' : ''),
|
|
3172
|
+
role: clockSeconds === null ? 'status' : 'timer',
|
|
3173
|
+
'aria-label': t(clockTitle),
|
|
3174
|
+
},
|
|
3175
|
+
clockSeconds === null ? t(clockTitle) : formatDuration(clockSeconds),
|
|
2155
3176
|
),
|
|
2156
3177
|
section !== 'earnings' &&
|
|
3178
|
+
['working', 'lunch', 'done'].includes(safeState.status) &&
|
|
2157
3179
|
h(
|
|
2158
3180
|
'div',
|
|
2159
3181
|
{
|
|
@@ -2197,24 +3219,29 @@ window.__ModuleLoader__.load({
|
|
|
2197
3219
|
),
|
|
2198
3220
|
),
|
|
2199
3221
|
h(
|
|
2200
|
-
'
|
|
2201
|
-
{ className: 'owc-
|
|
2202
|
-
h(
|
|
2203
|
-
'span',
|
|
2204
|
-
null,
|
|
2205
|
-
settings.start +
|
|
2206
|
-
' — ' +
|
|
2207
|
-
settings.end +
|
|
2208
|
-
(settings.end < settings.start ? ' +1' : '') +
|
|
2209
|
-
' · ' +
|
|
2210
|
-
(settings.breakStart === settings.breakEnd
|
|
2211
|
-
? t('noBreak')
|
|
2212
|
-
: settings.breakStart + '–' + settings.breakEnd),
|
|
2213
|
-
),
|
|
3222
|
+
'details',
|
|
3223
|
+
{ className: 'owc-detail-more' },
|
|
3224
|
+
h('summary', null, t('detailShift')),
|
|
2214
3225
|
h(
|
|
2215
|
-
'
|
|
2216
|
-
|
|
2217
|
-
|
|
3226
|
+
'div',
|
|
3227
|
+
{ className: 'owc-meta' },
|
|
3228
|
+
h(
|
|
3229
|
+
'span',
|
|
3230
|
+
null,
|
|
3231
|
+
settings.start +
|
|
3232
|
+
' — ' +
|
|
3233
|
+
settings.end +
|
|
3234
|
+
(settings.end < settings.start ? ' +1' : '') +
|
|
3235
|
+
' · ' +
|
|
3236
|
+
(settings.breakStart === settings.breakEnd
|
|
3237
|
+
? t('noBreak')
|
|
3238
|
+
: settings.breakStart + '–' + settings.breakEnd),
|
|
3239
|
+
),
|
|
3240
|
+
h(
|
|
3241
|
+
'span',
|
|
3242
|
+
null,
|
|
3243
|
+
t('shiftDate') + ' ' + safeState.shiftDate + ' · ' + settings.timezone,
|
|
3244
|
+
),
|
|
2218
3245
|
),
|
|
2219
3246
|
),
|
|
2220
3247
|
h(
|
|
@@ -2226,7 +3253,7 @@ window.__ModuleLoader__.load({
|
|
|
2226
3253
|
type: 'button',
|
|
2227
3254
|
className: 'owc-text',
|
|
2228
3255
|
onClick: () => reload(true),
|
|
2229
|
-
disabled:
|
|
3256
|
+
disabled: hostBusy,
|
|
2230
3257
|
},
|
|
2231
3258
|
iconSvg('refresh', 12),
|
|
2232
3259
|
t('refresh'),
|
|
@@ -2307,7 +3334,6 @@ window.__ModuleLoader__.load({
|
|
|
2307
3334
|
onChange: (event) => updateAppearance({ petSize: event.target.valueAsNumber }),
|
|
2308
3335
|
}),
|
|
2309
3336
|
),
|
|
2310
|
-
h('p', { className: 'owc-hint' }, t('petAppearanceHint')),
|
|
2311
3337
|
label(
|
|
2312
3338
|
t('opacity'),
|
|
2313
3339
|
h(
|
|
@@ -2327,32 +3353,108 @@ window.__ModuleLoader__.load({
|
|
|
2327
3353
|
}),
|
|
2328
3354
|
h(
|
|
2329
3355
|
'span',
|
|
2330
|
-
{ className: 'owc-
|
|
2331
|
-
Math.round(view.opacity * 100) + '%
|
|
3356
|
+
{ className: 'owc-range-value', title: t('opacityHint') },
|
|
3357
|
+
Math.round(view.opacity * 100) + '%',
|
|
3358
|
+
),
|
|
3359
|
+
),
|
|
3360
|
+
),
|
|
3361
|
+
);
|
|
3362
|
+
const previewActions = () => {
|
|
3363
|
+
if (!view.companion || petModel(view.petModel).renderer !== 'video') return null;
|
|
3364
|
+
const labels = {
|
|
3365
|
+
idle: 'petPreviewIdle',
|
|
3366
|
+
greet: 'petPreviewGreet',
|
|
3367
|
+
celebrate: 'petPreviewDrink',
|
|
3368
|
+
};
|
|
3369
|
+
const names = { idle: 'petTryIdle', greet: 'petTryGreet', celebrate: 'petTryDrink' };
|
|
3370
|
+
const clips = PET_CLIPS.models[view.petModel] || {};
|
|
3371
|
+
const blocked =
|
|
3372
|
+
!view.companionMotion || previewReduced || previewState.phase === 'unsupported';
|
|
3373
|
+
const message = previewReduced
|
|
3374
|
+
? t('petPreviewReduced')
|
|
3375
|
+
: !view.companionMotion
|
|
3376
|
+
? t('petPreviewEnable')
|
|
3377
|
+
: previewState.phase === 'loading'
|
|
3378
|
+
? t('petPreviewLoading')
|
|
3379
|
+
: previewState.phase === 'playing'
|
|
3380
|
+
? t('petPreviewPlaying') + ' · ' + t(labels[previewState.action])
|
|
3381
|
+
: previewState.phase === 'unsupported'
|
|
3382
|
+
? t('petPreviewUnsupported')
|
|
3383
|
+
: previewState.phase === 'unavailable'
|
|
3384
|
+
? t('petPreviewUnavailable')
|
|
3385
|
+
: t('petPreviewHint');
|
|
3386
|
+
return h(
|
|
3387
|
+
'div',
|
|
3388
|
+
{ className: 'owc-preview-tools' },
|
|
3389
|
+
h(
|
|
3390
|
+
'div',
|
|
3391
|
+
{
|
|
3392
|
+
className: 'owc-preview-actions',
|
|
3393
|
+
role: 'group',
|
|
3394
|
+
'aria-label': t('petPreviewActions'),
|
|
3395
|
+
},
|
|
3396
|
+
...Object.keys(labels)
|
|
3397
|
+
.filter((action) => Object.hasOwn(clips, action))
|
|
3398
|
+
.map((action) =>
|
|
3399
|
+
h(
|
|
3400
|
+
'button',
|
|
3401
|
+
{
|
|
3402
|
+
type: 'button',
|
|
3403
|
+
key: action,
|
|
3404
|
+
'aria-label': t(names[action]),
|
|
3405
|
+
'aria-pressed':
|
|
3406
|
+
previewState.action === action &&
|
|
3407
|
+
['loading', 'playing'].includes(previewState.phase),
|
|
3408
|
+
disabled: blocked,
|
|
3409
|
+
onClick: () => previewMotion.current?.retry?.(action),
|
|
3410
|
+
},
|
|
3411
|
+
t(labels[action]),
|
|
3412
|
+
),
|
|
2332
3413
|
),
|
|
3414
|
+
h(
|
|
3415
|
+
'button',
|
|
3416
|
+
{
|
|
3417
|
+
type: 'button',
|
|
3418
|
+
'aria-label': t('petStopPreview'),
|
|
3419
|
+
disabled: !['loading', 'playing'].includes(previewState.phase),
|
|
3420
|
+
onClick: () => previewMotion.current?.stop?.(),
|
|
3421
|
+
},
|
|
3422
|
+
t('petPreviewStop'),
|
|
2333
3423
|
),
|
|
2334
3424
|
),
|
|
3425
|
+
h(
|
|
3426
|
+
'p',
|
|
3427
|
+
{ className: 'owc-hint owc-preview-status', role: 'status', 'aria-atomic': true },
|
|
3428
|
+
message,
|
|
3429
|
+
),
|
|
2335
3430
|
);
|
|
3431
|
+
};
|
|
2336
3432
|
const editor = () => {
|
|
2337
3433
|
if (appearanceOnly)
|
|
2338
3434
|
return h(
|
|
2339
3435
|
'div',
|
|
2340
3436
|
{ className: 'owc-form owc-appearance', 'data-appearance-settings': true },
|
|
2341
|
-
h('p', { className: 'owc-
|
|
3437
|
+
h('p', { className: 'owc-sr-only' }, t('petAppearancePreview')),
|
|
2342
3438
|
h(
|
|
2343
3439
|
'div',
|
|
2344
|
-
{
|
|
3440
|
+
{
|
|
3441
|
+
ref: previewBox,
|
|
3442
|
+
'data-appearance-preview': true,
|
|
3443
|
+
className: 'owc-appearance-preview',
|
|
3444
|
+
},
|
|
2345
3445
|
view.companion
|
|
2346
3446
|
? petArtwork(
|
|
2347
3447
|
h,
|
|
2348
|
-
|
|
2349
|
-
view.petSize * (petModel(view.petModel).renderer === '
|
|
3448
|
+
{ pose: 'calm', animate: false },
|
|
3449
|
+
view.petSize * (petModel(view.petModel).renderer === 'svg' ? 1 : 0.65),
|
|
2350
3450
|
'',
|
|
2351
3451
|
view.petModel,
|
|
2352
3452
|
PET_SPRITES,
|
|
3453
|
+
petModel(view.petModel).renderer === 'video',
|
|
2353
3454
|
)
|
|
2354
3455
|
: h('p', null, t('petClassicPreview')),
|
|
2355
3456
|
),
|
|
3457
|
+
previewActions(),
|
|
2356
3458
|
appearanceFields(),
|
|
2357
3459
|
h(
|
|
2358
3460
|
'div',
|
|
@@ -2360,8 +3462,13 @@ window.__ModuleLoader__.load({
|
|
|
2360
3462
|
h('button', { type: 'button', onClick: closeEditor }, t('cancel')),
|
|
2361
3463
|
h(
|
|
2362
3464
|
'button',
|
|
2363
|
-
{
|
|
2364
|
-
|
|
3465
|
+
{
|
|
3466
|
+
type: 'button',
|
|
3467
|
+
className: 'owc-primary',
|
|
3468
|
+
'aria-label': t('petApplyAppearance'),
|
|
3469
|
+
onClick: applyAppearance,
|
|
3470
|
+
},
|
|
3471
|
+
t('detailApply'),
|
|
2365
3472
|
),
|
|
2366
3473
|
),
|
|
2367
3474
|
);
|
|
@@ -2369,21 +3476,7 @@ window.__ModuleLoader__.load({
|
|
|
2369
3476
|
const payDaily = workingDraft.payMode === 'daily';
|
|
2370
3477
|
const salaryKey = payDaily ? 'dailySalary' : 'monthlySalary';
|
|
2371
3478
|
const salaryControl = view.masked
|
|
2372
|
-
? h(
|
|
2373
|
-
'div',
|
|
2374
|
-
{ className: 'owc-private' },
|
|
2375
|
-
h('span', null, '••••'),
|
|
2376
|
-
h(
|
|
2377
|
-
'button',
|
|
2378
|
-
{
|
|
2379
|
-
type: 'button',
|
|
2380
|
-
className: 'owc-text',
|
|
2381
|
-
'aria-label': t('show'),
|
|
2382
|
-
onClick: () => setPreference({ masked: false }),
|
|
2383
|
-
},
|
|
2384
|
-
t('show'),
|
|
2385
|
-
),
|
|
2386
|
-
)
|
|
3479
|
+
? h('div', { className: 'owc-private', 'aria-label': t('masked') }, '••••')
|
|
2387
3480
|
: numberInput(salaryKey);
|
|
2388
3481
|
const breakOn = workingDraft.breakStart !== workingDraft.breakEnd;
|
|
2389
3482
|
const commonZones = [
|
|
@@ -2408,6 +3501,20 @@ window.__ModuleLoader__.load({
|
|
|
2408
3501
|
const zones = [
|
|
2409
3502
|
...new Set([workingDraft.timezone, ...commonZones, ...allZones].filter(Boolean)),
|
|
2410
3503
|
];
|
|
3504
|
+
const timezoneField = () =>
|
|
3505
|
+
label(
|
|
3506
|
+
t('timezone'),
|
|
3507
|
+
h(
|
|
3508
|
+
'select',
|
|
3509
|
+
{
|
|
3510
|
+
name: 'timezone',
|
|
3511
|
+
value: workingDraft.timezone ?? '',
|
|
3512
|
+
disabled: busy,
|
|
3513
|
+
onChange: (e) => update('timezone', e.target.value),
|
|
3514
|
+
},
|
|
3515
|
+
zones.map((zone) => h('option', { key: zone, value: zone }, zone)),
|
|
3516
|
+
),
|
|
3517
|
+
);
|
|
2411
3518
|
return h(
|
|
2412
3519
|
'form',
|
|
2413
3520
|
{
|
|
@@ -2424,7 +3531,22 @@ window.__ModuleLoader__.load({
|
|
|
2424
3531
|
h(
|
|
2425
3532
|
'fieldset',
|
|
2426
3533
|
{ className: 'owc-setting-group' },
|
|
2427
|
-
h(
|
|
3534
|
+
h(
|
|
3535
|
+
'legend',
|
|
3536
|
+
{ className: 'owc-salary-legend' },
|
|
3537
|
+
h('span', null, t('salaryGroup')),
|
|
3538
|
+
h(
|
|
3539
|
+
'button',
|
|
3540
|
+
{
|
|
3541
|
+
type: 'button',
|
|
3542
|
+
className: 'owc-icon',
|
|
3543
|
+
'aria-label': t(view.masked ? 'show' : 'hide'),
|
|
3544
|
+
title: t(view.masked ? 'show' : 'hide'),
|
|
3545
|
+
onClick: () => setPreference({ masked: !view.masked }),
|
|
3546
|
+
},
|
|
3547
|
+
iconSvg(view.masked ? 'eyeOff' : 'eye', 13),
|
|
3548
|
+
),
|
|
3549
|
+
),
|
|
2428
3550
|
label(
|
|
2429
3551
|
t('payMode'),
|
|
2430
3552
|
h(
|
|
@@ -2518,22 +3640,10 @@ window.__ModuleLoader__.load({
|
|
|
2518
3640
|
),
|
|
2519
3641
|
),
|
|
2520
3642
|
),
|
|
2521
|
-
label(
|
|
2522
|
-
t('timezone'),
|
|
2523
|
-
h(
|
|
2524
|
-
'select',
|
|
2525
|
-
{
|
|
2526
|
-
name: 'timezone',
|
|
2527
|
-
value: workingDraft.timezone ?? '',
|
|
2528
|
-
disabled: busy,
|
|
2529
|
-
onChange: (e) => update('timezone', e.target.value),
|
|
2530
|
-
},
|
|
2531
|
-
zones.map((zone) => h('option', { key: zone, value: zone }, zone)),
|
|
2532
|
-
),
|
|
2533
|
-
),
|
|
2534
3643
|
),
|
|
2535
3644
|
preview &&
|
|
2536
3645
|
!preview.error &&
|
|
3646
|
+
!view.masked &&
|
|
2537
3647
|
h(
|
|
2538
3648
|
'div',
|
|
2539
3649
|
{ className: 'owc-preview' },
|
|
@@ -2557,6 +3667,7 @@ window.__ModuleLoader__.load({
|
|
|
2557
3667
|
'details',
|
|
2558
3668
|
{ className: 'owc-advanced' },
|
|
2559
3669
|
h('summary', null, t('advanced')),
|
|
3670
|
+
timezoneField(),
|
|
2560
3671
|
h('p', { className: 'owc-hint' }, t('sessionHint')),
|
|
2561
3672
|
label(t('session'), sessionSelect()),
|
|
2562
3673
|
),
|
|
@@ -2579,12 +3690,18 @@ window.__ModuleLoader__.load({
|
|
|
2579
3690
|
);
|
|
2580
3691
|
};
|
|
2581
3692
|
|
|
2582
|
-
const notices = () =>
|
|
3693
|
+
const notices = (showBusinessError = true) =>
|
|
2583
3694
|
h(
|
|
2584
3695
|
React.Fragment,
|
|
2585
3696
|
null,
|
|
2586
|
-
|
|
2587
|
-
|
|
3697
|
+
showBusinessError &&
|
|
3698
|
+
!LOCAL_SCREENS.includes(ui.screen) &&
|
|
3699
|
+
error &&
|
|
3700
|
+
h('p', { className: 'owc-error', role: 'alert' }, error),
|
|
3701
|
+
showBusinessError &&
|
|
3702
|
+
!LOCAL_SCREENS.includes(ui.screen) &&
|
|
3703
|
+
state?.error &&
|
|
3704
|
+
h('p', { className: 'owc-error', role: 'alert' }, state.error),
|
|
2588
3705
|
notice && h('p', { className: 'owc-note', role: 'status' }, notice),
|
|
2589
3706
|
storageError && h('p', { className: 'owc-note' }, t('storage')),
|
|
2590
3707
|
);
|
|
@@ -2592,15 +3709,13 @@ window.__ModuleLoader__.load({
|
|
|
2592
3709
|
function closePetPanel() {
|
|
2593
3710
|
navigateScreen(null);
|
|
2594
3711
|
}
|
|
2595
|
-
function
|
|
3712
|
+
function interactWithPet(event) {
|
|
2596
3713
|
if (petDragged.current && event.detail !== 0) {
|
|
2597
3714
|
petDragged.current = false;
|
|
2598
3715
|
return;
|
|
2599
3716
|
}
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
event,
|
|
2603
|
-
);
|
|
3717
|
+
petDragged.current = false;
|
|
3718
|
+
if (!ui.pending) petMotion.current?.play('greet');
|
|
2604
3719
|
}
|
|
2605
3720
|
function closePetMenu(restore = true) {
|
|
2606
3721
|
dispatchUI({ type: 'dismiss-menu' });
|
|
@@ -2624,8 +3739,15 @@ window.__ModuleLoader__.load({
|
|
|
2624
3739
|
waterPrompt ? t('waterDue') : waterNotice ? t(waterNotice.key) : '',
|
|
2625
3740
|
),
|
|
2626
3741
|
);
|
|
3742
|
+
React.useLayoutEffect(() => {
|
|
3743
|
+
if (!ready) return;
|
|
3744
|
+
const scroll = petMode
|
|
3745
|
+
? petPanel.current?.querySelector('.owc-pet-scroll')
|
|
3746
|
+
: box.current?.querySelector('.owc-content');
|
|
3747
|
+
if (scroll) scroll.scrollTop = 0;
|
|
3748
|
+
}, [ready, petMode, ui.screen]);
|
|
2627
3749
|
const petShowing = petOpen;
|
|
2628
|
-
const petFloating = petShowing ||
|
|
3750
|
+
const petFloating = petShowing || autoWaterPrompt;
|
|
2629
3751
|
React.useLayoutEffect(() => {
|
|
2630
3752
|
if (!petMode || !petFloating || !cardContent.current) return;
|
|
2631
3753
|
const content = cardContent.current,
|
|
@@ -2729,7 +3851,13 @@ window.__ModuleLoader__.load({
|
|
|
2729
3851
|
return () => document.removeEventListener('pointerdown', outside);
|
|
2730
3852
|
}, [petMode, petShowing, ui.menu, editing, waterEditing, waterBusy]);
|
|
2731
3853
|
React.useEffect(() => {
|
|
2732
|
-
if (ui.menu)
|
|
3854
|
+
if (!ui.menu) return;
|
|
3855
|
+
const waterEntry = menuTrigger.current?.classList.contains('owc-pet-water-alert')
|
|
3856
|
+
? menuBox.current?.querySelector('[data-pet-menu-screen="home"]:not(:disabled)')
|
|
3857
|
+
: null;
|
|
3858
|
+
(
|
|
3859
|
+
waterEntry || menuBox.current?.querySelector('[role=menuitem]:not(:disabled)')
|
|
3860
|
+
)?.focus();
|
|
2733
3861
|
}, [ui.menu]);
|
|
2734
3862
|
React.useEffect(() => {
|
|
2735
3863
|
if (!petMode || editing || waterEditing || ui.menu) return;
|
|
@@ -2745,6 +3873,32 @@ window.__ModuleLoader__.load({
|
|
|
2745
3873
|
if (ui.pending && (busy || waterBusy))
|
|
2746
3874
|
box.current?.querySelector('[data-nav-confirm]')?.focus();
|
|
2747
3875
|
}, [ui.pending, busy, waterBusy]);
|
|
3876
|
+
const classicNavigation = React.useRef({ screen: ui.screen, pending: !!ui.pending });
|
|
3877
|
+
React.useEffect(() => {
|
|
3878
|
+
const previous = classicNavigation.current;
|
|
3879
|
+
classicNavigation.current = { screen: ui.screen, pending: !!ui.pending };
|
|
3880
|
+
if (
|
|
3881
|
+
petMode ||
|
|
3882
|
+
!ready ||
|
|
3883
|
+
(previous.screen === ui.screen && previous.pending === !!ui.pending)
|
|
3884
|
+
)
|
|
3885
|
+
return;
|
|
3886
|
+
const frame = requestAnimationFrame(() => {
|
|
3887
|
+
if (ui.pending) box.current?.querySelector('[data-keep-editing]')?.focus();
|
|
3888
|
+
else if (!editing && !waterEditing) {
|
|
3889
|
+
const kind = focusReturn.current?.getAttribute?.('data-classic-timer');
|
|
3890
|
+
const target =
|
|
3891
|
+
!ui.screen && ['water', 'smoking'].includes(kind)
|
|
3892
|
+
? box.current?.querySelector(`[data-classic-timer="${kind}"]`)
|
|
3893
|
+
: box.current?.querySelector(
|
|
3894
|
+
'.owc-classic-head .owc-title,.owc-classic-mini .owc-grip',
|
|
3895
|
+
);
|
|
3896
|
+
target?.focus();
|
|
3897
|
+
} else if (previous.pending)
|
|
3898
|
+
box.current?.querySelector('form input,form select,form button')?.focus();
|
|
3899
|
+
});
|
|
3900
|
+
return () => cancelAnimationFrame(frame);
|
|
3901
|
+
}, [petMode, ready, ui.screen, !!ui.pending]);
|
|
2748
3902
|
const draftGuard = () =>
|
|
2749
3903
|
h(
|
|
2750
3904
|
'div',
|
|
@@ -2806,6 +3960,8 @@ window.__ModuleLoader__.load({
|
|
|
2806
3960
|
);
|
|
2807
3961
|
const petPageTitle = {
|
|
2808
3962
|
home: 'petWaterCard',
|
|
3963
|
+
'water-countdown': 'waterCountdown',
|
|
3964
|
+
smoking: 'smokingCountdown',
|
|
2809
3965
|
income: 'petCountdownCard',
|
|
2810
3966
|
earnings: 'petEarningsCard',
|
|
2811
3967
|
water: 'petWaterSettings',
|
|
@@ -2832,9 +3988,22 @@ window.__ModuleLoader__.load({
|
|
|
2832
3988
|
),
|
|
2833
3989
|
h(
|
|
2834
3990
|
'strong',
|
|
2835
|
-
{ 'data-pet-page-title': true, tabIndex: -1, style: { flex: 1
|
|
3991
|
+
{ 'data-pet-page-title': true, tabIndex: -1, style: { flex: 1 } },
|
|
2836
3992
|
t(petPageTitle[ui.screen] || 'waterTitle'),
|
|
2837
3993
|
),
|
|
3994
|
+
['home', 'water-countdown'].includes(ui.screen) &&
|
|
3995
|
+
h(
|
|
3996
|
+
'button',
|
|
3997
|
+
{
|
|
3998
|
+
type: 'button',
|
|
3999
|
+
className: 'owc-icon',
|
|
4000
|
+
'aria-label': t('petWaterSettings'),
|
|
4001
|
+
title: t('petWaterSettings'),
|
|
4002
|
+
disabled: busy || waterBusy || !!ui.pending,
|
|
4003
|
+
onClick: openWaterEditor,
|
|
4004
|
+
},
|
|
4005
|
+
iconSvg('sliders', 14),
|
|
4006
|
+
),
|
|
2838
4007
|
h(
|
|
2839
4008
|
'button',
|
|
2840
4009
|
{
|
|
@@ -2850,102 +4019,91 @@ window.__ModuleLoader__.load({
|
|
|
2850
4019
|
const petHome = () =>
|
|
2851
4020
|
h(
|
|
2852
4021
|
'div',
|
|
2853
|
-
{
|
|
2854
|
-
|
|
4022
|
+
{
|
|
4023
|
+
className: 'owc-pet-home' + (waterPrompt ? ' owc-water-due' : ''),
|
|
4024
|
+
'data-pet-home': true,
|
|
4025
|
+
'data-water-event': waterPrompt ? waterState.eventId : undefined,
|
|
4026
|
+
},
|
|
4027
|
+
waterHero(),
|
|
2855
4028
|
h(
|
|
2856
4029
|
'div',
|
|
2857
|
-
{
|
|
2858
|
-
className: 'owc-pet-water-card' + (waterPrompt ? ' owc-water-due' : ''),
|
|
2859
|
-
'data-water-event': waterPrompt ? waterState.eventId : undefined,
|
|
2860
|
-
},
|
|
4030
|
+
{ className: 'owc-detail-primary-actions' },
|
|
2861
4031
|
h(
|
|
2862
|
-
'
|
|
2863
|
-
{
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
:
|
|
2867
|
-
|
|
2868
|
-
|
|
4032
|
+
'button',
|
|
4033
|
+
{
|
|
4034
|
+
type: 'button',
|
|
4035
|
+
className: 'owc-primary owc-water-record',
|
|
4036
|
+
disabled: !waterCanRecord || waterBusy,
|
|
4037
|
+
onClick: recordWater,
|
|
4038
|
+
},
|
|
4039
|
+
t('petRecord'),
|
|
2869
4040
|
),
|
|
2870
|
-
|
|
2871
|
-
'div',
|
|
2872
|
-
{ className: 'owc-pet-water-row' },
|
|
4041
|
+
waterPrompt &&
|
|
2873
4042
|
h(
|
|
2874
4043
|
'button',
|
|
2875
4044
|
{
|
|
2876
4045
|
type: 'button',
|
|
2877
|
-
className: 'owc-
|
|
2878
|
-
disabled:
|
|
2879
|
-
onClick:
|
|
4046
|
+
className: 'owc-text',
|
|
4047
|
+
disabled: waterBusy,
|
|
4048
|
+
onClick: () => waterAction({ type: 'snooze', eventId: waterState.eventId }),
|
|
2880
4049
|
},
|
|
2881
|
-
t('
|
|
4050
|
+
t('waterSnooze'),
|
|
2882
4051
|
),
|
|
2883
|
-
h(
|
|
2884
|
-
'span',
|
|
2885
|
-
{ 'data-water-count': true },
|
|
2886
|
-
t('waterCount') + ': ' + waterState.count,
|
|
2887
|
-
),
|
|
2888
|
-
),
|
|
2889
4052
|
),
|
|
2890
4053
|
h(
|
|
2891
4054
|
'div',
|
|
2892
|
-
{ className: 'owc-
|
|
2893
|
-
|
|
4055
|
+
{ className: 'owc-detail-secondary' },
|
|
4056
|
+
h(
|
|
4057
|
+
'button',
|
|
4058
|
+
{
|
|
4059
|
+
type: 'button',
|
|
4060
|
+
className: 'owc-text',
|
|
4061
|
+
disabled: !water.enabled || busy || waterBusy || waterError || !waterReady,
|
|
4062
|
+
onClick: () => waterAction({ type: water.paused ? 'resume' : 'pause' }),
|
|
4063
|
+
},
|
|
4064
|
+
t(water.paused ? 'waterResume' : 'waterPause'),
|
|
4065
|
+
),
|
|
4066
|
+
waterState.phase === 'quiet' &&
|
|
2894
4067
|
h(
|
|
2895
4068
|
'button',
|
|
2896
4069
|
{
|
|
2897
4070
|
type: 'button',
|
|
2898
|
-
|
|
2899
|
-
disabled:
|
|
2900
|
-
onClick: () => waterAction({ type: 'quiet'
|
|
4071
|
+
className: 'owc-text',
|
|
4072
|
+
disabled: waterBusy,
|
|
4073
|
+
onClick: () => waterAction({ type: 'end-quiet' }),
|
|
2901
4074
|
},
|
|
2902
|
-
t(
|
|
4075
|
+
t('waterQuietEnd'),
|
|
2903
4076
|
),
|
|
2904
|
-
),
|
|
2905
4077
|
),
|
|
2906
4078
|
h(
|
|
2907
|
-
'
|
|
2908
|
-
{ className: 'owc-
|
|
2909
|
-
|
|
2910
|
-
|
|
4079
|
+
'details',
|
|
4080
|
+
{ className: 'owc-detail-more' },
|
|
4081
|
+
h('summary', null, t('waterQuiet')),
|
|
4082
|
+
h(
|
|
4083
|
+
'div',
|
|
4084
|
+
{ className: 'owc-detail-options' },
|
|
4085
|
+
[30, 60].map((minutes) =>
|
|
4086
|
+
h(
|
|
2911
4087
|
'button',
|
|
2912
4088
|
{
|
|
2913
4089
|
type: 'button',
|
|
2914
|
-
|
|
2915
|
-
|
|
4090
|
+
key: minutes,
|
|
4091
|
+
disabled: !waterCanQuiet || waterBusy,
|
|
4092
|
+
onClick: (event) => {
|
|
4093
|
+
const menu = event.currentTarget.closest('details');
|
|
4094
|
+
menu.open = false;
|
|
4095
|
+
menu.querySelector('summary')?.focus();
|
|
4096
|
+
void waterAction({ type: 'quiet', minutes });
|
|
4097
|
+
},
|
|
2916
4098
|
},
|
|
2917
|
-
t('
|
|
2918
|
-
)
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
? h(
|
|
2923
|
-
React.Fragment,
|
|
2924
|
-
null,
|
|
2925
|
-
t('waterQuiet') +
|
|
2926
|
-
' · ' +
|
|
2927
|
-
formatDuration(waterState.quietRemaining / 1000),
|
|
2928
|
-
h(
|
|
2929
|
-
'button',
|
|
2930
|
-
{
|
|
2931
|
-
type: 'button',
|
|
2932
|
-
disabled: waterBusy,
|
|
2933
|
-
onClick: () => waterAction({ type: 'end-quiet' }),
|
|
2934
|
-
},
|
|
2935
|
-
t('waterQuietEnd'),
|
|
2936
|
-
),
|
|
2937
|
-
)
|
|
2938
|
-
: t('petQuietHint'),
|
|
2939
|
-
),
|
|
2940
|
-
h(
|
|
2941
|
-
'div',
|
|
2942
|
-
{ className: 'owc-pet-feedback' },
|
|
2943
|
-
waterNotice
|
|
2944
|
-
? t(waterNotice.key)
|
|
2945
|
-
: waterError
|
|
2946
|
-
? t('waterUnavailable')
|
|
2947
|
-
: t('petHomeHint'),
|
|
4099
|
+
t(minutes === 30 ? 'petQuiet30' : 'petQuiet60'),
|
|
4100
|
+
),
|
|
4101
|
+
),
|
|
4102
|
+
),
|
|
4103
|
+
h('p', { className: 'owc-hint' }, t('petQuietHint')),
|
|
2948
4104
|
),
|
|
4105
|
+
waterError &&
|
|
4106
|
+
h('p', { className: 'owc-error', role: 'alert' }, t('waterUnavailable')),
|
|
2949
4107
|
waterError &&
|
|
2950
4108
|
h(
|
|
2951
4109
|
'button',
|
|
@@ -2956,26 +4114,6 @@ window.__ModuleLoader__.load({
|
|
|
2956
4114
|
},
|
|
2957
4115
|
t('waterRetry'),
|
|
2958
4116
|
),
|
|
2959
|
-
h(
|
|
2960
|
-
'div',
|
|
2961
|
-
{ className: 'owc-pet-home-links' },
|
|
2962
|
-
h('button', { type: 'button', onClick: openWaterEditor }, t('petWaterSettings')),
|
|
2963
|
-
h(
|
|
2964
|
-
'button',
|
|
2965
|
-
{
|
|
2966
|
-
type: 'button',
|
|
2967
|
-
disabled:
|
|
2968
|
-
!water.enabled ||
|
|
2969
|
-
busy ||
|
|
2970
|
-
waterBusy ||
|
|
2971
|
-
waterError ||
|
|
2972
|
-
!waterContext ||
|
|
2973
|
-
water.shift?.id !== waterContext.id,
|
|
2974
|
-
onClick: () => waterAction({ type: water.shift?.paused ? 'resume' : 'pause' }),
|
|
2975
|
-
},
|
|
2976
|
-
t(water.shift?.paused ? 'waterResume' : 'waterPause'),
|
|
2977
|
-
),
|
|
2978
|
-
),
|
|
2979
4117
|
);
|
|
2980
4118
|
const contextMenu = () =>
|
|
2981
4119
|
ui.menu &&
|
|
@@ -3030,6 +4168,8 @@ window.__ModuleLoader__.load({
|
|
|
3030
4168
|
[
|
|
3031
4169
|
['income', 'petCountdownCard'],
|
|
3032
4170
|
['home', 'petWaterCard'],
|
|
4171
|
+
['water-countdown', 'waterCountdown'],
|
|
4172
|
+
['smoking', 'smokingCountdown'],
|
|
3033
4173
|
['earnings', 'petEarningsCard'],
|
|
3034
4174
|
['appearance', 'petAppearance'],
|
|
3035
4175
|
['schedule', 'petWorkSettings'],
|
|
@@ -3040,14 +4180,34 @@ window.__ModuleLoader__.load({
|
|
|
3040
4180
|
key: screen,
|
|
3041
4181
|
type: 'button',
|
|
3042
4182
|
role: 'menuitem',
|
|
4183
|
+
'data-pet-menu-screen': screen,
|
|
3043
4184
|
tabIndex: -1,
|
|
3044
|
-
disabled:
|
|
4185
|
+
disabled:
|
|
4186
|
+
requestBusy.current ||
|
|
4187
|
+
waterBusy ||
|
|
4188
|
+
(!LOCAL_SCREENS.includes(screen) &&
|
|
4189
|
+
(hostBusy || !data || (!data.configured && screen !== 'schedule'))),
|
|
3045
4190
|
onClick: (event) => navigateScreen(screen, event),
|
|
3046
4191
|
},
|
|
3047
4192
|
t(label),
|
|
3048
4193
|
),
|
|
3049
4194
|
),
|
|
3050
4195
|
|
|
4196
|
+
(!data || error || state?.error) &&
|
|
4197
|
+
h(
|
|
4198
|
+
'button',
|
|
4199
|
+
{
|
|
4200
|
+
type: 'button',
|
|
4201
|
+
role: 'menuitem',
|
|
4202
|
+
tabIndex: -1,
|
|
4203
|
+
disabled: hostBusy || !sessionId,
|
|
4204
|
+
onClick: () => {
|
|
4205
|
+
closePetMenu();
|
|
4206
|
+
void reload(true);
|
|
4207
|
+
},
|
|
4208
|
+
},
|
|
4209
|
+
t('retry'),
|
|
4210
|
+
),
|
|
3051
4211
|
h('hr', null),
|
|
3052
4212
|
h(
|
|
3053
4213
|
'button',
|
|
@@ -3065,15 +4225,23 @@ window.__ModuleLoader__.load({
|
|
|
3065
4225
|
),
|
|
3066
4226
|
);
|
|
3067
4227
|
|
|
4228
|
+
if (!ready) return null;
|
|
3068
4229
|
if (petMode)
|
|
3069
4230
|
return h(
|
|
3070
4231
|
'section',
|
|
3071
4232
|
{
|
|
3072
4233
|
ref: box,
|
|
3073
4234
|
className: 'owc-v2 owc-pet-shell',
|
|
3074
|
-
'data-version': '1.1.
|
|
4235
|
+
'data-version': '1.1.2',
|
|
3075
4236
|
'data-pet-model': stageModel,
|
|
3076
|
-
'data-status':
|
|
4237
|
+
'data-status':
|
|
4238
|
+
error || state?.error
|
|
4239
|
+
? 'error'
|
|
4240
|
+
: data?.configured && safeState
|
|
4241
|
+
? safeState.status
|
|
4242
|
+
: data
|
|
4243
|
+
? 'setup'
|
|
4244
|
+
: 'loading',
|
|
3077
4245
|
'aria-label': t('companionLabel'),
|
|
3078
4246
|
style: {
|
|
3079
4247
|
'--owc-alpha': String(view.opacity),
|
|
@@ -3099,8 +4267,7 @@ window.__ModuleLoader__.load({
|
|
|
3099
4267
|
else if (ui.pending) dispatchUI({ type: 'stay' });
|
|
3100
4268
|
else if (editing || waterEditing) navigateScreen(ui.back);
|
|
3101
4269
|
else if (petShowing) closePetPanel();
|
|
3102
|
-
else if (
|
|
3103
|
-
void waterAction({ type: 'snooze', eventId: waterState.eventId });
|
|
4270
|
+
else if (autoWaterPrompt) dismissWaterPopup();
|
|
3104
4271
|
},
|
|
3105
4272
|
},
|
|
3106
4273
|
h('style', null, STYLE),
|
|
@@ -3113,10 +4280,13 @@ window.__ModuleLoader__.load({
|
|
|
3113
4280
|
{
|
|
3114
4281
|
type: 'button',
|
|
3115
4282
|
className: 'owc-pet-handle',
|
|
3116
|
-
'aria-label': t('
|
|
3117
|
-
'aria-expanded': !!petShowing,
|
|
4283
|
+
'aria-label': t('petGreet'),
|
|
3118
4284
|
title: t('companionName') + ' · ' + t('petHint'),
|
|
3119
|
-
onClick:
|
|
4285
|
+
onClick: interactWithPet,
|
|
4286
|
+
onPointerEnter: (event) => {
|
|
4287
|
+
if (event.pointerType === 'mouse' && !dragState.current)
|
|
4288
|
+
petMotion.current?.play('look');
|
|
4289
|
+
},
|
|
3120
4290
|
onKeyDown: gripKeys,
|
|
3121
4291
|
},
|
|
3122
4292
|
petArtwork(
|
|
@@ -3126,23 +4296,74 @@ window.__ModuleLoader__.load({
|
|
|
3126
4296
|
waterNotice?.token,
|
|
3127
4297
|
stageModel,
|
|
3128
4298
|
PET_SPRITES,
|
|
4299
|
+
true,
|
|
3129
4300
|
),
|
|
3130
4301
|
),
|
|
3131
4302
|
h(
|
|
3132
|
-
'
|
|
4303
|
+
'div',
|
|
3133
4304
|
{
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
'aria-label': t('petClock'),
|
|
4305
|
+
className: 'owc-pet-timers',
|
|
4306
|
+
role: 'group',
|
|
4307
|
+
'aria-label': t('petCountdownGroup'),
|
|
3138
4308
|
},
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
4309
|
+
h(
|
|
4310
|
+
'div',
|
|
4311
|
+
{
|
|
4312
|
+
className: 'owc-pet-clock',
|
|
4313
|
+
role: data?.configured && safeState ? 'timer' : 'status',
|
|
4314
|
+
'aria-label': t('petClock'),
|
|
4315
|
+
title:
|
|
4316
|
+
error || state?.error
|
|
4317
|
+
? t('petRetryHint')
|
|
4318
|
+
: !sessionId
|
|
4319
|
+
? t('noSession')
|
|
4320
|
+
: undefined,
|
|
4321
|
+
},
|
|
4322
|
+
iconSvg('alarm', 12),
|
|
4323
|
+
h(
|
|
4324
|
+
'span',
|
|
4325
|
+
{ className: 'owc-pet-clock-label', title: t(petClockStatus) },
|
|
4326
|
+
t(petClockStatus),
|
|
4327
|
+
),
|
|
4328
|
+
data?.configured &&
|
|
4329
|
+
safeState &&
|
|
4330
|
+
!error &&
|
|
4331
|
+
sessionId &&
|
|
4332
|
+
!['done', 'rest'].includes(safeState.status)
|
|
4333
|
+
? h('strong', null, duration)
|
|
4334
|
+
: null,
|
|
4335
|
+
),
|
|
4336
|
+
h(
|
|
4337
|
+
'div',
|
|
4338
|
+
{ className: 'owc-pet-timer-pair' },
|
|
4339
|
+
timerRow(
|
|
4340
|
+
'water',
|
|
4341
|
+
waterCountdownText(),
|
|
4342
|
+
waterState.phase === 'due',
|
|
4343
|
+
t('waterCountdown') + ' · ' + waterSummary(),
|
|
4344
|
+
),
|
|
4345
|
+
timerRow(
|
|
4346
|
+
'smoking',
|
|
4347
|
+
smokingCountdownText(),
|
|
4348
|
+
smokingState.phase === 'due',
|
|
4349
|
+
t('smokingCountdown') +
|
|
4350
|
+
' · ' +
|
|
4351
|
+
(smokingUnavailable
|
|
4352
|
+
? t('timerUnavailable')
|
|
4353
|
+
: t(
|
|
4354
|
+
{
|
|
4355
|
+
idle: 'timerIdle',
|
|
4356
|
+
running: 'timerRunning',
|
|
4357
|
+
paused: 'timerPaused',
|
|
4358
|
+
due: 'timerDue',
|
|
4359
|
+
}[smokingState.phase],
|
|
4360
|
+
)),
|
|
4361
|
+
),
|
|
4362
|
+
),
|
|
3142
4363
|
),
|
|
3143
4364
|
),
|
|
3144
4365
|
waterPrompt &&
|
|
3145
|
-
|
|
4366
|
+
!autoWaterPrompt &&
|
|
3146
4367
|
ui.screen !== 'home' &&
|
|
3147
4368
|
h(
|
|
3148
4369
|
'button',
|
|
@@ -3151,7 +4372,9 @@ window.__ModuleLoader__.load({
|
|
|
3151
4372
|
className: 'owc-pet-water-alert owc-water-due',
|
|
3152
4373
|
'data-water-event': waterState.eventId,
|
|
3153
4374
|
'aria-label': t('petWaterAlert'),
|
|
3154
|
-
|
|
4375
|
+
title: t('petWaterAlert'),
|
|
4376
|
+
disabled: busy || waterBusy || !!ui.pending,
|
|
4377
|
+
onClick: openWaterPopup,
|
|
3155
4378
|
style: {
|
|
3156
4379
|
left: waterAlertLayout.left,
|
|
3157
4380
|
top: waterAlertLayout.top,
|
|
@@ -3160,7 +4383,8 @@ window.__ModuleLoader__.load({
|
|
|
3160
4383
|
},
|
|
3161
4384
|
},
|
|
3162
4385
|
iconSvg('water', 13),
|
|
3163
|
-
t('petWaterBadge'),
|
|
4386
|
+
h('span', { className: 'owc-pet-water-label' }, t('petWaterBadge')),
|
|
4387
|
+
iconSvg('chevronUp', 10),
|
|
3164
4388
|
),
|
|
3165
4389
|
contextMenu(),
|
|
3166
4390
|
!petShowing &&
|
|
@@ -3185,8 +4409,8 @@ window.__ModuleLoader__.load({
|
|
|
3185
4409
|
'div',
|
|
3186
4410
|
{
|
|
3187
4411
|
ref: petPanel,
|
|
3188
|
-
className: 'owc-pet-panel owc-pet-workspace',
|
|
3189
|
-
'data-pet-panel': petShowing ? 'open' : '
|
|
4412
|
+
className: 'owc-pet-panel owc-pet-workspace owc-detail-page',
|
|
4413
|
+
'data-pet-panel': petShowing ? 'open' : 'reminder',
|
|
3190
4414
|
'data-pet-screen': ui.screen || 'reminder',
|
|
3191
4415
|
'data-side': floatingPanel.side,
|
|
3192
4416
|
role: 'region',
|
|
@@ -3211,10 +4435,10 @@ window.__ModuleLoader__.load({
|
|
|
3211
4435
|
{
|
|
3212
4436
|
type: 'button',
|
|
3213
4437
|
className: 'owc-icon',
|
|
3214
|
-
'aria-label': t('
|
|
4438
|
+
'aria-label': t('petDismissReminder'),
|
|
4439
|
+
title: t('petDismissReminder'),
|
|
3215
4440
|
disabled: waterBusy,
|
|
3216
|
-
onClick:
|
|
3217
|
-
waterAction({ type: 'snooze', eventId: waterState.eventId }),
|
|
4441
|
+
onClick: dismissWaterPopup,
|
|
3218
4442
|
},
|
|
3219
4443
|
iconSvg('close', 14),
|
|
3220
4444
|
),
|
|
@@ -3237,9 +4461,13 @@ window.__ModuleLoader__.load({
|
|
|
3237
4461
|
? editor()
|
|
3238
4462
|
: petDetails
|
|
3239
4463
|
? panelBody('clock')
|
|
3240
|
-
: ui.screen === '
|
|
3241
|
-
?
|
|
3242
|
-
:
|
|
4464
|
+
: ui.screen === 'water-countdown'
|
|
4465
|
+
? waterCountdownCard()
|
|
4466
|
+
: ui.screen === 'smoking'
|
|
4467
|
+
? smokingCountdownCard()
|
|
4468
|
+
: ui.screen === 'earnings'
|
|
4469
|
+
? panelBody('earnings')
|
|
4470
|
+
: petHome(),
|
|
3243
4471
|
notices(),
|
|
3244
4472
|
)
|
|
3245
4473
|
: waterReminder(),
|
|
@@ -3253,13 +4481,14 @@ window.__ModuleLoader__.load({
|
|
|
3253
4481
|
{
|
|
3254
4482
|
ref: box,
|
|
3255
4483
|
className:
|
|
3256
|
-
'owc-v2' +
|
|
4484
|
+
'owc-v2 owc-classic' +
|
|
4485
|
+
(!collapsed ? ' owc-detail-page' : '') +
|
|
3257
4486
|
(collapsed ? ' owc-compact' : '') +
|
|
3258
4487
|
(collapsed && waterQuickOpen ? ' owc-quick' : '') +
|
|
3259
4488
|
(editing || waterEditing ? ' owc-editing' : ''),
|
|
3260
4489
|
'aria-label': t('title'),
|
|
3261
|
-
'data-version': '1.1.
|
|
3262
|
-
'data-status':
|
|
4490
|
+
'data-version': '1.1.2',
|
|
4491
|
+
'data-status': classicClock.phase,
|
|
3263
4492
|
style: cardStyle,
|
|
3264
4493
|
onPointerDown: dragDown,
|
|
3265
4494
|
onPointerMove: dragMove,
|
|
@@ -3278,8 +4507,8 @@ window.__ModuleLoader__.load({
|
|
|
3278
4507
|
return;
|
|
3279
4508
|
event.preventDefault();
|
|
3280
4509
|
if (ui.pending) dispatchUI({ type: 'stay' });
|
|
3281
|
-
else if (
|
|
3282
|
-
|
|
4510
|
+
else if (ui.screen)
|
|
4511
|
+
navigateScreen(PET_EDITOR_SCREENS.includes(ui.screen) ? ui.back : null);
|
|
3283
4512
|
else setPreference({ mini: true });
|
|
3284
4513
|
},
|
|
3285
4514
|
},
|
|
@@ -3301,33 +4530,28 @@ window.__ModuleLoader__.load({
|
|
|
3301
4530
|
waterPrompt ? t('waterDue') : waterNotice ? t(waterNotice.key) : '',
|
|
3302
4531
|
),
|
|
3303
4532
|
),
|
|
3304
|
-
collapsed
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
(
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
: panelBody(),
|
|
3327
|
-
notices(),
|
|
3328
|
-
!editing && !waterEditing && waterOverview(),
|
|
3329
|
-
),
|
|
3330
|
-
!collapsed && waterReminder(),
|
|
4533
|
+
!collapsed && classicHeader(),
|
|
4534
|
+
ui.pending
|
|
4535
|
+
? draftGuard()
|
|
4536
|
+
: waterEditing
|
|
4537
|
+
? waterSettings()
|
|
4538
|
+
: editing
|
|
4539
|
+
? editor()
|
|
4540
|
+
: ui.screen === 'water-countdown'
|
|
4541
|
+
? waterCountdownCard()
|
|
4542
|
+
: ui.screen === 'smoking'
|
|
4543
|
+
? smokingCountdownCard()
|
|
4544
|
+
: ui.screen === 'home'
|
|
4545
|
+
? petHome()
|
|
4546
|
+
: ui.screen === 'earnings'
|
|
4547
|
+
? panelBody('earnings')
|
|
4548
|
+
: ui.screen === 'income'
|
|
4549
|
+
? panelBody('clock')
|
|
4550
|
+
: collapsed
|
|
4551
|
+
? classicMini()
|
|
4552
|
+
: classicOverview(),
|
|
4553
|
+
notices(ui.screen !== null),
|
|
4554
|
+
ui.screen === null && waterReminder(),
|
|
3331
4555
|
),
|
|
3332
4556
|
);
|
|
3333
4557
|
}
|