aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-11-08 00:48:32 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-11-08 00:48:32 +0900
commita81bb98c971bd9470bbba0c415b6c111f5146ad3 (patch)
treebe5fc4b3280a8892c3d277c19cca588361f868f5 /scripts
parentf6bdca7dea59e81006a659676a00aae10d2cec58 (diff)
公園マップ: フィルタを分類・拡充し、地図の表示不具合を修正
ParksFilters.astro: フィルタを「遊具/広場・休憩/設備/利用ルール」に 分類し、パーゴラ・ボール遊び禁止の表示なし・犬禁止の表示なしを追加。 冒頭にβ版の注意書きを追加 ParkMap.astro: 全画面時のズーム調整と、小数ズーム時のタイル読込の 不具合を修正 ParksTable.astro: 絞り込みトークンを追加し、確認済みの表示を反映 park-editor: 確認チェックの自動同期と文字化けを修正 kodaira-parks.json: 水飲み場表記の統一と確認済みを反映 park-icons.ts: パーゴラのアイコンを追加 parks.mdx: 説明文を更新
Diffstat (limited to 'scripts')
-rw-r--r--scripts/park-editor.html53
-rw-r--r--scripts/park-editor.mjs36
2 files changed, 84 insertions, 5 deletions
diff --git a/scripts/park-editor.html b/scripts/park-editor.html
index 6c4605a..ce93b29 100644
--- a/scripts/park-editor.html
+++ b/scripts/park-editor.html
@@ -91,7 +91,7 @@
<option value="">種別: すべて</option>
</select>
<span id="checked-count" class="checked-count"></span>
- <span class="check-note" title="確認チェックはこのブラウザにのみ保存され、JSONには入りません">このブラウザのみ</span>
+ <span class="check-note" title="確認チェックは自動でJSONに書き込まれます">自動反映</span>
<button id="clear-checks" title="このデータの確認チェックをすべて外す">確認をクリア</button>
<button id="save">保存</button>
<button id="export-unchecked" title="確認チェックが付いていない行をCSVで保存します(Excel / Googleスプレッドシートで開けます)">未確認CSV</button>
@@ -367,12 +367,44 @@ function setChecked(id, key, value) {
updateCheckedCount();
}
+// 確認チェックを、その場でJSONにも反映する(サイトの「未確認」用)。
+const syncCheck = async (index, checked) => {
+ try {
+ await fetch("/api/check", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ id: currentId, updates: [{ index, checked }] }),
+ });
+ } catch {
+ // 反映に失敗しても編集は続行できるようにする。
+ }
+};
+
+// このブラウザに付いているチェックを、開いたときにJSONへ同期する。
+function syncExistingChecks() {
+ const updates = [];
+ rows.forEach((row, r) => {
+ const props = isFC ? row.properties || {} : row;
+ const key = rowKey(props, currentId);
+ if (isChecked(currentId, key) && (props["確認"] || "") !== "済") {
+ updates.push({ index: r, checked: true });
+ }
+ });
+ if (!updates.length) return;
+ fetch("/api/check", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ id: currentId, updates }),
+ }).catch(() => {});
+}
+
function updateCheckedCount() {
if (!currentId) return;
let done = 0;
rows.forEach((row) => {
const props = isFC ? row.properties || {} : row;
- if (isChecked(currentId, rowKey(props, currentId))) done++;
+ // ブラウザのチェックだけでなく、すでにJSONに書き込まれた「確認:済」も数える。
+ if (isChecked(currentId, rowKey(props, currentId)) || (props["確認"] || "") === "済") done++;
});
checkedCount.textContent = `確認済み ${done} / ${rows.length}件`;
}
@@ -461,6 +493,7 @@ async function load(id) {
rows = isFC ? data.features : data;
populateTypeFilter();
render();
+ syncExistingChecks();
showCorruptKeys();
statusEl.textContent = `${DATASETS[id].label}: ${rows.length}件`;
} catch (e) {
@@ -534,14 +567,16 @@ function render() {
cb.type = "checkbox";
cb.dataset.check = "1";
cb.dataset.key = key;
- cb.checked = isChecked(currentId, key);
- cb.title = "確認済み(このブラウザにのみ保存)";
+ cb.dataset.r = String(r);
+ cb.checked = isChecked(currentId, key) || (props["確認"] || "") === "済";
+ cb.title = "確認済み(サイトの未確認にも反映)";
cb.addEventListener("change", () => {
setChecked(currentId, key, cb.checked);
tbody.querySelectorAll("input[data-check]").forEach((other) => {
if (other.dataset.key === key) other.checked = cb.checked;
});
filter();
+ syncCheck(r, cb.checked);
});
checkTd.appendChild(cb);
tr.appendChild(checkTd);
@@ -754,6 +789,16 @@ document.getElementById("save").onclick = async function () {
}
}
collect();
+ // 確認チェックの状態をデータにも書き込む(サイトの「未確認」に反映)。
+ const checkedByKey = new Map();
+ tbody.querySelectorAll("input[data-check]").forEach((cb) => {
+ checkedByKey.set(cb.dataset.key, cb.checked);
+ });
+ rows.forEach((row) => {
+ const props = isFC ? row.properties || {} : row;
+ const key = rowKey(props, currentId);
+ props["確認"] = checkedByKey.get(key) ? "済" : "";
+ });
statusEl.textContent = "保存中…";
statusEl.classList.remove("has-warning");
try {
diff --git a/scripts/park-editor.mjs b/scripts/park-editor.mjs
index feeb059..b3a3e80 100644
--- a/scripts/park-editor.mjs
+++ b/scripts/park-editor.mjs
@@ -109,7 +109,7 @@ function canonicalKeysFrom(lists) {
return [...keys];
}
-// 「ベンチ・椅���」のような項目名の文字化けを、前後の文字から正しい項目名に戻す
+// 「ベンチ・椅子」のような項目名の文字化けを、前後の文字から正しい項目名に戻す
function normalizeRecordKeys(rec, canonicalKeys) {
const src = rec && rec.properties ? rec.properties : rec;
if (!src) return;
@@ -241,6 +241,40 @@ const server = createServer(async (req, res) => {
return;
}
+ // 確認チェックの切り替えを、その場でJSONに反映する(サイトの「未確認」用)。
+ if (req.method === "POST" && url === "/api/check") {
+ let body = "";
+ for await (const chunk of req) body += chunk;
+ const payload = JSON.parse(body);
+ const dataset = DATASETS[payload.id];
+ if (!dataset) {
+ send(res, 404, "text/plain; charset=utf-8", "not found");
+ return;
+ }
+ const data = JSON.parse(await readFile(dataset.path, "utf-8"));
+ const list = payload.id === "nearbyParks" ? data.features : data;
+ const updates = Array.isArray(payload.updates) ? payload.updates : [];
+ for (const update of updates) {
+ const rec = list[Number(update.index)];
+ if (!rec) continue;
+ const props = rec.properties || rec;
+ props["確認"] = update.checked ? "済" : "";
+ }
+ const text = dataset.pretty
+ ? `${JSON.stringify(data, null, 2)}\n`
+ : `${JSON.stringify(data)}\n`;
+ const tmp = `${dataset.path}.tmp`;
+ await writeFile(tmp, text, "utf-8");
+ await rename(tmp, dataset.path);
+ send(
+ res,
+ 200,
+ "application/json; charset=utf-8",
+ JSON.stringify({ ok: true, count: updates.length }),
+ );
+ return;
+ }
+
const target = datasetFromUrl(url);
if (!target) {
send(res, 404, "text/plain; charset=utf-8", "not found");