@takagaki/cortex-decisions-viewer 0.4.76 → 0.4.77
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/build.js +23 -4
- package/dist/render.js +225 -60
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -312,18 +312,37 @@ async function readFleetSources(repoRoot) {
|
|
|
312
312
|
// **画面に現在値を出すために要る。** 「接続先を変更」フォームが入力欄だけだったため、
|
|
313
313
|
// 実機で「何もセットされていない」と読まれ、同じURLが貼り直された(#127)。
|
|
314
314
|
//
|
|
315
|
-
//
|
|
316
|
-
// features
|
|
315
|
+
// **接続先そのものしか通さない。** 生成側は configPath / sections / strays /
|
|
316
|
+
// features も持っているが、画面が使うのは接続先だけ。素通しにすると、engine が
|
|
317
317
|
// このオブジェクトに足したものが顧客の画面データへ黙って流れる(Lambda 側の
|
|
318
318
|
// FLEET_PUBLIC_KEYS と同じ考え方で、こちらも許可リストにする)。
|
|
319
|
+
// 配列の要素も同じ2つに絞る——要素だけ素通しにすると、許可リストを通したつもりで
|
|
320
|
+
// 中身が漏れる(externalSources で実際にやった間違い)。
|
|
321
|
+
//
|
|
322
|
+
// **接続先は複数ありうる(#127)。** 1案件が複数の Backlog プロジェクトを同期できる
|
|
323
|
+
// ようになったので、`targets` があればそれを一覧の正本にする。**単数の2つは残す**
|
|
324
|
+
// (先頭と同じ値)——`targets` を載せない古いスナップショットでも現在値を出せるように、
|
|
325
|
+
// 読み手の両対応と同じ約束を画面データでも守る。
|
|
319
326
|
//
|
|
320
327
|
// **値が無い形は落とす。** 正本もミラーも持たない案件では `{ source: "secrets" }` だけが
|
|
321
328
|
// 来る。空の器を通すと、画面は「接続先の情報がある」と判断して空文字を並べることになる
|
|
322
329
|
const bt = obj.backlogTarget != null && typeof obj.backlogTarget === "object" && !Array.isArray(obj.backlogTarget)
|
|
323
330
|
? obj.backlogTarget
|
|
324
331
|
: null;
|
|
325
|
-
const
|
|
326
|
-
|
|
332
|
+
const oneTarget = (v) => {
|
|
333
|
+
if (v == null || typeof v !== "object")
|
|
334
|
+
return null;
|
|
335
|
+
const o = v;
|
|
336
|
+
return typeof o.domain === "string" && o.domain && typeof o.projectKey === "string" && o.projectKey
|
|
337
|
+
? { domain: o.domain, projectKey: o.projectKey }
|
|
338
|
+
: null;
|
|
339
|
+
};
|
|
340
|
+
const btTargets = Array.isArray(bt?.targets)
|
|
341
|
+
? bt.targets.map(oneTarget).filter((t) => t !== null)
|
|
342
|
+
: [];
|
|
343
|
+
const btHead = oneTarget(bt) ?? btTargets[0] ?? null;
|
|
344
|
+
const backlogTarget = btHead
|
|
345
|
+
? { ...btHead, ...(btTargets.length ? { targets: btTargets } : {}) }
|
|
327
346
|
: undefined;
|
|
328
347
|
const secretSources = obj.secretSources && typeof obj.secretSources === "object"
|
|
329
348
|
? obj.secretSources
|
package/dist/render.js
CHANGED
|
@@ -1304,6 +1304,12 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
1304
1304
|
.ds-current-label { color: var(--muted); flex: 0 0 auto; }
|
|
1305
1305
|
.ds-current-value { color: var(--text); font-weight: 600; word-break: break-all; }
|
|
1306
1306
|
.ds-current-value.is-unset { color: var(--muted); font-weight: 400; }
|
|
1307
|
+
/* 接続先の一覧(#127)。**1件のときは従来どおりラベルの右に1行**——単数の案件で
|
|
1308
|
+
見た目が変わると、複数対応と関係のない全案件に「何か変わった」と読ませる */
|
|
1309
|
+
.ds-target-list { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
1310
|
+
.ds-target-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
|
|
1311
|
+
/* 行の中のボタンは行に揃える(.ds-toggle の縦マージンは節と節の間隔なので効かせない) */
|
|
1312
|
+
.ds-target-row .ds-toggle-btn { flex: 0 0 auto; }
|
|
1307
1313
|
/* 入力欄のすぐ下。貼る直前に読める位置に置く */
|
|
1308
1314
|
.sec-note { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 6px 0 0; }
|
|
1309
1315
|
.sec-how p { margin: 0; }
|
|
@@ -2875,31 +2881,200 @@ ${capabilityState.toString()}
|
|
|
2875
2881
|
}
|
|
2876
2882
|
|
|
2877
2883
|
/**
|
|
2878
|
-
* いま同期している Backlog
|
|
2884
|
+
* いま同期している Backlog の接続先の一覧(#127)。**1件でも配列で返す。**
|
|
2879
2885
|
*
|
|
2880
|
-
* **
|
|
2881
|
-
*
|
|
2882
|
-
*
|
|
2883
|
-
*
|
|
2886
|
+
* **3つの経路から引く。** 正本は fleet の backlogTarget で、接続先が複数ある案件では
|
|
2887
|
+
* targets に全件が入る。targets を載せていないスナップショット(複数対応より前に
|
|
2888
|
+
* 生成されたもの)では単数の2つを1件として読み、それも無ければ内部ソースの url
|
|
2889
|
+
* (同じ値から機械導出されたプロジェクトURL)を解析して補う。
|
|
2884
2890
|
* ここを backlogTarget だけにすると、**設定済みの案件に「未設定」と出す**
|
|
2885
|
-
*
|
|
2886
|
-
* 画面の側からもう一度作ることになる。
|
|
2891
|
+
* ——直したい誤読(何もセットされていないと読んで貼り直す)を画面の側から作ることになる。
|
|
2887
2892
|
*
|
|
2888
|
-
*
|
|
2889
|
-
*
|
|
2893
|
+
* **半端な要素は落とす。** ドメインかキーの片方しか無い接続先は行として読めない
|
|
2894
|
+
* (「cm1.backlog.jp / 」が並ぶ)。空になったら「未設定」に倒す。
|
|
2890
2895
|
*/
|
|
2891
|
-
function
|
|
2896
|
+
function backlogTargetsNow(s) {
|
|
2892
2897
|
var t = fleetVal("backlogTarget");
|
|
2893
|
-
|
|
2894
|
-
var
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2898
|
+
var raw = t && Array.isArray(t.targets) ? t.targets : t ? [t] : [];
|
|
2899
|
+
var out = [];
|
|
2900
|
+
for (var i = 0; i < raw.length; i++) {
|
|
2901
|
+
var x = raw[i];
|
|
2902
|
+
if (x && x.domain && x.projectKey) out.push({ domain: String(x.domain), projectKey: String(x.projectKey) });
|
|
2903
|
+
}
|
|
2904
|
+
if (out.length) return out;
|
|
2905
|
+
var u = (s && s.url && backlogTargetOf(s.url)) || null;
|
|
2906
|
+
return u ? [u] : [];
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
/**
|
|
2910
|
+
* 接続先の一覧を1つの文字列にする(**サーバへ送る「いま画面が見ている世代」**・#127)。
|
|
2911
|
+
*
|
|
2912
|
+
* 追加・削除は一覧に対する操作なので、後勝ちで上書きすると**他の人が足した接続先が
|
|
2913
|
+
* 黙って消える**。サーバは受け取った世代といまの正本を突き合わせ、違えば 409 で断る。
|
|
2914
|
+
*
|
|
2915
|
+
* **並びと大小は問わない**(サーバ側の targetsGeneration と同じ畳み方にすること)。
|
|
2916
|
+
* 片方だけが大文字化していると、正しい操作が毎回 409 になって**再読み込みしても直らない**
|
|
2917
|
+
* ——食い違わないことは backlog-targets-ui.test.mjs が実物どうしで突き合わせている。
|
|
2918
|
+
*
|
|
2919
|
+
* **クロージャを持たない純関数に保つこと**(テストが配信物から切り出して直接呼ぶ)。
|
|
2920
|
+
*/
|
|
2921
|
+
function targetsGenerationOf(list) {
|
|
2922
|
+
return (list || [])
|
|
2923
|
+
.filter(function (t) { return t && t.domain && t.projectKey; })
|
|
2924
|
+
.map(function (t) { return String(t.domain).toLowerCase() + "/" + String(t.projectKey).toUpperCase(); })
|
|
2925
|
+
.sort()
|
|
2926
|
+
.join(",");
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
/**
|
|
2930
|
+
* 他の人が先に一覧を変えていたときの文面(#127)。
|
|
2931
|
+
*
|
|
2932
|
+
* **サーバの文面をそのまま出さない。** サーバは「画面を再読み込みして…」と案内するが、
|
|
2933
|
+
* 画面は応答に添えられた一覧でその場で追いついている。再読み込みを促すと、
|
|
2934
|
+
* 済んでいることをやらせる(そして再読み込みしても同じ表示になる=堂々巡りに見える)。
|
|
2935
|
+
*/
|
|
2936
|
+
var TARGETS_RACE_NOTE =
|
|
2937
|
+
"他の人が先に接続先を変更しました。最新の一覧に更新しましたので、内容を確かめてもう一度お試しください。";
|
|
2938
|
+
|
|
2939
|
+
/**
|
|
2940
|
+
* 「課題管理」カードの接続先(一覧+追加+削除・#127)。
|
|
2941
|
+
*
|
|
2942
|
+
* **一覧は応答で描き直す。** 追加・削除の応答には必ずいまの一覧が添えられている
|
|
2943
|
+
* (成功も、重複で断られたときも、世代がずれていたときも)。夜間スナップショットの
|
|
2944
|
+
* 到着を待つと、押した直後は何も変わらないので「効いていない」と読まれ、もう一度
|
|
2945
|
+
* 押して「すでに登録されています」になる——他の追加フォームで実際に起きたこと。
|
|
2946
|
+
*
|
|
2947
|
+
* **結果の表示は一覧の外に置く。** 行の中に置くと、一覧を描き直した瞬間に
|
|
2948
|
+
* 押した結果が消える(Gold の先出しバッジで踏んだのと同じ罠)。
|
|
2949
|
+
*/
|
|
2950
|
+
function backlogTargetsSection(s) {
|
|
2951
|
+
var targets = backlogTargetsNow(s);
|
|
2952
|
+
// 書き込み先(正本の置き場)が決まらない案件では操作を出さない。判定は呼び出し側と
|
|
2953
|
+
// 同じ s.url(同期ミラーが1つでもあるか)で、フォームを出さない条件と揃える
|
|
2954
|
+
var editable = !!(s && s.url);
|
|
2955
|
+
|
|
2956
|
+
var listEl = el("div", { class: "ds-target-list" });
|
|
2957
|
+
var current = el("div", { class: "ds-current" }, [
|
|
2958
|
+
el("span", { class: "ds-current-label", text: "現在の接続先" }),
|
|
2959
|
+
listEl,
|
|
2960
|
+
]);
|
|
2961
|
+
// 追加・削除の結果はここに1つだけ出す(行を描き直しても消えない位置)
|
|
2962
|
+
var msg = el("div", { class: "ds-toggle-msg" });
|
|
2963
|
+
|
|
2964
|
+
function removeRow(t) {
|
|
2965
|
+
var row = el("div", { class: "ds-target-row" }, [
|
|
2966
|
+
el("span", { class: "ds-current-value", text: t.domain + " / " + t.projectKey }),
|
|
2967
|
+
]);
|
|
2968
|
+
if (!editable) return row;
|
|
2969
|
+
var btn = el("button", { class: "ds-toggle-btn ds-danger", type: "button", text: "この接続先を外す" });
|
|
2970
|
+
btn.addEventListener("click", function () {
|
|
2971
|
+
// **最後の1件かどうかは「押した時点」で数える。** 描画時の件数で決めると、
|
|
2972
|
+
// 2件を1件ずつ外したとき最後の1件で確認が出ない(Figma で踏んだのと同じ)
|
|
2973
|
+
var last = targets.length === 1;
|
|
2974
|
+
if (!confirmDanger(removeTargetConfirmLines(t, last))) { msg.textContent = ""; return; }
|
|
2975
|
+
var payload = {
|
|
2976
|
+
op: "backlog-target-remove",
|
|
2977
|
+
// **projectKey では送らない。** body の projectKey は案件キー(cortex の
|
|
2978
|
+
// 案件識別子)に予約されていて、トークンから解決した案件と食い違えば 403 で断られる
|
|
2979
|
+
backlogProjectKey: t.projectKey,
|
|
2980
|
+
baseGeneration: targetsGenerationOf(targets),
|
|
2981
|
+
};
|
|
2982
|
+
// **常には送らない。** 最後の1件の重さを利用者に確かめてもらった証がこのフラグで、
|
|
2983
|
+
// 無条件に付けると「一覧から1行消すだけ」の見た目で同期が止まる
|
|
2984
|
+
if (last) payload.confirmLast = true;
|
|
2985
|
+
sendConfig(payload, msg, btn, null, targetsHooks());
|
|
2986
|
+
});
|
|
2987
|
+
row.appendChild(btn);
|
|
2988
|
+
return row;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
function paint() {
|
|
2992
|
+
while (listEl.firstChild) listEl.removeChild(listEl.firstChild);
|
|
2993
|
+
if (!targets.length) {
|
|
2994
|
+
// **行ごと消さない。** 表示が無いと「まだ何も無い」のか「出し忘れ」なのか区別できない
|
|
2995
|
+
listEl.appendChild(el("span", { class: "ds-current-value is-unset", text: "未設定" }));
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
for (var i = 0; i < targets.length; i++) listEl.appendChild(removeRow(targets[i]));
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
/**
|
|
3002
|
+
* 応答に添えられた一覧を取り込む。**画面が見ていた集合と違っていたら true**
|
|
3003
|
+
* (=他の人が先に変えていた。409 の文面をこちらの言葉に差し替える判断に使う)。
|
|
3004
|
+
*/
|
|
3005
|
+
function adoptTargets(list) {
|
|
3006
|
+
var before = targetsGenerationOf(targets);
|
|
3007
|
+
var next = [];
|
|
3008
|
+
for (var i = 0; i < list.length; i++) {
|
|
3009
|
+
var x = list[i];
|
|
3010
|
+
if (x && x.domain && x.projectKey) next.push({ domain: String(x.domain), projectKey: String(x.projectKey) });
|
|
3011
|
+
}
|
|
3012
|
+
targets = next;
|
|
3013
|
+
paint();
|
|
3014
|
+
return targetsGenerationOf(targets) !== before;
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
function targetsHooks() {
|
|
3018
|
+
return { onTargets: adoptTargets, staleNote: TARGETS_RACE_NOTE };
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
paint();
|
|
3022
|
+
var box = el("div", {}, [current]);
|
|
3023
|
+
if (editable) {
|
|
3024
|
+
box.appendChild(addForm({
|
|
3025
|
+
// **「追加」に戻す(#127)。** 1件しか持てなかった頃は「保存」(置き換え)だったが、
|
|
3026
|
+
// 接続先は本当に足せるようになった。押した結果は一覧の行として出る
|
|
3027
|
+
title: "+ 接続先を追加",
|
|
3028
|
+
placeholder: "https://<スペース>.backlog.jp/projects/<プロジェクトキー>",
|
|
3029
|
+
requiredNote: "BacklogのURLを入力してください(プロジェクトキーが入った形。数字だけのURLは受け付けません)。",
|
|
3030
|
+
hint: "Backlog でこの案件のプロジェクトを開き、ブラウザのURLをそのまま貼ってください(課題の画面のURLでも構いません)。APIキーは下の「Backlog のAPIキー」から別に登録します。",
|
|
3031
|
+
confirmLines: function (v) {
|
|
3032
|
+
// **解析した結果を見せる。** 貼ったURLをそのまま出すと、どのプロジェクトを
|
|
3033
|
+
// 指すことになるかが確定前に分からない。実際 /projects/PJ-CORTEX は
|
|
3034
|
+
// ハイフンで切れて PJ と解釈される(黙って別プロジェクトを指す)
|
|
3035
|
+
var t = backlogTargetOf(v);
|
|
3036
|
+
return [
|
|
3037
|
+
"この案件が同期する Backlog プロジェクトを追加します。",
|
|
3038
|
+
"",
|
|
3039
|
+
t ? t.domain + " / " + t.projectKey : v,
|
|
3040
|
+
"",
|
|
3041
|
+
"このプロジェクトの課題・ドキュメント・Wiki が、次の同期から取り込まれます。",
|
|
3042
|
+
"貼ったURLがこの案件のものか、もう一度確認してください。",
|
|
3043
|
+
"",
|
|
3044
|
+
"※ 増えるのは同期先だけです。ビューアからの起票先は変わりません。リアルタイム同期を使う場合は、追加したプロジェクトの Backlog 側にも案件専用URLの登録が要ります。",
|
|
3045
|
+
"",
|
|
3046
|
+
"追加しますか?",
|
|
3047
|
+
];
|
|
3048
|
+
},
|
|
3049
|
+
build: function (url) {
|
|
3050
|
+
return { op: "backlog-target-add", url: url, baseGeneration: targetsGenerationOf(targets) };
|
|
3051
|
+
},
|
|
3052
|
+
// 応答の一覧で本物の行が増えるので、フォーム側の控え(「追加済み・反映中」)は出さない
|
|
3053
|
+
hooks: targetsHooks,
|
|
3054
|
+
}));
|
|
3055
|
+
}
|
|
3056
|
+
box.appendChild(msg);
|
|
3057
|
+
return box;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
/** 接続先を外すときの確認。**最後の1件は、止まるものを名指しで伝える** */
|
|
3061
|
+
function removeTargetConfirmLines(t, last) {
|
|
3062
|
+
var lines = [
|
|
3063
|
+
"「" + t.domain + " / " + t.projectKey + "」を同期の対象から外します。",
|
|
3064
|
+
"",
|
|
3065
|
+
];
|
|
3066
|
+
if (last) {
|
|
3067
|
+
// 一覧から1行消す見た目と、実際に起きることの重さが釣り合わないので必ず言う
|
|
3068
|
+
lines.push("これが最後の接続先です。この接続先を外すと課題の同期が止まります。よろしいですか?");
|
|
3069
|
+
} else {
|
|
3070
|
+
lines.push(
|
|
3071
|
+
"次回の同期から、このプロジェクトの課題・ドキュメント・Wiki は取り込まれなくなります。",
|
|
3072
|
+
"取り込み済みの内容はリポジトリに残ります(不要なら手で削除してください)。",
|
|
3073
|
+
"",
|
|
3074
|
+
"外しますか?",
|
|
3075
|
+
);
|
|
3076
|
+
}
|
|
3077
|
+
return lines;
|
|
2903
3078
|
}
|
|
2904
3079
|
|
|
2905
3080
|
/**
|
|
@@ -2969,46 +3144,21 @@ ${capabilityState.toString()}
|
|
|
2969
3144
|
// 画面のどこにも出ていなかった。保存直後に出る楽観的なチップはリロードで消えるので、
|
|
2970
3145
|
// **利用者は「何もセットされていない」と読み、同じURLを貼り直した**。
|
|
2971
3146
|
// 設定を変える口の隣には、いまの値が必ず要る。
|
|
2972
|
-
|
|
2973
|
-
//
|
|
2974
|
-
// 貼っても 404 で終わる。三菱電機は tools が backlog なのに
|
|
3147
|
+
// **設定ファイルが無い案件には追加・削除を出さない。** 書き込み先が無いので
|
|
3148
|
+
// 押しても 404 で終わる。三菱電機は tools が backlog なのに
|
|
2975
3149
|
// backlog-settings.json が1つも無い(固定IP経由の同期で、UIからの投入は
|
|
2976
3150
|
// 鍵と同じく対応していない)。会議・共有資料は unset / broken を文言で
|
|
2977
|
-
//
|
|
3151
|
+
// 伝えてフォームを出さない形なので、課題管理だけ不揃いだった。
|
|
3152
|
+
// 一覧そのもの(現在の接続先)は**そういう案件にも出す**——読めているものを隠さない
|
|
2978
3153
|
//
|
|
2979
3154
|
// **接続先の正本は issues-config.json(#111)**だが、その置き場は
|
|
2980
3155
|
// 同期ミラー(backlog-settings.json)の位置から決めている。ミラーが1つも
|
|
2981
3156
|
// 無い案件では正本をどこへ置けばよいかも決められないので、条件は変わらない
|
|
3157
|
+
box.appendChild(backlogTargetsSection(s));
|
|
2982
3158
|
if (!s.url) {
|
|
2983
3159
|
box.appendChild(el("p", { class: "sec-how", text: "この案件はまだ一度も Backlog と同期していないため、この画面からは接続先を設定できません。先に初回の同期を実行するか、cortex の担当者にご連絡ください。" }));
|
|
2984
3160
|
used = true;
|
|
2985
|
-
}
|
|
2986
|
-
title: "接続先を変更",
|
|
2987
|
-
// **「追加」ではなく「保存」。** 接続先は1件しか持てない置き換えで、
|
|
2988
|
-
// 「追加」だと2つ目を足せると読める(実機テストでの指摘・複数対応は #127)
|
|
2989
|
-
submitLabel: "保存",
|
|
2990
|
-
placeholder: "https://<スペース>.backlog.jp/projects/<プロジェクトキー>",
|
|
2991
|
-
requiredNote: "BacklogのURLを入力してください(プロジェクトキーが入った形。数字だけのURLは受け付けません)。",
|
|
2992
|
-
hint: "Backlog でこの案件のプロジェクトを開き、ブラウザのURLをそのまま貼ってください(課題の画面のURLでも構いません)。APIキーは下の「Backlog のAPIキー」から別に登録します。",
|
|
2993
|
-
confirmLines: function (v) {
|
|
2994
|
-
// **解析した結果を見せる。** 貼ったURLをそのまま出すと、どのプロジェクトを
|
|
2995
|
-
// 指すことになるかが確定前に分からない。実際 /projects/PJ-CORTEX は
|
|
2996
|
-
// ハイフンで切れて PJ と解釈される(黙って別プロジェクトを指す)
|
|
2997
|
-
var t = backlogTargetOf(v);
|
|
2998
|
-
return [
|
|
2999
|
-
"この案件が同期する Backlog プロジェクトを設定します。",
|
|
3000
|
-
"",
|
|
3001
|
-
"別のプロジェクトを指すと、次の同期で別案件の課題が入ります。",
|
|
3002
|
-
"",
|
|
3003
|
-
t ? t.domain + " / " + t.projectKey : v,
|
|
3004
|
-
"",
|
|
3005
|
-
"※ 変わるのは同期先だけです。ビューアからの起票先と Webhook の配送先は別途の対応が要ります。",
|
|
3006
|
-
"",
|
|
3007
|
-
"設定しますか?",
|
|
3008
|
-
];
|
|
3009
|
-
},
|
|
3010
|
-
build: function (url) { return { op: "set-backlog-target", url: url }; },
|
|
3011
|
-
}));
|
|
3161
|
+
}
|
|
3012
3162
|
// **接続先の下に置く。** リアルタイム同期は「どのプロジェクトを見るか」が
|
|
3013
3163
|
// 決まった後の話で、順序が逆だと、接続先を設定していない案件が先に
|
|
3014
3164
|
// Webhook を貼りに行く(貼っても届かない)
|
|
@@ -3391,8 +3541,15 @@ ${capabilityState.toString()}
|
|
|
3391
3541
|
* 押した内容を「(反映中)」として先に出す。出してよいのは (1) 保存の成否がサーバで
|
|
3392
3542
|
* 確定していて推測が入らない (2) **確定した表示と見た目で区別できる**、の両方を満たす
|
|
3393
3543
|
* ときだけ。区別しないと、リロードで消えたときに「戻された」と読まれる。
|
|
3544
|
+
*
|
|
3545
|
+
* **もう1つの例外が接続先の一覧(#127)。** 追加・削除の応答にはいまの一覧が必ず
|
|
3546
|
+
* 添えられていて(成功も、重複で断られたときも、世代がずれていたときも)、これは
|
|
3547
|
+
* スナップショットではなく**サーバが書いた直後の確定値**なので、その場で描き直してよい。
|
|
3548
|
+
* @param hooks { onTargets(list) → 画面が見ていた集合と違ったか, staleNote }
|
|
3549
|
+
* onTargets … 応答に添えられた一覧を画面へ取り込む。**成否を問わず先に呼ぶ**——
|
|
3550
|
+
* 断られたときこそ、画面を正しい状態へ追いつかせないと堂々巡りになる
|
|
3394
3551
|
*/
|
|
3395
|
-
async function sendConfig(payload, msg, btn, onDone) {
|
|
3552
|
+
async function sendConfig(payload, msg, btn, onDone, hooks) {
|
|
3396
3553
|
var intake = SITE.intake;
|
|
3397
3554
|
if (!intake) return;
|
|
3398
3555
|
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
@@ -3406,10 +3563,14 @@ ${capabilityState.toString()}
|
|
|
3406
3563
|
body: JSON.stringify(payload),
|
|
3407
3564
|
});
|
|
3408
3565
|
var j = await res.json().catch(function () { return {}; });
|
|
3566
|
+
// 一覧が添えられていれば、断られていても先に画面を追いつかせる(#127)
|
|
3567
|
+
var raced = hooks && hooks.onTargets && Array.isArray(j.targets) ? hooks.onTargets(j.targets) : false;
|
|
3409
3568
|
if (!res.ok) {
|
|
3410
|
-
//
|
|
3569
|
+
// サーバの文言をそのまま出す(貼り方の案内・取り違えの警告を含んでいる)。
|
|
3570
|
+
// **例外は「他の人が先に変えていた」だけ**——サーバは再読み込みを案内するが、
|
|
3571
|
+
// 画面は既に追いついているので、済んでいることをやらせる案内になる(#127)
|
|
3411
3572
|
msg.className = "ds-toggle-msg is-error";
|
|
3412
|
-
msg.textContent = "✗ " + (j.message || "保存できませんでした。");
|
|
3573
|
+
msg.textContent = "✗ " + ((res.status === 409 && raced && hooks.staleNote) || j.message || "保存できませんでした。");
|
|
3413
3574
|
btn.disabled = false;
|
|
3414
3575
|
return;
|
|
3415
3576
|
}
|
|
@@ -3520,12 +3681,14 @@ ${capabilityState.toString()}
|
|
|
3520
3681
|
/**
|
|
3521
3682
|
* 「+…を追加」のフォーム。URLをそのまま貼れるようにする(IDだけを要求すると転記ミスが増える)。
|
|
3522
3683
|
*
|
|
3523
|
-
* **ボタンの語は呼び出し側が決める(既定は「追加」)。**
|
|
3524
|
-
*
|
|
3525
|
-
*
|
|
3526
|
-
* 押した結果の表示(+/追加済み)も同じ理由で語を揃える。
|
|
3684
|
+
* **ボタンの語は呼び出し側が決める(既定は「追加」)。** 置き換えの設定に「追加」と書くと
|
|
3685
|
+
* 2つ目を足せると読めるため、そういう口だけ submitLabel で語を変える。押した結果の
|
|
3686
|
+
* 表示(+/追加済み)も同じ理由で語を揃える。
|
|
3527
3687
|
*
|
|
3528
|
-
* @param opts { title, placeholder, hint, needsName, confirmLines, build(values), submitLabel }
|
|
3688
|
+
* @param opts { title, placeholder, hint, needsName, confirmLines, build(values), submitLabel, hooks }
|
|
3689
|
+
* hooks … sendConfig へ渡す追い付き処理を返す関数(接続先の一覧・#127)。
|
|
3690
|
+
* **これがある口では、その場の控え(「追加済み・反映中」)を出さない**——
|
|
3691
|
+
* 応答で本物の一覧が増えるので、控えを併記すると同じものが2回出る
|
|
3529
3692
|
*/
|
|
3530
3693
|
function addForm(opts) {
|
|
3531
3694
|
var main = el("input", { class: "sec-input", type: "text", placeholder: opts.placeholder, autocomplete: "off" });
|
|
@@ -3573,6 +3736,8 @@ ${capabilityState.toString()}
|
|
|
3573
3736
|
// 追加できたら入力欄を空にする(同じものを二度押ししないように)
|
|
3574
3737
|
main.value = "";
|
|
3575
3738
|
if (name) name.value = "";
|
|
3739
|
+
// 応答で本物の一覧が描き直される口では、控えを出さない(同じものが2回並ぶ)
|
|
3740
|
+
if (opts.hooks) return;
|
|
3576
3741
|
// **追加したものをその場に出す。** 上の一覧は fleet-status.json(夜間スナップショット)
|
|
3577
3742
|
// から描かれているので、追加してもその場では変わらない。何も変わらないと
|
|
3578
3743
|
// 「追加できていない」と読まれ、もう一度押して「既に登録されています」になる
|
|
@@ -3584,7 +3749,7 @@ ${capabilityState.toString()}
|
|
|
3584
3749
|
el("span", { class: "ds-pending-note", text: submitLabel + "済み・反映中" }),
|
|
3585
3750
|
]),
|
|
3586
3751
|
);
|
|
3587
|
-
});
|
|
3752
|
+
}, opts.hooks ? opts.hooks() : null);
|
|
3588
3753
|
});
|
|
3589
3754
|
|
|
3590
3755
|
// このセッションで追加したものを並べる場所(リロードすると消える=正式な一覧は上に出る)
|
package/package.json
CHANGED