@eintrek/erp-shell 0.1.77 → 0.1.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/chunks/{format-ILrTMV4G.esm.js → format-BqUpxbzD.esm.js} +244 -39
- package/dist/chunks/{format-ILrTMV4G.esm.js.map → format-BqUpxbzD.esm.js.map} +1 -1
- package/dist/chunks/{format-DQLPHq7h.cjs → format-CFXhGhta.cjs} +244 -39
- package/dist/chunks/{format-DQLPHq7h.cjs.map → format-CFXhGhta.cjs.map} +1 -1
- package/dist/guide/machine.d.ts +15 -0
- package/dist/guide/progress-sync.d.ts +6 -1
- package/dist/guide-core.cjs +1 -1
- package/dist/guide-core.esm.js +1 -1
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +5 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -176,6 +176,30 @@ progress API: `API.md` next to it.
|
|
|
176
176
|
shell's adapters), `guide-ui.tsx`, `guide-styles.ts`. Tests:
|
|
177
177
|
`src/guide/*.test.ts`.
|
|
178
178
|
|
|
179
|
+
### 0.1.79
|
|
180
|
+
|
|
181
|
+
- **The ภารกิจ chip no longer covers dialogs.** The guide layer sits above every
|
|
182
|
+
dialog, and the chip sat at the dialog's bottom right, over its buttons, in
|
|
183
|
+
every app. While an app dialog (Radix dialog or alert dialog) is open, the
|
|
184
|
+
chip is not drawn; it comes back when the dialog closes.
|
|
185
|
+
|
|
186
|
+
### 0.1.78
|
|
187
|
+
|
|
188
|
+
- **Every tab shows the "?" menu.** Each tab of an app reads guides/me and
|
|
189
|
+
publishes "ไกด์เริ่มต้น n/N", "คู่มือหน้านี้", "คู่มือทุกหน้า", "พาทำงาน",
|
|
190
|
+
"อธิบายส่วนนี้" and the SectionGuideButtons. Before, only the tab holding
|
|
191
|
+
the Web Lock did, so with two tabs open "?" went straight to ช่วยเหลือ in
|
|
192
|
+
the other (since 0.1.74 every guide item was lost there).
|
|
193
|
+
- **One tab runs.** Steps, offers, the welcome, celebrations and saves stay
|
|
194
|
+
with the lock holder. Pressing a guide item in another tab makes that tab
|
|
195
|
+
the runner: it steals the lock, reads progress fresh and runs the item;
|
|
196
|
+
the old holder stops what it was running, silently and without writing,
|
|
197
|
+
and waits its turn again. Nothing resumes by itself in a tab taken over.
|
|
198
|
+
- **Progress reaches every tab.** A tab that saves posts "saved" on a
|
|
199
|
+
BroadcastChannel (`erp-guide:<orgId>:<app>`); the others read guides/me
|
|
200
|
+
again (bypassing the 5-minute cache), as does any tab coming back into
|
|
201
|
+
view, so n/N and ดูแล้ว stay right.
|
|
202
|
+
|
|
179
203
|
### 0.1.77
|
|
180
204
|
|
|
181
205
|
Fixes from the apps' first week on 0.1.76.
|
|
@@ -2807,6 +2807,37 @@ function reduceGuide(state, action, suite, env) {
|
|
|
2807
2807
|
d.state = { ...d.state, pages: { ...d.state.pages, [action.key]: action.row } };
|
|
2808
2808
|
break;
|
|
2809
2809
|
}
|
|
2810
|
+
case "yield": {
|
|
2811
|
+
const run = d.state.pageRun;
|
|
2812
|
+
if (run?.kind === "flow" && run.context)
|
|
2813
|
+
withProgress(d, { context: run.context });
|
|
2814
|
+
d.state = { ...d.state, pageRun: null, replayPage: null, panelOpen: false, justDone: null, pendingCelebrate: [] };
|
|
2815
|
+
setScreen(d, { name: "idle" });
|
|
2816
|
+
break;
|
|
2817
|
+
}
|
|
2818
|
+
case "refresh": {
|
|
2819
|
+
const p = action.progress;
|
|
2820
|
+
const local = d.state.progress;
|
|
2821
|
+
// The server's row when it is this version and not behind this tab's.
|
|
2822
|
+
if (p.guide_version === local.guide_version && p.rev >= local.rev)
|
|
2823
|
+
d.state = { ...d.state, progress: p };
|
|
2824
|
+
const newer = (server, mine) => {
|
|
2825
|
+
const out = { ...(mine ?? {}) };
|
|
2826
|
+
for (const [k, r] of Object.entries(server ?? {})) {
|
|
2827
|
+
const m = out[k];
|
|
2828
|
+
if (!m || r.version > m.version || (r.version === m.version && r.rev >= m.rev))
|
|
2829
|
+
out[k] = r;
|
|
2830
|
+
}
|
|
2831
|
+
return out;
|
|
2832
|
+
};
|
|
2833
|
+
d.state = {
|
|
2834
|
+
...d.state,
|
|
2835
|
+
pages: newer(action.pages, d.state.pages),
|
|
2836
|
+
sections: newer(action.sections, d.state.sections),
|
|
2837
|
+
flows: newer(action.flows, d.state.flows),
|
|
2838
|
+
};
|
|
2839
|
+
break;
|
|
2840
|
+
}
|
|
2810
2841
|
case "startSection": {
|
|
2811
2842
|
// Never offered: only on request. Never over a mission step, a
|
|
2812
2843
|
// welcome, a gate or a celebration; only on screen, only theirs.
|
|
@@ -3176,9 +3207,9 @@ function conflictOf(err) {
|
|
|
3176
3207
|
return null;
|
|
3177
3208
|
}
|
|
3178
3209
|
/** The GET, parsed for this suite; null (show nothing) on any failure or odd answer. */
|
|
3179
|
-
async function loadGuides(adapter, suite, signal) {
|
|
3210
|
+
async function loadGuides(adapter, suite, signal, fresh = false) {
|
|
3180
3211
|
try {
|
|
3181
|
-
const raw = await adapter.fetchProgress({ app: suite.app, guideKey: suite.key, signal });
|
|
3212
|
+
const raw = await adapter.fetchProgress({ app: suite.app, guideKey: suite.key, signal, ...(fresh ? { fresh: true } : {}) });
|
|
3182
3213
|
return parseGuidesAnswer(raw, suite);
|
|
3183
3214
|
}
|
|
3184
3215
|
catch {
|
|
@@ -4377,36 +4408,81 @@ const flowMirrorKey = (orgId, suite) => `erp-guide-flow-run:${orgId}:${suite.app
|
|
|
4377
4408
|
/* One tab at a time */
|
|
4378
4409
|
/* ------------------------------------------------------------------ */
|
|
4379
4410
|
/**
|
|
4380
|
-
* Whether this tab runs the tour
|
|
4381
|
-
*
|
|
4411
|
+
* Whether this tab runs the tour: one tab at a time holds a Web Lock and
|
|
4412
|
+
* runs steps, offers, the welcome and saves. Every other tab still reads
|
|
4413
|
+
* progress and shows the "?" menu (shell ≥ 0.1.78); pressing one of its
|
|
4414
|
+
* guide items calls `takeover`, which steals the lock. The tab it was
|
|
4415
|
+
* stolen from goes back to waiting its turn (and yields what it was running).
|
|
4416
|
+
* Without Web Locks every tab runs, as before.
|
|
4382
4417
|
*/
|
|
4383
4418
|
function usePrimaryTab(name) {
|
|
4384
4419
|
const [primary, setPrimary] = useState(false);
|
|
4420
|
+
const primaryRef = useRef(false);
|
|
4421
|
+
const takeoverRef = useRef(() => Promise.resolve());
|
|
4385
4422
|
useEffect(() => {
|
|
4386
4423
|
const locks = (typeof navigator !== "undefined" ? navigator.locks : undefined);
|
|
4424
|
+
const set = (v) => {
|
|
4425
|
+
primaryRef.current = v;
|
|
4426
|
+
setPrimary(v);
|
|
4427
|
+
};
|
|
4387
4428
|
if (!locks?.request) {
|
|
4388
|
-
|
|
4429
|
+
set(true);
|
|
4430
|
+
takeoverRef.current = () => Promise.resolve();
|
|
4389
4431
|
return;
|
|
4390
4432
|
}
|
|
4391
|
-
|
|
4433
|
+
let alive = true;
|
|
4434
|
+
let gen = 0;
|
|
4435
|
+
let pending = null;
|
|
4392
4436
|
let release = null;
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4437
|
+
let waiters = [];
|
|
4438
|
+
const queue = (steal) => {
|
|
4439
|
+
const my = ++gen;
|
|
4440
|
+
const ac = new AbortController();
|
|
4441
|
+
pending = steal ? null : ac;
|
|
4442
|
+
locks
|
|
4443
|
+
.request(name, steal ? { steal: true } : { signal: ac.signal }, () => {
|
|
4444
|
+
if (!alive || my !== gen)
|
|
4445
|
+
return undefined;
|
|
4446
|
+
pending = null;
|
|
4447
|
+
set(true);
|
|
4448
|
+
const w = waiters;
|
|
4449
|
+
waiters = [];
|
|
4450
|
+
w.forEach(f => f());
|
|
4451
|
+
return new Promise(resolve => {
|
|
4452
|
+
release = resolve;
|
|
4453
|
+
});
|
|
4454
|
+
})
|
|
4455
|
+
.catch(() => {
|
|
4456
|
+
// Stolen by another tab (or this request aborted): not the runner;
|
|
4457
|
+
// wait for the lock again.
|
|
4458
|
+
if (!alive || my !== gen)
|
|
4459
|
+
return;
|
|
4460
|
+
release = null;
|
|
4461
|
+
set(false);
|
|
4462
|
+
queue(false);
|
|
4398
4463
|
});
|
|
4399
|
-
}
|
|
4400
|
-
|
|
4401
|
-
|
|
4464
|
+
};
|
|
4465
|
+
queue(false);
|
|
4466
|
+
takeoverRef.current = () => new Promise(resolve => {
|
|
4467
|
+
if (primaryRef.current) {
|
|
4468
|
+
resolve();
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
waiters.push(resolve);
|
|
4472
|
+
pending?.abort();
|
|
4473
|
+
queue(true);
|
|
4402
4474
|
});
|
|
4403
4475
|
return () => {
|
|
4404
|
-
|
|
4476
|
+
alive = false;
|
|
4477
|
+
pending?.abort();
|
|
4405
4478
|
release?.();
|
|
4406
|
-
|
|
4479
|
+
waiters.forEach(f => f());
|
|
4480
|
+
waiters = [];
|
|
4481
|
+
set(false);
|
|
4407
4482
|
};
|
|
4408
4483
|
}, [name]);
|
|
4409
|
-
|
|
4484
|
+
const takeover = useCallback(() => takeoverRef.current(), []);
|
|
4485
|
+
return { primary, takeover };
|
|
4410
4486
|
}
|
|
4411
4487
|
/* ------------------------------------------------------------------ */
|
|
4412
4488
|
/* Putting the page back after a replay */
|
|
@@ -4515,7 +4591,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4515
4591
|
busyRef.current = busy;
|
|
4516
4592
|
// No menu: never a menu step (the machine sees it as always open).
|
|
4517
4593
|
const sidebarOpen = menu ? !!menu.open : true;
|
|
4518
|
-
const primary = usePrimaryTab(`erp-guide:${organizationId}:${suite.key}`);
|
|
4594
|
+
const { primary, takeover } = usePrimaryTab(`erp-guide:${organizationId}:${suite.key}`);
|
|
4595
|
+
const primaryRef = useRef(primary);
|
|
4596
|
+
primaryRef.current = primary;
|
|
4519
4597
|
// Progress: read once this tab runs the tour and the app is ready (for
|
|
4520
4598
|
// the missions, or for the page guides alone: config.pagesReady).
|
|
4521
4599
|
const [answer, setAnswer] = useState(null);
|
|
@@ -4524,7 +4602,8 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4524
4602
|
const hasPages = useMemo(() => validPages(suite).length > 0 || validSections(suite).length > 0 || validFlows(suite).length > 0, [suite]);
|
|
4525
4603
|
const hasFlows = useMemo(() => validFlows(suite).length > 0, [suite]);
|
|
4526
4604
|
const hasSections = useMemo(() => validSections(suite).length > 0, [suite]);
|
|
4527
|
-
|
|
4605
|
+
// Every tab reads progress (0.1.78): the "?" menu is shown in all of them.
|
|
4606
|
+
const enabled = missionsReady || (hasPages && pagesReady);
|
|
4528
4607
|
const loaded = useRef(false);
|
|
4529
4608
|
useEffect(() => {
|
|
4530
4609
|
if (!enabled || loaded.current)
|
|
@@ -4548,8 +4627,12 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4548
4627
|
}, [enabled, suite]);
|
|
4549
4628
|
// The missions run for their audience; the page guides for theirs (every
|
|
4550
4629
|
// member while pages are on), each with its own ready.
|
|
4551
|
-
|
|
4552
|
-
|
|
4630
|
+
// On: this person has them here (every tab, for the "?" menu). Live: this
|
|
4631
|
+
// tab runs them (the primary tab only): steps, offers, the welcome, saves.
|
|
4632
|
+
const missionsOn = !!answer && answer.audience !== "none" && !answer.newerOnServer && missionsReady && !hidden;
|
|
4633
|
+
const pagesOn = !!answer && hasPages && answer.pageAudience !== "none" && pagesReady && !hidden;
|
|
4634
|
+
const missionsLive = missionsOn && primary;
|
|
4635
|
+
const pagesLive = pagesOn && primary;
|
|
4553
4636
|
const live = missionsLive || pagesLive;
|
|
4554
4637
|
/* ---------------- machine ---------------- */
|
|
4555
4638
|
const [state, setState] = useState(null);
|
|
@@ -4642,6 +4725,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4642
4725
|
const row = await storeGuides(adapterRef.current, suite, cur.progress);
|
|
4643
4726
|
if (row)
|
|
4644
4727
|
savedKey.current = payloadKey(row, suite.app);
|
|
4728
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4729
|
+
if (row)
|
|
4730
|
+
announceSaved();
|
|
4645
4731
|
if (row && stateRef.current) {
|
|
4646
4732
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4647
4733
|
dispatchRef.current({ type: "serverRow", progress: row });
|
|
@@ -4698,6 +4784,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4698
4784
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4699
4785
|
if (row)
|
|
4700
4786
|
dispatchRef.current({ type: "pageRow", key: e.key, row, kind });
|
|
4787
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4788
|
+
if (row)
|
|
4789
|
+
announceSaved();
|
|
4701
4790
|
});
|
|
4702
4791
|
}
|
|
4703
4792
|
}
|
|
@@ -4744,6 +4833,95 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4744
4833
|
clearTimeout(toastTimer.current);
|
|
4745
4834
|
};
|
|
4746
4835
|
}, [flushSave]);
|
|
4836
|
+
/* ---------------- other tabs (0.1.78) ---------------- */
|
|
4837
|
+
// Progress saved in one tab reaches the others: a message on a channel
|
|
4838
|
+
// per organisation and app, and a fresh read when a tab comes back into
|
|
4839
|
+
// view. Only the server's rows are read, never another tab's state.
|
|
4840
|
+
const channelRef = useRef(null);
|
|
4841
|
+
const refreshNow = useCallback(async () => {
|
|
4842
|
+
const a = await loadGuides(adapterRef.current, suite, undefined, true);
|
|
4843
|
+
const cur = stateRef.current;
|
|
4844
|
+
if (!a || !cur)
|
|
4845
|
+
return;
|
|
4846
|
+
// The runner's own state wins while it is running something.
|
|
4847
|
+
if (primaryRef.current && (cur.screen.name !== "idle" || saving.current))
|
|
4848
|
+
return;
|
|
4849
|
+
if (a.progress.guide_version === cur.progress.guide_version && a.progress.rev >= cur.progress.rev) {
|
|
4850
|
+
savedKey.current = a.progress.rev > 0 ? payloadKey(a.progress, suite.app) : null;
|
|
4851
|
+
}
|
|
4852
|
+
dispatchRef.current({ type: "refresh", progress: a.progress, pages: a.pages, sections: a.sections, flows: a.flows });
|
|
4853
|
+
}, [suite]);
|
|
4854
|
+
const refreshRef = useRef(refreshNow);
|
|
4855
|
+
refreshRef.current = refreshNow;
|
|
4856
|
+
useEffect(() => {
|
|
4857
|
+
if (typeof BroadcastChannel === "undefined")
|
|
4858
|
+
return;
|
|
4859
|
+
let ch;
|
|
4860
|
+
try {
|
|
4861
|
+
ch = new BroadcastChannel(`erp-guide:${organizationId}:${suite.app}`);
|
|
4862
|
+
}
|
|
4863
|
+
catch {
|
|
4864
|
+
return;
|
|
4865
|
+
}
|
|
4866
|
+
channelRef.current = ch;
|
|
4867
|
+
ch.onmessage = (e) => {
|
|
4868
|
+
if (e.data?.type === "saved")
|
|
4869
|
+
void refreshRef.current();
|
|
4870
|
+
};
|
|
4871
|
+
return () => {
|
|
4872
|
+
channelRef.current = null;
|
|
4873
|
+
ch.close();
|
|
4874
|
+
};
|
|
4875
|
+
}, [organizationId, suite.app]);
|
|
4876
|
+
useEffect(() => {
|
|
4877
|
+
const onVisible = () => {
|
|
4878
|
+
if (document.visibilityState === "visible" && stateRef.current)
|
|
4879
|
+
void refreshRef.current();
|
|
4880
|
+
};
|
|
4881
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
4882
|
+
return () => document.removeEventListener("visibilitychange", onVisible);
|
|
4883
|
+
}, []);
|
|
4884
|
+
function announceSaved() {
|
|
4885
|
+
try {
|
|
4886
|
+
channelRef.current?.postMessage({ type: "saved" });
|
|
4887
|
+
}
|
|
4888
|
+
catch {
|
|
4889
|
+
/* a closed channel: the next visibility change reads again */
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
// The tab that lost the lock stops cleanly: whatever it was running ends
|
|
4893
|
+
// silently, without writing.
|
|
4894
|
+
useEffect(() => {
|
|
4895
|
+
if (primary || !stateRef.current)
|
|
4896
|
+
return;
|
|
4897
|
+
const cur = stateRef.current;
|
|
4898
|
+
if (saveTimer.current) {
|
|
4899
|
+
clearTimeout(saveTimer.current);
|
|
4900
|
+
saveTimer.current = null;
|
|
4901
|
+
}
|
|
4902
|
+
if (cur.screen.name !== "idle" || cur.panelOpen)
|
|
4903
|
+
dispatchRef.current({ type: "yield" });
|
|
4904
|
+
}, [primary]);
|
|
4905
|
+
/**
|
|
4906
|
+
* A guide item pressed in this tab: run it here. In a tab that is not the
|
|
4907
|
+
* runner, take the lock first (the other tab stops), read progress fresh,
|
|
4908
|
+
* then do it. Nothing resumes by itself in a tab taken over this way.
|
|
4909
|
+
*/
|
|
4910
|
+
const viaRunner = useCallback((fn) => {
|
|
4911
|
+
if (primaryRef.current) {
|
|
4912
|
+
fn();
|
|
4913
|
+
return;
|
|
4914
|
+
}
|
|
4915
|
+
resumed.current = true;
|
|
4916
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4917
|
+
flowResumed.current = true;
|
|
4918
|
+
void takeover()
|
|
4919
|
+
.then(() => refreshRef.current().catch(() => undefined))
|
|
4920
|
+
.then(() => {
|
|
4921
|
+
if (primaryRef.current)
|
|
4922
|
+
fn();
|
|
4923
|
+
});
|
|
4924
|
+
}, [takeover]);
|
|
4747
4925
|
/* ---------------- boot: resume, welcome ---------------- */
|
|
4748
4926
|
const screen = state?.screen ?? { name: "idle" };
|
|
4749
4927
|
const resumed = useRef(false);
|
|
@@ -5392,12 +5570,13 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5392
5570
|
!due;
|
|
5393
5571
|
useAfterIdle(passiveDue, PASSIVE_IDLE_MS, busy, () => dispatchRef.current({ type: "idleCelebrate" }), [state?.pendingCelebrate?.join(",")]);
|
|
5394
5572
|
useEffect(() => {
|
|
5395
|
-
|
|
5573
|
+
// Every tab shows "ไกด์เริ่มต้น n/N"; pressing it runs it in this tab.
|
|
5574
|
+
if (!missionsOn || !state) {
|
|
5396
5575
|
publishGuideEntry(null, null);
|
|
5397
5576
|
return;
|
|
5398
5577
|
}
|
|
5399
|
-
publishGuideEntry({ done, total }, () => dispatchRef.current({ type: "openPanel" }));
|
|
5400
|
-
}, [
|
|
5578
|
+
publishGuideEntry({ done, total }, () => viaRunner(() => dispatchRef.current({ type: "openPanel" })));
|
|
5579
|
+
}, [missionsOn, state, done, total, viaRunner]);
|
|
5401
5580
|
useEffect(() => () => publishGuideEntry(null, null), []);
|
|
5402
5581
|
/* ---------------- page guides: this page's, the offer, the list ---------------- */
|
|
5403
5582
|
// `when` anchors (LIFF's sub-screens) come and go without a route change:
|
|
@@ -5405,7 +5584,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5405
5584
|
const hasWhen = useMemo(() => validPages(suite).some(p => !!p.when) || validSections(suite).length > 0, [suite]);
|
|
5406
5585
|
const [domTick, setDomTick] = useState(0);
|
|
5407
5586
|
useEffect(() => {
|
|
5408
|
-
if (!
|
|
5587
|
+
if (!pagesOn || !hasWhen || typeof MutationObserver === "undefined")
|
|
5409
5588
|
return;
|
|
5410
5589
|
let t = null;
|
|
5411
5590
|
const obs = new MutationObserver(records => {
|
|
@@ -5429,18 +5608,18 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5429
5608
|
if (t)
|
|
5430
5609
|
clearTimeout(t);
|
|
5431
5610
|
};
|
|
5432
|
-
}, [
|
|
5611
|
+
}, [pagesOn, hasWhen]);
|
|
5433
5612
|
const hasState = !!state;
|
|
5434
|
-
const pageNow = useMemo(() => (
|
|
5613
|
+
const pageNow = useMemo(() => (pagesOn && hasState ? pageHere(suite, env()) : null),
|
|
5435
5614
|
// pathname/search/factsKey/domTick make it current.
|
|
5436
5615
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5437
|
-
[
|
|
5438
|
-
const listed = useMemo(() => (
|
|
5439
|
-
const sectionsNow = useMemo(() =>
|
|
5616
|
+
[pagesOn, hasState, suite, env, pathname, search, factsKey, domTick]);
|
|
5617
|
+
const listed = useMemo(() => (pagesOn && ctxIdle ? listedPages(suite, ctxIdle) : []), [pagesOn, suite, ctxIdle]);
|
|
5618
|
+
const sectionsNow = useMemo(() => pagesOn && hasState && hasSections
|
|
5440
5619
|
? sectionsAvailable(suite, env()).map(k => ({ key: k, title: findSection(suite, k)?.title ?? "" }))
|
|
5441
5620
|
: [],
|
|
5442
5621
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5443
|
-
[
|
|
5622
|
+
[pagesOn, hasState, hasSections, suite, env, pathname, search, factsKey, domTick]);
|
|
5444
5623
|
const sectionsSig = sectionsNow.map(x => x.key).join(",");
|
|
5445
5624
|
const openSection = useCallback((key) => {
|
|
5446
5625
|
openingSection.current = key;
|
|
@@ -5451,14 +5630,14 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5451
5630
|
openingSection.current = null;
|
|
5452
5631
|
}
|
|
5453
5632
|
}, []);
|
|
5454
|
-
const flowsNow = useMemo(() =>
|
|
5633
|
+
const flowsNow = useMemo(() => pagesOn && hasState && hasFlows
|
|
5455
5634
|
? flowsAvailable(suite, env()).map(k => ({ key: k, title: findFlow(suite, k)?.title ?? "" }))
|
|
5456
5635
|
: [],
|
|
5457
5636
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5458
|
-
[
|
|
5637
|
+
[pagesOn, hasState, hasFlows, suite, env, pathname, search, factsKey]);
|
|
5459
5638
|
const flowsSig = flowsNow.map(x => x.key).join(",");
|
|
5460
5639
|
/** The flows the panel lists: this person's, startable here or with somewhere to go. */
|
|
5461
|
-
const flowsListed = useMemo(() => (
|
|
5640
|
+
const flowsListed = useMemo(() => (pagesOn && hasFlows && ctxIdle ? shownFlows(suite, ctxIdle).filter(f => onFlowStart(f, { code, pathname, search: new URLSearchParams(search) }) || !!flowHref(f, ctxIdle)) : []), [pagesOn, hasFlows, suite, ctxIdle, code, pathname, search]);
|
|
5462
5641
|
const openFlowHere = useCallback((key) => dispatchRef.current({ type: "startFlow", flow: key }), []);
|
|
5463
5642
|
const openPageHere = useCallback(() => {
|
|
5464
5643
|
const p = pageHere(suite, env());
|
|
@@ -5466,7 +5645,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5466
5645
|
dispatchRef.current({ type: "startPage", page: p.key });
|
|
5467
5646
|
}, [suite, env]);
|
|
5468
5647
|
useEffect(() => {
|
|
5469
|
-
if (!
|
|
5648
|
+
if (!pagesOn || !hasState) {
|
|
5470
5649
|
publishPageGuides(null, null, null);
|
|
5471
5650
|
return;
|
|
5472
5651
|
}
|
|
@@ -5476,10 +5655,10 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5476
5655
|
sections: sectionsNow,
|
|
5477
5656
|
flows: flowsNow,
|
|
5478
5657
|
flowsListed: flowsListed.length,
|
|
5479
|
-
}, openPageHere, () => dispatchRef.current({ type: "openPanel", section: "pages" }), openSection, openFlowHere, () => dispatchRef.current({ type: "openPanel", section: "flows" }));
|
|
5658
|
+
}, () => viaRunner(openPageHere), () => viaRunner(() => dispatchRef.current({ type: "openPanel", section: "pages" })), (key) => viaRunner(() => openSection(key)), (key) => viaRunner(() => openFlowHere(key)), () => viaRunner(() => dispatchRef.current({ type: "openPanel", section: "flows" })));
|
|
5480
5659
|
// sectionsSig / flowsSig stand for sectionsNow / flowsNow.
|
|
5481
5660
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5482
|
-
}, [
|
|
5661
|
+
}, [pagesOn, hasState, pageNow, listed.length, openPageHere, sectionsSig, openSection, flowsSig, flowsListed.length, openFlowHere, viaRunner]);
|
|
5483
5662
|
useEffect(() => () => publishPageGuides(null, null, null), []);
|
|
5484
5663
|
// The first-visit offer: in "offer" mode, nothing else up, the welcome
|
|
5485
5664
|
// not due here, and a moment of nothing loading on this page.
|
|
@@ -5676,6 +5855,32 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5676
5855
|
dispatchRef.current({ type: "resumeFlow", flow: saved.flow, step: saved.step, context, write: saved.write === true });
|
|
5677
5856
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5678
5857
|
}, [pagesLive, !!state, hasFlows, missionsLive]);
|
|
5858
|
+
// The layer sits above every dialog, and a dialog's buttons sit at its
|
|
5859
|
+
// bottom right, where the chip is: the chip covered them in every app.
|
|
5860
|
+
// While an app dialog is open, the chip is not drawn.
|
|
5861
|
+
const [appDialogShown, setAppDialogShown] = useState(false);
|
|
5862
|
+
useEffect(() => {
|
|
5863
|
+
if (!missionsLive || typeof MutationObserver === "undefined" || typeof document === "undefined" || !document.body)
|
|
5864
|
+
return;
|
|
5865
|
+
let raf = 0;
|
|
5866
|
+
const check = () => {
|
|
5867
|
+
raf = 0;
|
|
5868
|
+
setAppDialogShown(appDialogOpen());
|
|
5869
|
+
};
|
|
5870
|
+
check();
|
|
5871
|
+
const obs = new MutationObserver(records => {
|
|
5872
|
+
if (records.every(r => inGuideLayer(r.target)))
|
|
5873
|
+
return;
|
|
5874
|
+
if (!raf)
|
|
5875
|
+
raf = requestAnimationFrame(check);
|
|
5876
|
+
});
|
|
5877
|
+
obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-state", "role"] });
|
|
5878
|
+
return () => {
|
|
5879
|
+
obs.disconnect();
|
|
5880
|
+
if (raf)
|
|
5881
|
+
cancelAnimationFrame(raf);
|
|
5882
|
+
};
|
|
5883
|
+
}, [missionsLive]);
|
|
5679
5884
|
if (!live || !state || !layer || !ctxIdle)
|
|
5680
5885
|
return null;
|
|
5681
5886
|
/* ---------------- render ---------------- */
|
|
@@ -5783,7 +5988,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5783
5988
|
.join(", ")
|
|
5784
5989
|
: undefined,
|
|
5785
5990
|
}));
|
|
5786
|
-
const showChip = missionsLive && chipVisible(state, suite, ctxIdle);
|
|
5991
|
+
const showChip = missionsLive && !appDialogShown && chipVisible(state, suite, ctxIdle);
|
|
5787
5992
|
// The panel: the missions (when this person has them, and not "คู่มือทุกหน้า"),
|
|
5788
5993
|
// then the page guides they have.
|
|
5789
5994
|
const panelMissions = missionsLive && total > 0 && state.panelSection === "all";
|
|
@@ -5903,4 +6108,4 @@ function thaiMonthYear(m, opts = {}) {
|
|
|
5903
6108
|
}
|
|
5904
6109
|
|
|
5905
6110
|
export { API_EVENT as A, useGuideEntry as B, usePageGuideEntry as C, usePageGuides as D, useSectionGuideValues as E, GUIDE_EVENT_PREFIX as G, SectionGuideButton as S, THAI_MONTHS_SHORT as T, GUIDE_LAYER_ATTRIBUTE as a, GuideErrorBoundary as b, GuideProgressConflict as c, GuideRuntime as d, apiCallDetail as e, emitGuideEvent as f, fillRoute as g, formatBaht as h, guideAnchor as i, guideMenuItems as j, guideMenuShown as k, loadGuides as l, matchApiPath as m, matchRoute as n, openFlow as o, openFlowsPanel as p, openGuidePanel as q, openPageGuide as r, openPagesPanel as s, openSectionGuide as t, reportApiCall as u, reportApiEvent as v, setSectionGuideValues as w, storeGuides as x, thaiMonth as y, thaiMonthYear as z };
|
|
5906
|
-
//# sourceMappingURL=format-
|
|
6111
|
+
//# sourceMappingURL=format-BqUpxbzD.esm.js.map
|