tarinai/js/freeze_panel.js
2026-06-25 14:51:58 +09:00

162 lines
8.7 KiB
JavaScript

"use strict";
(function (global) {
const htmlEscape = global.TarinaiUIHelpers.htmlEscape;
function spritePathFor(type = "smile") {
const sprites = (typeof SPRITES !== "undefined" ? SPRITES : (global.SPRITES || []));
const meta = sprites.find(s => s.id === type) || sprites[0];
return meta?.path || "assets/sprites/tarinai_01_smile.webp";
}
function ensureStyles() {
if (document.getElementById("freezeSystemStyles")) return;
const style = document.createElement("style");
style.id = "freezeSystemStyles";
style.textContent = `
.frozen-panel { grid-column: 1 / -1; margin-top: 8px; padding: 7px 8px; border: 1px solid rgba(82, 63, 43, 0.18); border-radius: 14px; background: rgba(244, 249, 255, 0.72); min-width: 0; }
.frozen-panel-title { display:flex; align-items:center; justify-content:space-between; gap:8px; font-weight:800; font-size:12px; color:#4f5f70; margin-bottom:5px; }
.frozen-panel-toggle { width:100%; border:0; background:transparent; color:inherit; font:inherit; display:flex; align-items:center; justify-content:space-between; gap:8px; padding:0; cursor:pointer; text-align:left; }
.frozen-panel-toggle::after { content:"\u6298\u308a\u7573\u307f"; font-size:11px; opacity:.62; font-weight:700; }
.frozen-panel.collapsed .frozen-panel-toggle::after { content:"\u5c55\u958b"; }
.frozen-panel-count { font-size:12px; opacity:.72; }
.frozen-list { display:flex; gap:6px; overflow-x:auto; padding-bottom:2px; }
.frozen-panel.collapsed .frozen-list { display:none; }
.frozen-panel.collapsed { padding-bottom:7px; }
.frozen-card { display:flex; align-items:center; gap:6px; flex:0 0 138px; min-width:138px; max-width:168px; border:1px solid rgba(72, 98, 124, .20); border-radius:11px; padding:5px 6px; background:rgba(255,255,255,.78); cursor:grab; color:#354657; text-align:left; box-shadow: 0 2px 7px rgba(41,53,65,.06); }
.frozen-card:active { cursor:grabbing; }
.frozen-card.pending-thaw { outline:2px solid rgba(70, 142, 210, .46); background:rgba(227, 242, 255, .86); }
.frozen-card img { width:26px; height:26px; object-fit:contain; flex:none; filter: drop-shadow(0 2px 3px rgba(34,44,55,.14)); }
.frozen-card-main { min-width:0; flex:1; }
.frozen-card-name { display:block; font-weight:700; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.frozen-card-meta { display:block; font-size:11px; opacity:.70; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.frozen-empty { font-size:12px; color:rgba(58,70,82,.68); padding:4px 2px; }
canvas.freeze-drop-ready { outline: 3px solid rgba(70, 142, 210, .45); outline-offset: -3px; }
`;
document.head.appendChild(style);
}
function ensurePanel() {
ensureStyles();
let panel = document.getElementById("frozenPanel");
if (panel) return panel;
panel = document.createElement("section");
panel.id = "frozenPanel";
panel.className = "frozen-panel";
panel.innerHTML = `
<div class="frozen-panel-title"><button id="frozenPanelToggle" class="frozen-panel-toggle" type="button" aria-expanded="false"><span>\u51b7\u51cd\u5eab</span><span id="frozenPanelCount" class="frozen-panel-count">0</span></button></div>
<div id="frozenList" class="frozen-list"></div>
`;
const storedCollapse = global.localStorage?.getItem("tarinaiFrozenPanelCollapsed");
const collapsedByDefault = storedCollapse !== "0";
if (collapsedByDefault) {
panel.classList.add("collapsed");
panel.querySelector("#frozenPanelToggle")?.setAttribute("aria-expanded", "false");
} else {
panel.querySelector("#frozenPanelToggle")?.setAttribute("aria-expanded", "true");
}
const operateBody = document.querySelector('[data-tool-category="operate"] .tool-category-body');
const anchor = operateBody || global.ui?.toolPalette || document.getElementById("toolPalette");
if (operateBody) operateBody.appendChild(panel);
else if (anchor?.parentNode) anchor.parentNode.insertBefore(panel, anchor.nextSibling);
else document.body.appendChild(panel);
return panel;
}
function render(entries = []) {
const panel = ensurePanel();
const listEl = panel.querySelector("#frozenList");
const countEl = panel.querySelector("#frozenPanelCount");
const list = Array.isArray(entries) ? entries : [];
if (countEl) countEl.textContent = `${list.length}`;
if (!listEl) return;
if (!list.length) {
listEl.innerHTML = `<div class="frozen-empty">\u51b7\u51cd\u4e2d\u306e\u305f\u308a\u306a\u3044\u306f\u3044\u307e\u305b\u3093\u3002\u51b7\u51cd\u3057\u305f\u305f\u308a\u306a\u3044\u306f\u30ea\u30bb\u30c3\u30c8\u3092\u7121\u8996\u3057\u3066\u4fdd\u5b58\u3067\u304d\u307e\u3059\u3002</div>`;
return;
}
listEl.innerHTML = list.map(entry => {
const data = entry.data || entry;
const gen = data.generation ? `\u7b2c${data.generation}\u4e16\u4ee3` : "";
const id = htmlEscape(entry.freezeId || entry.id || data.familyKey || "");
return `<button class="frozen-card" type="button" draggable="true" data-frozen-id="${id}" title="\u30c9\u30e9\u30c3\u30b0\u3057\u3066\u30d5\u30a3\u30fc\u30eb\u30c9\u306b\u623b\u3059">
<img src="${htmlEscape(spritePathFor(data.type || "smile"))}" alt="">
<span class="frozen-card-main"><span class="frozen-card-name">${htmlEscape(data.name || "\u305f\u308a\u306a\u3044")}</span><span class="frozen-card-meta">${htmlEscape(gen)}</span></span>
</button>`;
}).join("");
}
function markPending(id = "") {
const panel = ensurePanel();
for (const c of panel.querySelectorAll(".frozen-card")) c.classList.toggle("pending-thaw", c.dataset.frozenId === id);
}
function clearPending() {
document.querySelectorAll(".frozen-card.pending-thaw").forEach(el => el.classList.remove("pending-thaw"));
}
function bind({ onSelect, onDrop } = {}) {
const panel = ensurePanel();
if (panel.dataset.freezeBound !== "1") {
panel.dataset.freezeBound = "1";
panel.addEventListener("dragstart", (e) => {
const card = e.target.closest(".frozen-card[data-frozen-id]");
if (!card) return;
const id = card.dataset.frozenId || "";
e.dataTransfer?.setData("text/plain", `tarinai-frozen:${id}`);
e.dataTransfer?.setData("application/x-tarinai-frozen", id);
e.dataTransfer.effectAllowed = "move";
global.document?.querySelector("canvas")?.classList.add("freeze-drop-ready");
});
panel.addEventListener("dragend", () => global.document?.querySelector("canvas")?.classList.remove("freeze-drop-ready"));
panel.addEventListener("click", (e) => {
const toggle = e.target.closest("#frozenPanelToggle");
if (toggle) {
const collapsed = panel.classList.toggle("collapsed");
toggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
try { global.localStorage?.setItem("tarinaiFrozenPanelCollapsed", collapsed ? "1" : "0"); } catch (_) {}
global.audio?.uiFold?.();
return;
}
const card = e.target.closest(".frozen-card[data-frozen-id]");
if (!card) return;
const id = card.dataset.frozenId || "";
markPending(id);
onSelect?.(id);
});
}
const canvas = global.canvas || document.getElementById("gameCanvas");
if (canvas && canvas.dataset.freezeDropBound !== "1") {
canvas.dataset.freezeDropBound = "1";
canvas.addEventListener("dragover", (e) => {
const text = e.dataTransfer?.types?.includes?.("application/x-tarinai-frozen") || [...(e.dataTransfer?.types || [])].includes("text/plain");
if (!text) return;
e.preventDefault();
e.dataTransfer.dropEffect = "move";
canvas.classList.add("freeze-drop-ready");
});
canvas.addEventListener("dragleave", () => canvas.classList.remove("freeze-drop-ready"));
canvas.addEventListener("drop", (e) => {
const id = e.dataTransfer?.getData("application/x-tarinai-frozen") || String(e.dataTransfer?.getData("text/plain") || "").replace(/^tarinai-frozen:/, "");
if (!id) return;
e.preventDefault();
canvas.classList.remove("freeze-drop-ready");
const p = typeof global.screenToWorld === "function" ? global.screenToWorld(e) : (() => {
const rect = canvas.getBoundingClientRect();
const sx = e.clientX - rect.left;
const sy = e.clientY - rect.top;
const worldRef = global.world;
return worldRef?.screenToWorld ? worldRef.screenToWorld(sx, sy) : { x: sx, y: sy, inside: true };
})();
onDrop?.(id, p.x, p.y);
});
}
}
global.TarinaiFreezePanel = {
ensurePanel,
render,
bind,
markPending,
clearPending,
};
})(typeof window !== "undefined" ? window : globalThis);