@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
|
@@ -2827,6 +2827,37 @@ function reduceGuide(state, action, suite, env) {
|
|
|
2827
2827
|
d.state = { ...d.state, pages: { ...d.state.pages, [action.key]: action.row } };
|
|
2828
2828
|
break;
|
|
2829
2829
|
}
|
|
2830
|
+
case "yield": {
|
|
2831
|
+
const run = d.state.pageRun;
|
|
2832
|
+
if (run?.kind === "flow" && run.context)
|
|
2833
|
+
withProgress(d, { context: run.context });
|
|
2834
|
+
d.state = { ...d.state, pageRun: null, replayPage: null, panelOpen: false, justDone: null, pendingCelebrate: [] };
|
|
2835
|
+
setScreen(d, { name: "idle" });
|
|
2836
|
+
break;
|
|
2837
|
+
}
|
|
2838
|
+
case "refresh": {
|
|
2839
|
+
const p = action.progress;
|
|
2840
|
+
const local = d.state.progress;
|
|
2841
|
+
// The server's row when it is this version and not behind this tab's.
|
|
2842
|
+
if (p.guide_version === local.guide_version && p.rev >= local.rev)
|
|
2843
|
+
d.state = { ...d.state, progress: p };
|
|
2844
|
+
const newer = (server, mine) => {
|
|
2845
|
+
const out = { ...(mine ?? {}) };
|
|
2846
|
+
for (const [k, r] of Object.entries(server ?? {})) {
|
|
2847
|
+
const m = out[k];
|
|
2848
|
+
if (!m || r.version > m.version || (r.version === m.version && r.rev >= m.rev))
|
|
2849
|
+
out[k] = r;
|
|
2850
|
+
}
|
|
2851
|
+
return out;
|
|
2852
|
+
};
|
|
2853
|
+
d.state = {
|
|
2854
|
+
...d.state,
|
|
2855
|
+
pages: newer(action.pages, d.state.pages),
|
|
2856
|
+
sections: newer(action.sections, d.state.sections),
|
|
2857
|
+
flows: newer(action.flows, d.state.flows),
|
|
2858
|
+
};
|
|
2859
|
+
break;
|
|
2860
|
+
}
|
|
2830
2861
|
case "startSection": {
|
|
2831
2862
|
// Never offered: only on request. Never over a mission step, a
|
|
2832
2863
|
// welcome, a gate or a celebration; only on screen, only theirs.
|
|
@@ -3196,9 +3227,9 @@ function conflictOf(err) {
|
|
|
3196
3227
|
return null;
|
|
3197
3228
|
}
|
|
3198
3229
|
/** The GET, parsed for this suite; null (show nothing) on any failure or odd answer. */
|
|
3199
|
-
async function loadGuides(adapter, suite, signal) {
|
|
3230
|
+
async function loadGuides(adapter, suite, signal, fresh = false) {
|
|
3200
3231
|
try {
|
|
3201
|
-
const raw = await adapter.fetchProgress({ app: suite.app, guideKey: suite.key, signal });
|
|
3232
|
+
const raw = await adapter.fetchProgress({ app: suite.app, guideKey: suite.key, signal, ...(fresh ? { fresh: true } : {}) });
|
|
3202
3233
|
return parseGuidesAnswer(raw, suite);
|
|
3203
3234
|
}
|
|
3204
3235
|
catch {
|
|
@@ -4397,36 +4428,81 @@ const flowMirrorKey = (orgId, suite) => `erp-guide-flow-run:${orgId}:${suite.app
|
|
|
4397
4428
|
/* One tab at a time */
|
|
4398
4429
|
/* ------------------------------------------------------------------ */
|
|
4399
4430
|
/**
|
|
4400
|
-
* Whether this tab runs the tour
|
|
4401
|
-
*
|
|
4431
|
+
* Whether this tab runs the tour: one tab at a time holds a Web Lock and
|
|
4432
|
+
* runs steps, offers, the welcome and saves. Every other tab still reads
|
|
4433
|
+
* progress and shows the "?" menu (shell ≥ 0.1.78); pressing one of its
|
|
4434
|
+
* guide items calls `takeover`, which steals the lock. The tab it was
|
|
4435
|
+
* stolen from goes back to waiting its turn (and yields what it was running).
|
|
4436
|
+
* Without Web Locks every tab runs, as before.
|
|
4402
4437
|
*/
|
|
4403
4438
|
function usePrimaryTab(name) {
|
|
4404
4439
|
const [primary, setPrimary] = React.useState(false);
|
|
4440
|
+
const primaryRef = React.useRef(false);
|
|
4441
|
+
const takeoverRef = React.useRef(() => Promise.resolve());
|
|
4405
4442
|
React.useEffect(() => {
|
|
4406
4443
|
const locks = (typeof navigator !== "undefined" ? navigator.locks : undefined);
|
|
4444
|
+
const set = (v) => {
|
|
4445
|
+
primaryRef.current = v;
|
|
4446
|
+
setPrimary(v);
|
|
4447
|
+
};
|
|
4407
4448
|
if (!locks?.request) {
|
|
4408
|
-
|
|
4449
|
+
set(true);
|
|
4450
|
+
takeoverRef.current = () => Promise.resolve();
|
|
4409
4451
|
return;
|
|
4410
4452
|
}
|
|
4411
|
-
|
|
4453
|
+
let alive = true;
|
|
4454
|
+
let gen = 0;
|
|
4455
|
+
let pending = null;
|
|
4412
4456
|
let release = null;
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4457
|
+
let waiters = [];
|
|
4458
|
+
const queue = (steal) => {
|
|
4459
|
+
const my = ++gen;
|
|
4460
|
+
const ac = new AbortController();
|
|
4461
|
+
pending = steal ? null : ac;
|
|
4462
|
+
locks
|
|
4463
|
+
.request(name, steal ? { steal: true } : { signal: ac.signal }, () => {
|
|
4464
|
+
if (!alive || my !== gen)
|
|
4465
|
+
return undefined;
|
|
4466
|
+
pending = null;
|
|
4467
|
+
set(true);
|
|
4468
|
+
const w = waiters;
|
|
4469
|
+
waiters = [];
|
|
4470
|
+
w.forEach(f => f());
|
|
4471
|
+
return new Promise(resolve => {
|
|
4472
|
+
release = resolve;
|
|
4473
|
+
});
|
|
4474
|
+
})
|
|
4475
|
+
.catch(() => {
|
|
4476
|
+
// Stolen by another tab (or this request aborted): not the runner;
|
|
4477
|
+
// wait for the lock again.
|
|
4478
|
+
if (!alive || my !== gen)
|
|
4479
|
+
return;
|
|
4480
|
+
release = null;
|
|
4481
|
+
set(false);
|
|
4482
|
+
queue(false);
|
|
4418
4483
|
});
|
|
4419
|
-
}
|
|
4420
|
-
|
|
4421
|
-
|
|
4484
|
+
};
|
|
4485
|
+
queue(false);
|
|
4486
|
+
takeoverRef.current = () => new Promise(resolve => {
|
|
4487
|
+
if (primaryRef.current) {
|
|
4488
|
+
resolve();
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
waiters.push(resolve);
|
|
4492
|
+
pending?.abort();
|
|
4493
|
+
queue(true);
|
|
4422
4494
|
});
|
|
4423
4495
|
return () => {
|
|
4424
|
-
|
|
4496
|
+
alive = false;
|
|
4497
|
+
pending?.abort();
|
|
4425
4498
|
release?.();
|
|
4426
|
-
|
|
4499
|
+
waiters.forEach(f => f());
|
|
4500
|
+
waiters = [];
|
|
4501
|
+
set(false);
|
|
4427
4502
|
};
|
|
4428
4503
|
}, [name]);
|
|
4429
|
-
|
|
4504
|
+
const takeover = React.useCallback(() => takeoverRef.current(), []);
|
|
4505
|
+
return { primary, takeover };
|
|
4430
4506
|
}
|
|
4431
4507
|
/* ------------------------------------------------------------------ */
|
|
4432
4508
|
/* Putting the page back after a replay */
|
|
@@ -4535,7 +4611,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4535
4611
|
busyRef.current = busy;
|
|
4536
4612
|
// No menu: never a menu step (the machine sees it as always open).
|
|
4537
4613
|
const sidebarOpen = menu ? !!menu.open : true;
|
|
4538
|
-
const primary = usePrimaryTab(`erp-guide:${organizationId}:${suite.key}`);
|
|
4614
|
+
const { primary, takeover } = usePrimaryTab(`erp-guide:${organizationId}:${suite.key}`);
|
|
4615
|
+
const primaryRef = React.useRef(primary);
|
|
4616
|
+
primaryRef.current = primary;
|
|
4539
4617
|
// Progress: read once this tab runs the tour and the app is ready (for
|
|
4540
4618
|
// the missions, or for the page guides alone: config.pagesReady).
|
|
4541
4619
|
const [answer, setAnswer] = React.useState(null);
|
|
@@ -4544,7 +4622,8 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4544
4622
|
const hasPages = React.useMemo(() => validPages(suite).length > 0 || validSections(suite).length > 0 || validFlows(suite).length > 0, [suite]);
|
|
4545
4623
|
const hasFlows = React.useMemo(() => validFlows(suite).length > 0, [suite]);
|
|
4546
4624
|
const hasSections = React.useMemo(() => validSections(suite).length > 0, [suite]);
|
|
4547
|
-
|
|
4625
|
+
// Every tab reads progress (0.1.78): the "?" menu is shown in all of them.
|
|
4626
|
+
const enabled = missionsReady || (hasPages && pagesReady);
|
|
4548
4627
|
const loaded = React.useRef(false);
|
|
4549
4628
|
React.useEffect(() => {
|
|
4550
4629
|
if (!enabled || loaded.current)
|
|
@@ -4568,8 +4647,12 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4568
4647
|
}, [enabled, suite]);
|
|
4569
4648
|
// The missions run for their audience; the page guides for theirs (every
|
|
4570
4649
|
// member while pages are on), each with its own ready.
|
|
4571
|
-
|
|
4572
|
-
|
|
4650
|
+
// On: this person has them here (every tab, for the "?" menu). Live: this
|
|
4651
|
+
// tab runs them (the primary tab only): steps, offers, the welcome, saves.
|
|
4652
|
+
const missionsOn = !!answer && answer.audience !== "none" && !answer.newerOnServer && missionsReady && !hidden;
|
|
4653
|
+
const pagesOn = !!answer && hasPages && answer.pageAudience !== "none" && pagesReady && !hidden;
|
|
4654
|
+
const missionsLive = missionsOn && primary;
|
|
4655
|
+
const pagesLive = pagesOn && primary;
|
|
4573
4656
|
const live = missionsLive || pagesLive;
|
|
4574
4657
|
/* ---------------- machine ---------------- */
|
|
4575
4658
|
const [state, setState] = React.useState(null);
|
|
@@ -4662,6 +4745,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4662
4745
|
const row = await storeGuides(adapterRef.current, suite, cur.progress);
|
|
4663
4746
|
if (row)
|
|
4664
4747
|
savedKey.current = payloadKey(row, suite.app);
|
|
4748
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4749
|
+
if (row)
|
|
4750
|
+
announceSaved();
|
|
4665
4751
|
if (row && stateRef.current) {
|
|
4666
4752
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4667
4753
|
dispatchRef.current({ type: "serverRow", progress: row });
|
|
@@ -4718,6 +4804,9 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4718
4804
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4719
4805
|
if (row)
|
|
4720
4806
|
dispatchRef.current({ type: "pageRow", key: e.key, row, kind });
|
|
4807
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4808
|
+
if (row)
|
|
4809
|
+
announceSaved();
|
|
4721
4810
|
});
|
|
4722
4811
|
}
|
|
4723
4812
|
}
|
|
@@ -4764,6 +4853,95 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
4764
4853
|
clearTimeout(toastTimer.current);
|
|
4765
4854
|
};
|
|
4766
4855
|
}, [flushSave]);
|
|
4856
|
+
/* ---------------- other tabs (0.1.78) ---------------- */
|
|
4857
|
+
// Progress saved in one tab reaches the others: a message on a channel
|
|
4858
|
+
// per organisation and app, and a fresh read when a tab comes back into
|
|
4859
|
+
// view. Only the server's rows are read, never another tab's state.
|
|
4860
|
+
const channelRef = React.useRef(null);
|
|
4861
|
+
const refreshNow = React.useCallback(async () => {
|
|
4862
|
+
const a = await loadGuides(adapterRef.current, suite, undefined, true);
|
|
4863
|
+
const cur = stateRef.current;
|
|
4864
|
+
if (!a || !cur)
|
|
4865
|
+
return;
|
|
4866
|
+
// The runner's own state wins while it is running something.
|
|
4867
|
+
if (primaryRef.current && (cur.screen.name !== "idle" || saving.current))
|
|
4868
|
+
return;
|
|
4869
|
+
if (a.progress.guide_version === cur.progress.guide_version && a.progress.rev >= cur.progress.rev) {
|
|
4870
|
+
savedKey.current = a.progress.rev > 0 ? payloadKey(a.progress, suite.app) : null;
|
|
4871
|
+
}
|
|
4872
|
+
dispatchRef.current({ type: "refresh", progress: a.progress, pages: a.pages, sections: a.sections, flows: a.flows });
|
|
4873
|
+
}, [suite]);
|
|
4874
|
+
const refreshRef = React.useRef(refreshNow);
|
|
4875
|
+
refreshRef.current = refreshNow;
|
|
4876
|
+
React.useEffect(() => {
|
|
4877
|
+
if (typeof BroadcastChannel === "undefined")
|
|
4878
|
+
return;
|
|
4879
|
+
let ch;
|
|
4880
|
+
try {
|
|
4881
|
+
ch = new BroadcastChannel(`erp-guide:${organizationId}:${suite.app}`);
|
|
4882
|
+
}
|
|
4883
|
+
catch {
|
|
4884
|
+
return;
|
|
4885
|
+
}
|
|
4886
|
+
channelRef.current = ch;
|
|
4887
|
+
ch.onmessage = (e) => {
|
|
4888
|
+
if (e.data?.type === "saved")
|
|
4889
|
+
void refreshRef.current();
|
|
4890
|
+
};
|
|
4891
|
+
return () => {
|
|
4892
|
+
channelRef.current = null;
|
|
4893
|
+
ch.close();
|
|
4894
|
+
};
|
|
4895
|
+
}, [organizationId, suite.app]);
|
|
4896
|
+
React.useEffect(() => {
|
|
4897
|
+
const onVisible = () => {
|
|
4898
|
+
if (document.visibilityState === "visible" && stateRef.current)
|
|
4899
|
+
void refreshRef.current();
|
|
4900
|
+
};
|
|
4901
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
4902
|
+
return () => document.removeEventListener("visibilitychange", onVisible);
|
|
4903
|
+
}, []);
|
|
4904
|
+
function announceSaved() {
|
|
4905
|
+
try {
|
|
4906
|
+
channelRef.current?.postMessage({ type: "saved" });
|
|
4907
|
+
}
|
|
4908
|
+
catch {
|
|
4909
|
+
/* a closed channel: the next visibility change reads again */
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
// The tab that lost the lock stops cleanly: whatever it was running ends
|
|
4913
|
+
// silently, without writing.
|
|
4914
|
+
React.useEffect(() => {
|
|
4915
|
+
if (primary || !stateRef.current)
|
|
4916
|
+
return;
|
|
4917
|
+
const cur = stateRef.current;
|
|
4918
|
+
if (saveTimer.current) {
|
|
4919
|
+
clearTimeout(saveTimer.current);
|
|
4920
|
+
saveTimer.current = null;
|
|
4921
|
+
}
|
|
4922
|
+
if (cur.screen.name !== "idle" || cur.panelOpen)
|
|
4923
|
+
dispatchRef.current({ type: "yield" });
|
|
4924
|
+
}, [primary]);
|
|
4925
|
+
/**
|
|
4926
|
+
* A guide item pressed in this tab: run it here. In a tab that is not the
|
|
4927
|
+
* runner, take the lock first (the other tab stops), read progress fresh,
|
|
4928
|
+
* then do it. Nothing resumes by itself in a tab taken over this way.
|
|
4929
|
+
*/
|
|
4930
|
+
const viaRunner = React.useCallback((fn) => {
|
|
4931
|
+
if (primaryRef.current) {
|
|
4932
|
+
fn();
|
|
4933
|
+
return;
|
|
4934
|
+
}
|
|
4935
|
+
resumed.current = true;
|
|
4936
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
4937
|
+
flowResumed.current = true;
|
|
4938
|
+
void takeover()
|
|
4939
|
+
.then(() => refreshRef.current().catch(() => undefined))
|
|
4940
|
+
.then(() => {
|
|
4941
|
+
if (primaryRef.current)
|
|
4942
|
+
fn();
|
|
4943
|
+
});
|
|
4944
|
+
}, [takeover]);
|
|
4767
4945
|
/* ---------------- boot: resume, welcome ---------------- */
|
|
4768
4946
|
const screen = state?.screen ?? { name: "idle" };
|
|
4769
4947
|
const resumed = React.useRef(false);
|
|
@@ -5412,12 +5590,13 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5412
5590
|
!due;
|
|
5413
5591
|
useAfterIdle(passiveDue, PASSIVE_IDLE_MS, busy, () => dispatchRef.current({ type: "idleCelebrate" }), [state?.pendingCelebrate?.join(",")]);
|
|
5414
5592
|
React.useEffect(() => {
|
|
5415
|
-
|
|
5593
|
+
// Every tab shows "ไกด์เริ่มต้น n/N"; pressing it runs it in this tab.
|
|
5594
|
+
if (!missionsOn || !state) {
|
|
5416
5595
|
publishGuideEntry(null, null);
|
|
5417
5596
|
return;
|
|
5418
5597
|
}
|
|
5419
|
-
publishGuideEntry({ done, total }, () => dispatchRef.current({ type: "openPanel" }));
|
|
5420
|
-
}, [
|
|
5598
|
+
publishGuideEntry({ done, total }, () => viaRunner(() => dispatchRef.current({ type: "openPanel" })));
|
|
5599
|
+
}, [missionsOn, state, done, total, viaRunner]);
|
|
5421
5600
|
React.useEffect(() => () => publishGuideEntry(null, null), []);
|
|
5422
5601
|
/* ---------------- page guides: this page's, the offer, the list ---------------- */
|
|
5423
5602
|
// `when` anchors (LIFF's sub-screens) come and go without a route change:
|
|
@@ -5425,7 +5604,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5425
5604
|
const hasWhen = React.useMemo(() => validPages(suite).some(p => !!p.when) || validSections(suite).length > 0, [suite]);
|
|
5426
5605
|
const [domTick, setDomTick] = React.useState(0);
|
|
5427
5606
|
React.useEffect(() => {
|
|
5428
|
-
if (!
|
|
5607
|
+
if (!pagesOn || !hasWhen || typeof MutationObserver === "undefined")
|
|
5429
5608
|
return;
|
|
5430
5609
|
let t = null;
|
|
5431
5610
|
const obs = new MutationObserver(records => {
|
|
@@ -5449,18 +5628,18 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5449
5628
|
if (t)
|
|
5450
5629
|
clearTimeout(t);
|
|
5451
5630
|
};
|
|
5452
|
-
}, [
|
|
5631
|
+
}, [pagesOn, hasWhen]);
|
|
5453
5632
|
const hasState = !!state;
|
|
5454
|
-
const pageNow = React.useMemo(() => (
|
|
5633
|
+
const pageNow = React.useMemo(() => (pagesOn && hasState ? pageHere(suite, env()) : null),
|
|
5455
5634
|
// pathname/search/factsKey/domTick make it current.
|
|
5456
5635
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5457
|
-
[
|
|
5458
|
-
const listed = React.useMemo(() => (
|
|
5459
|
-
const sectionsNow = React.useMemo(() =>
|
|
5636
|
+
[pagesOn, hasState, suite, env, pathname, search, factsKey, domTick]);
|
|
5637
|
+
const listed = React.useMemo(() => (pagesOn && ctxIdle ? listedPages(suite, ctxIdle) : []), [pagesOn, suite, ctxIdle]);
|
|
5638
|
+
const sectionsNow = React.useMemo(() => pagesOn && hasState && hasSections
|
|
5460
5639
|
? sectionsAvailable(suite, env()).map(k => ({ key: k, title: findSection(suite, k)?.title ?? "" }))
|
|
5461
5640
|
: [],
|
|
5462
5641
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5463
|
-
[
|
|
5642
|
+
[pagesOn, hasState, hasSections, suite, env, pathname, search, factsKey, domTick]);
|
|
5464
5643
|
const sectionsSig = sectionsNow.map(x => x.key).join(",");
|
|
5465
5644
|
const openSection = React.useCallback((key) => {
|
|
5466
5645
|
openingSection.current = key;
|
|
@@ -5471,14 +5650,14 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5471
5650
|
openingSection.current = null;
|
|
5472
5651
|
}
|
|
5473
5652
|
}, []);
|
|
5474
|
-
const flowsNow = React.useMemo(() =>
|
|
5653
|
+
const flowsNow = React.useMemo(() => pagesOn && hasState && hasFlows
|
|
5475
5654
|
? flowsAvailable(suite, env()).map(k => ({ key: k, title: findFlow(suite, k)?.title ?? "" }))
|
|
5476
5655
|
: [],
|
|
5477
5656
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5478
|
-
[
|
|
5657
|
+
[pagesOn, hasState, hasFlows, suite, env, pathname, search, factsKey]);
|
|
5479
5658
|
const flowsSig = flowsNow.map(x => x.key).join(",");
|
|
5480
5659
|
/** The flows the panel lists: this person's, startable here or with somewhere to go. */
|
|
5481
|
-
const flowsListed = React.useMemo(() => (
|
|
5660
|
+
const flowsListed = React.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]);
|
|
5482
5661
|
const openFlowHere = React.useCallback((key) => dispatchRef.current({ type: "startFlow", flow: key }), []);
|
|
5483
5662
|
const openPageHere = React.useCallback(() => {
|
|
5484
5663
|
const p = pageHere(suite, env());
|
|
@@ -5486,7 +5665,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5486
5665
|
dispatchRef.current({ type: "startPage", page: p.key });
|
|
5487
5666
|
}, [suite, env]);
|
|
5488
5667
|
React.useEffect(() => {
|
|
5489
|
-
if (!
|
|
5668
|
+
if (!pagesOn || !hasState) {
|
|
5490
5669
|
publishPageGuides(null, null, null);
|
|
5491
5670
|
return;
|
|
5492
5671
|
}
|
|
@@ -5496,10 +5675,10 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5496
5675
|
sections: sectionsNow,
|
|
5497
5676
|
flows: flowsNow,
|
|
5498
5677
|
flowsListed: flowsListed.length,
|
|
5499
|
-
}, openPageHere, () => dispatchRef.current({ type: "openPanel", section: "pages" }), openSection, openFlowHere, () => dispatchRef.current({ type: "openPanel", section: "flows" }));
|
|
5678
|
+
}, () => viaRunner(openPageHere), () => viaRunner(() => dispatchRef.current({ type: "openPanel", section: "pages" })), (key) => viaRunner(() => openSection(key)), (key) => viaRunner(() => openFlowHere(key)), () => viaRunner(() => dispatchRef.current({ type: "openPanel", section: "flows" })));
|
|
5500
5679
|
// sectionsSig / flowsSig stand for sectionsNow / flowsNow.
|
|
5501
5680
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5502
|
-
}, [
|
|
5681
|
+
}, [pagesOn, hasState, pageNow, listed.length, openPageHere, sectionsSig, openSection, flowsSig, flowsListed.length, openFlowHere, viaRunner]);
|
|
5503
5682
|
React.useEffect(() => () => publishPageGuides(null, null, null), []);
|
|
5504
5683
|
// The first-visit offer: in "offer" mode, nothing else up, the welcome
|
|
5505
5684
|
// not due here, and a moment of nothing loading on this page.
|
|
@@ -5696,6 +5875,32 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5696
5875
|
dispatchRef.current({ type: "resumeFlow", flow: saved.flow, step: saved.step, context, write: saved.write === true });
|
|
5697
5876
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5698
5877
|
}, [pagesLive, !!state, hasFlows, missionsLive]);
|
|
5878
|
+
// The layer sits above every dialog, and a dialog's buttons sit at its
|
|
5879
|
+
// bottom right, where the chip is: the chip covered them in every app.
|
|
5880
|
+
// While an app dialog is open, the chip is not drawn.
|
|
5881
|
+
const [appDialogShown, setAppDialogShown] = React.useState(false);
|
|
5882
|
+
React.useEffect(() => {
|
|
5883
|
+
if (!missionsLive || typeof MutationObserver === "undefined" || typeof document === "undefined" || !document.body)
|
|
5884
|
+
return;
|
|
5885
|
+
let raf = 0;
|
|
5886
|
+
const check = () => {
|
|
5887
|
+
raf = 0;
|
|
5888
|
+
setAppDialogShown(appDialogOpen());
|
|
5889
|
+
};
|
|
5890
|
+
check();
|
|
5891
|
+
const obs = new MutationObserver(records => {
|
|
5892
|
+
if (records.every(r => inGuideLayer(r.target)))
|
|
5893
|
+
return;
|
|
5894
|
+
if (!raf)
|
|
5895
|
+
raf = requestAnimationFrame(check);
|
|
5896
|
+
});
|
|
5897
|
+
obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-state", "role"] });
|
|
5898
|
+
return () => {
|
|
5899
|
+
obs.disconnect();
|
|
5900
|
+
if (raf)
|
|
5901
|
+
cancelAnimationFrame(raf);
|
|
5902
|
+
};
|
|
5903
|
+
}, [missionsLive]);
|
|
5699
5904
|
if (!live || !state || !layer || !ctxIdle)
|
|
5700
5905
|
return null;
|
|
5701
5906
|
/* ---------------- render ---------------- */
|
|
@@ -5803,7 +6008,7 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
|
|
|
5803
6008
|
.join(", ")
|
|
5804
6009
|
: undefined,
|
|
5805
6010
|
}));
|
|
5806
|
-
const showChip = missionsLive && chipVisible(state, suite, ctxIdle);
|
|
6011
|
+
const showChip = missionsLive && !appDialogShown && chipVisible(state, suite, ctxIdle);
|
|
5807
6012
|
// The panel: the missions (when this person has them, and not "คู่มือทุกหน้า"),
|
|
5808
6013
|
// then the page guides they have.
|
|
5809
6014
|
const panelMissions = missionsLive && total > 0 && state.panelSection === "all";
|
|
@@ -5956,4 +6161,4 @@ exports.useGuideEntry = useGuideEntry;
|
|
|
5956
6161
|
exports.usePageGuideEntry = usePageGuideEntry;
|
|
5957
6162
|
exports.usePageGuides = usePageGuides;
|
|
5958
6163
|
exports.useSectionGuideValues = useSectionGuideValues;
|
|
5959
|
-
//# sourceMappingURL=format-
|
|
6164
|
+
//# sourceMappingURL=format-CFXhGhta.cjs.map
|