@takagaki/cortex-decisions-viewer 0.12.173 → 0.12.175
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/dist/render.js +135 -11
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -4923,6 +4923,10 @@ a:hover { text-decoration: underline; }
|
|
|
4923
4923
|
.mface { position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; overflow: hidden; background: var(--border, #e5e7eb); color: var(--muted, #6b7280); font-size: 13px; font-weight: 700; line-height: 1; text-decoration: none; box-sizing: border-box; }
|
|
4924
4924
|
a.mface:hover { box-shadow: 0 0 0 2px var(--accent, #4f46e5); }
|
|
4925
4925
|
.mface svg { width: 18px; height: 18px; display: block; }
|
|
4926
|
+
.mface-loading { animation: mface-pulse 1.4s ease-in-out infinite; }
|
|
4927
|
+
.mface-loading img { opacity: 0; }
|
|
4928
|
+
@keyframes mface-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
|
|
4929
|
+
@media (prefers-reduced-motion: reduce) { .mface-loading { animation: none; } }
|
|
4926
4930
|
.mface img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
4927
4931
|
.detail-namerow .mface { width: 36px; height: 36px; font-size: 16px; }
|
|
4928
4932
|
.detail-namerow .mface svg { width: 22px; height: 22px; }
|
|
@@ -7741,42 +7745,131 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
7741
7745
|
var mfAsked = {};
|
|
7742
7746
|
var mfWaiting = [];
|
|
7743
7747
|
var mfTimer = null;
|
|
7748
|
+
/** 頼んでいて答え待ちの人 -> その人の丸(待っている間はスケルトン) */
|
|
7749
|
+
var mfInflight = {};
|
|
7750
|
+
var MF_ASK_TIMEOUT_MS = 8000;
|
|
7751
|
+
var MF_IMG_TIMEOUT_MS = 8000;
|
|
7752
|
+
/** 待ちの形から最後の形へ(loading を外す)。state: photo=写真が読めた/initial=頭文字 */
|
|
7753
|
+
function mfSettle(box, state) {
|
|
7754
|
+
if (box.getAttribute("data-state") !== "loading") return;
|
|
7755
|
+
box.classList.remove("mface-loading");
|
|
7756
|
+
box.setAttribute("data-state", state);
|
|
7757
|
+
if (state === "initial") {
|
|
7758
|
+
var img = box.querySelector("img");
|
|
7759
|
+
if (img && img.parentNode) img.parentNode.removeChild(img);
|
|
7760
|
+
box.appendChild(el("span", { class: "mface-initial", text: box.getAttribute("data-initial") || "" }));
|
|
7761
|
+
}
|
|
7762
|
+
}
|
|
7763
|
+
/** 取れた写真を読み込む。読めるまではスケルトンのまま・失敗か時間切れで頭文字(止まり続けない) */
|
|
7744
7764
|
function mfAttach(box, path) {
|
|
7745
|
-
if (
|
|
7765
|
+
if (box.getAttribute("data-state") !== "loading") return;
|
|
7746
7766
|
var img = document.createElement("img");
|
|
7767
|
+
var timer = setTimeout(function () { mfSettle(box, "initial"); }, MF_IMG_TIMEOUT_MS);
|
|
7747
7768
|
img.setAttribute("alt", "");
|
|
7748
7769
|
img.setAttribute("referrerpolicy", "no-referrer");
|
|
7749
|
-
img.
|
|
7750
|
-
img.addEventListener("error", function () {
|
|
7770
|
+
img.addEventListener("load", function () { clearTimeout(timer); mfSettle(box, "photo"); });
|
|
7771
|
+
img.addEventListener("error", function () { clearTimeout(timer); mfSettle(box, "initial"); });
|
|
7751
7772
|
img.setAttribute("src", PORTAL_ORIGIN + path);
|
|
7752
7773
|
box.appendChild(img);
|
|
7753
7774
|
}
|
|
7775
|
+
/** もう答えが分かっている人の丸を決める(写真のパスがあれば読みに行き、無ければ頭文字) */
|
|
7776
|
+
function mfResolve(box, id) {
|
|
7777
|
+
if (mfFaces[id]) mfAttach(box, mfFaces[id]);
|
|
7778
|
+
else mfSettle(box, "initial");
|
|
7779
|
+
}
|
|
7754
7780
|
function mfFlush() {
|
|
7755
7781
|
mfTimer = null;
|
|
7756
7782
|
var waiting = mfWaiting;
|
|
7757
7783
|
mfWaiting = [];
|
|
7758
|
-
if (!waiting.length
|
|
7784
|
+
if (!waiting.length) return;
|
|
7785
|
+
if (!SITE.intake || !actorTicket()) { waiting.forEach(function (w) { mfSettle(w.box, "initial"); }); return; }
|
|
7759
7786
|
var ids = [];
|
|
7760
7787
|
waiting.forEach(function (w) {
|
|
7761
|
-
if (Object.prototype.hasOwnProperty.call(mfFaces, w.id)) {
|
|
7762
|
-
|
|
7788
|
+
if (Object.prototype.hasOwnProperty.call(mfFaces, w.id)) { mfResolve(w.box, w.id); return; }
|
|
7789
|
+
var asked = Object.prototype.hasOwnProperty.call(mfAsked, w.id);
|
|
7790
|
+
if (!asked && ids.indexOf(w.id) < 0) {
|
|
7791
|
+
// 1 回に頼むのは 200 人まで。あふれた人は待たせず頭文字(スケルトンのまま止まらない)
|
|
7792
|
+
if (ids.length >= 200) { mfSettle(w.box, "initial"); return; }
|
|
7793
|
+
ids.push(w.id);
|
|
7794
|
+
}
|
|
7795
|
+
(mfInflight[w.id] = mfInflight[w.id] || []).push(w.box);
|
|
7763
7796
|
});
|
|
7764
7797
|
if (!ids.length) return;
|
|
7798
|
+
mfRequest(ids, false);
|
|
7799
|
+
}
|
|
7800
|
+
/**
|
|
7801
|
+
* member-faces を 1 回頼む(200 人まで)。頼んだ人は mfAsked に入れ、答え(または諦め)で待っている丸を最後の形へ。
|
|
7802
|
+
* preload=起動時の先読み。画像もブラウザのキャッシュに載せておく
|
|
7803
|
+
*/
|
|
7804
|
+
function mfRequest(ids, preload) {
|
|
7765
7805
|
ids.forEach(function (id) { mfAsked[id] = 1; });
|
|
7806
|
+
var finished = false;
|
|
7807
|
+
// 答えが出た(または諦めた)人の待ち行列を空にして、全部の丸を最後の形へ
|
|
7808
|
+
var drain = function () {
|
|
7809
|
+
ids.forEach(function (id) {
|
|
7810
|
+
var boxes = mfInflight[id] || [];
|
|
7811
|
+
delete mfInflight[id];
|
|
7812
|
+
boxes.forEach(function (b) { mfResolve(b, id); });
|
|
7813
|
+
});
|
|
7814
|
+
};
|
|
7766
7815
|
var give = function (j) {
|
|
7767
7816
|
(j.people || []).forEach(function (one) {
|
|
7768
|
-
if (one && typeof one.userId === "string" && typeof one.avatarUrl === "string" && MF_AVATAR.test(one.avatarUrl))
|
|
7817
|
+
if (one && typeof one.userId === "string" && typeof one.avatarUrl === "string" && MF_AVATAR.test(one.avatarUrl)) {
|
|
7818
|
+
mfFaces[one.userId] = one.avatarUrl;
|
|
7819
|
+
if (preload) mfWarm(one.avatarUrl);
|
|
7820
|
+
}
|
|
7769
7821
|
});
|
|
7770
7822
|
ids.forEach(function (id) { if (!Object.prototype.hasOwnProperty.call(mfFaces, id)) mfFaces[id] = null; });
|
|
7771
|
-
|
|
7823
|
+
if (!finished) { finished = true; clearTimeout(giveUp); drain(); }
|
|
7824
|
+
};
|
|
7825
|
+
// 失敗・断り・時間切れは覚えない(あとで画面を開き直せば取り直す)。待っていた丸は頭文字に落とす
|
|
7826
|
+
var forget = function () {
|
|
7827
|
+
if (finished) return;
|
|
7828
|
+
finished = true;
|
|
7829
|
+
clearTimeout(giveUp);
|
|
7830
|
+
ids.forEach(function (id) { delete mfAsked[id]; });
|
|
7831
|
+
drain();
|
|
7772
7832
|
};
|
|
7773
|
-
var
|
|
7833
|
+
var giveUp = setTimeout(forget, MF_ASK_TIMEOUT_MS);
|
|
7774
7834
|
intakeJson(callIntake("member-faces", { op: "member-faces", userIds: ids })).then(function (res) {
|
|
7775
7835
|
var j = (res && res.j) || {};
|
|
7776
7836
|
if (res && res.ok && j.ok === true && Array.isArray(j.people)) give(j);
|
|
7777
7837
|
else forget();
|
|
7778
7838
|
}, forget);
|
|
7779
7839
|
}
|
|
7840
|
+
/** 先読みした画像のパス -> 読み込み済み(読み込み済みなら関係者タブでスケルトンを経ずに写真から出す) */
|
|
7841
|
+
var mfReady = {};
|
|
7842
|
+
function mfWarm(path) {
|
|
7843
|
+
if (Object.prototype.hasOwnProperty.call(mfReady, path)) return;
|
|
7844
|
+
mfReady[path] = false;
|
|
7845
|
+
var img = new Image();
|
|
7846
|
+
img.setAttribute("referrerpolicy", "no-referrer");
|
|
7847
|
+
img.addEventListener("load", function () { mfReady[path] = true; });
|
|
7848
|
+
img.addEventListener("error", function () { delete mfReady[path]; });
|
|
7849
|
+
img.src = PORTAL_ORIGIN + path;
|
|
7850
|
+
}
|
|
7851
|
+
/**
|
|
7852
|
+
* 起動時に 1 回だけ、全部の関係者の portal.id を 200 人ずつ頼んで写真を先読みする(loadLiveFleet と同じ作り)。
|
|
7853
|
+
* 🔴 route() の後に置く(popstate は route だけ通るので、戻る/進むで何度も走らない)。券・intake が無ければ何もしない。
|
|
7854
|
+
* 先読みの飛行中に関係者タブを開いた丸は mfInflight に並び、先読みの答えで同じ流れ(スケルトン → 写真)に乗る。
|
|
7855
|
+
* 失敗は覚えない(mfRequest の forget)。タブを開いたときにもう一度頼める
|
|
7856
|
+
*/
|
|
7857
|
+
function mfPreload() {
|
|
7858
|
+
if (!SITE.intake || !actorTicket()) return;
|
|
7859
|
+
var ids = [];
|
|
7860
|
+
(SITE.sections || []).forEach(function (sec) {
|
|
7861
|
+
(sec.records || []).forEach(function (r) {
|
|
7862
|
+
if (!isMemberRec(r)) return;
|
|
7863
|
+
var id = mfPortalId(r);
|
|
7864
|
+
if (id && ids.indexOf(id) < 0 && !Object.prototype.hasOwnProperty.call(mfAsked, id) && !Object.prototype.hasOwnProperty.call(mfFaces, id)) ids.push(id);
|
|
7865
|
+
});
|
|
7866
|
+
});
|
|
7867
|
+
for (var i = 0; i < ids.length; i += 200) {
|
|
7868
|
+
var chunk = ids.slice(i, i + 200);
|
|
7869
|
+
chunk.forEach(function (id) { mfAsked[id] = 1; });
|
|
7870
|
+
mfRequest(chunk, true);
|
|
7871
|
+
}
|
|
7872
|
+
}
|
|
7780
7873
|
function mfWant(id, box) {
|
|
7781
7874
|
mfWaiting.push({ id: id, box: box });
|
|
7782
7875
|
if (!mfTimer) mfTimer = setTimeout(mfFlush, 0);
|
|
@@ -7805,17 +7898,46 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
7805
7898
|
return plain;
|
|
7806
7899
|
}
|
|
7807
7900
|
var name = Array.from(humanTitle(rec.title))[0] || "";
|
|
7901
|
+
// 🔴 画像を取りに行っている間は頭文字を出さず、丸の形のスケルトン(確定した表示がすり替わって見えないように)。
|
|
7902
|
+
// 頭文字は「取れなかった・読めなかった・時間切れ」の最後の形にだけ出す。読み上げには「読み込み中」を足さない
|
|
7808
7903
|
var a = el("a", {
|
|
7809
|
-
class: "mface mface-portal",
|
|
7904
|
+
class: "mface mface-portal mface-loading",
|
|
7810
7905
|
"data-face": "portal",
|
|
7906
|
+
"data-state": "loading",
|
|
7907
|
+
"data-initial": name,
|
|
7811
7908
|
href: PORTAL_ORIGIN + "/people/" + encodeURIComponent(id),
|
|
7812
7909
|
target: "_blank",
|
|
7813
7910
|
rel: "noopener",
|
|
7814
7911
|
title: "ポータルでこの人のページを開く",
|
|
7815
7912
|
});
|
|
7816
|
-
a.appendChild(el("span", { class: "mface-initial", text: name }));
|
|
7817
7913
|
// 行ごと(カード・詳細)の click に伝えない(カードを押した扱いで詳細へ移らない)
|
|
7818
7914
|
a.addEventListener("click", function (ev) { ev.stopPropagation(); });
|
|
7915
|
+
// 先読みで答えが分かっている人は、スケルトンを経ずに最後の形から出す(写真は読み込み済みのときだけ)
|
|
7916
|
+
if (Object.prototype.hasOwnProperty.call(mfFaces, id)) {
|
|
7917
|
+
var known = mfFaces[id];
|
|
7918
|
+
if (!known) {
|
|
7919
|
+
a.classList.remove("mface-loading");
|
|
7920
|
+
a.setAttribute("data-state", "initial");
|
|
7921
|
+
a.appendChild(el("span", { class: "mface-initial", text: name }));
|
|
7922
|
+
return a;
|
|
7923
|
+
}
|
|
7924
|
+
if (mfReady[known] === true) {
|
|
7925
|
+
var ready = document.createElement("img");
|
|
7926
|
+
ready.setAttribute("alt", "");
|
|
7927
|
+
ready.setAttribute("referrerpolicy", "no-referrer");
|
|
7928
|
+
ready.addEventListener("error", function () {
|
|
7929
|
+
// 先読みで読めていたはずの画像が壊れたとき(キャッシュ切れ・ポータル不調)も頭文字に落とす
|
|
7930
|
+
if (ready.parentNode) ready.parentNode.removeChild(ready);
|
|
7931
|
+
a.setAttribute("data-state", "initial");
|
|
7932
|
+
a.appendChild(el("span", { class: "mface-initial", text: name }));
|
|
7933
|
+
});
|
|
7934
|
+
ready.setAttribute("src", PORTAL_ORIGIN + known);
|
|
7935
|
+
a.classList.remove("mface-loading");
|
|
7936
|
+
a.setAttribute("data-state", "photo");
|
|
7937
|
+
a.appendChild(ready);
|
|
7938
|
+
return a;
|
|
7939
|
+
}
|
|
7940
|
+
}
|
|
7819
7941
|
mfWant(id, a);
|
|
7820
7942
|
return a;
|
|
7821
7943
|
}
|
|
@@ -21723,6 +21845,8 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
21723
21845
|
// ビルド時には存在しないので、**焼いた href は起動の時点で既に古いことがある**。
|
|
21724
21846
|
// 🔴 route() の後に置く——ヘッダー以外の導線は route() が描くまで DOM に無い
|
|
21725
21847
|
syncAiLinks();
|
|
21848
|
+
/** 関係者の写真の先読み(mfPreload の項)。loadLiveFleet と同じく起動時に 1 回だけ */
|
|
21849
|
+
mfPreload();
|
|
21726
21850
|
/**
|
|
21727
21851
|
* 接続状況の先読み(#264)。**開いたタブに関係なく、起動時に1回だけ取りに行く。**
|
|
21728
21852
|
*
|
package/package.json
CHANGED