|
| 1 | +const SPRITES_API_PATH = "/api/sprites/records"; |
| 2 | + |
| 3 | +const elements = { |
| 4 | + apiStatus: document.querySelector("[data-sprites-api-status]"), |
| 5 | + count: document.querySelector("[data-sprites-count]"), |
| 6 | + emptyState: document.querySelector("[data-sprites-empty-state]"), |
| 7 | + errorState: document.querySelector("[data-sprites-error-state]"), |
| 8 | + libraryStatus: document.querySelector("[data-sprites-library-status]"), |
| 9 | + metadata: document.querySelector("[data-sprites-metadata]"), |
| 10 | + outputStatus: document.querySelector("[data-sprites-output-status]"), |
| 11 | + outputSummary: document.querySelector("[data-sprites-output-summary]"), |
| 12 | + paletteStatus: document.querySelector("[data-sprites-palette-status]"), |
| 13 | + refresh: document.querySelector("[data-sprites-refresh]"), |
| 14 | + tableBody: document.querySelector("[data-sprites-table-body]"), |
| 15 | + updated: document.querySelector("[data-sprites-updated]"), |
| 16 | +}; |
| 17 | + |
| 18 | +function setText(target, value) { |
| 19 | + if (target) { |
| 20 | + target.textContent = value; |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +function setHidden(target, hidden) { |
| 25 | + if (target) { |
| 26 | + target.hidden = hidden; |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +function createCell(value) { |
| 31 | + const cell = document.createElement("td"); |
| 32 | + cell.textContent = value; |
| 33 | + return cell; |
| 34 | +} |
| 35 | + |
| 36 | +function createHeaderCell(value) { |
| 37 | + const cell = document.createElement("th"); |
| 38 | + cell.scope = "row"; |
| 39 | + cell.textContent = value; |
| 40 | + return cell; |
| 41 | +} |
| 42 | + |
| 43 | +function normalizeText(value, fallback = "Unavailable") { |
| 44 | + const text = String(value ?? "").trim(); |
| 45 | + return text || fallback; |
| 46 | +} |
| 47 | + |
| 48 | +function formatTimestamp(value) { |
| 49 | + const text = String(value ?? "").trim(); |
| 50 | + if (!text) { |
| 51 | + return "Unavailable"; |
| 52 | + } |
| 53 | + const date = new Date(text); |
| 54 | + if (Number.isNaN(date.getTime())) { |
| 55 | + return text; |
| 56 | + } |
| 57 | + return date.toLocaleString(); |
| 58 | +} |
| 59 | + |
| 60 | +function formatDimensions(sprite) { |
| 61 | + const width = Number(sprite?.width ?? sprite?.dimensions?.width); |
| 62 | + const height = Number(sprite?.height ?? sprite?.dimensions?.height); |
| 63 | + if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) { |
| 64 | + return "Unavailable"; |
| 65 | + } |
| 66 | + return `${width} x ${height}`; |
| 67 | +} |
| 68 | + |
| 69 | +function formatSource(sprite) { |
| 70 | + return normalizeText(sprite?.sourceName || sprite?.sourcePath || sprite?.storagePath || sprite?.storageKey || sprite?.sourceStorageReference); |
| 71 | +} |
| 72 | + |
| 73 | +function paletteKeysFor(sprite) { |
| 74 | + if (Array.isArray(sprite?.paletteColorKeys)) { |
| 75 | + return sprite.paletteColorKeys.map((key) => String(key || "").trim()).filter(Boolean); |
| 76 | + } |
| 77 | + if (Array.isArray(sprite?.palette_color_keys)) { |
| 78 | + return sprite.palette_color_keys.map((key) => String(key || "").trim()).filter(Boolean); |
| 79 | + } |
| 80 | + return []; |
| 81 | +} |
| 82 | + |
| 83 | +function usageCountFor(sprite) { |
| 84 | + const count = Number(sprite?.usageCount ?? sprite?.usage_count ?? sprite?.references?.length); |
| 85 | + return Number.isFinite(count) && count >= 0 ? String(count) : "0"; |
| 86 | +} |
| 87 | + |
| 88 | +function spriteRowsFromPayload(payload) { |
| 89 | + if (Array.isArray(payload?.data?.sprites)) { |
| 90 | + return payload.data.sprites; |
| 91 | + } |
| 92 | + if (Array.isArray(payload?.sprites)) { |
| 93 | + return payload.sprites; |
| 94 | + } |
| 95 | + return []; |
| 96 | +} |
| 97 | + |
| 98 | +function renderLoading() { |
| 99 | + setText(elements.apiStatus, "Loading"); |
| 100 | + setText(elements.libraryStatus, "Loading"); |
| 101 | + setText(elements.outputStatus, "Loading"); |
| 102 | + setText(elements.outputSummary, "Waiting for Sprites API response."); |
| 103 | + setText(elements.emptyState, "Loading Sprites records."); |
| 104 | + setText(elements.updated, "Checking"); |
| 105 | + setHidden(elements.emptyState, false); |
| 106 | + setHidden(elements.errorState, true); |
| 107 | + if (elements.tableBody) { |
| 108 | + const row = document.createElement("tr"); |
| 109 | + const cell = createCell("Loading Sprites records."); |
| 110 | + cell.colSpan = 8; |
| 111 | + row.append(cell); |
| 112 | + elements.tableBody.replaceChildren(row); |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +function renderUnavailable(message) { |
| 117 | + const detail = normalizeText(message, "Sprites API unavailable."); |
| 118 | + setText(elements.apiStatus, "Unavailable"); |
| 119 | + setText(elements.libraryStatus, "Unavailable"); |
| 120 | + setText(elements.outputStatus, "Unavailable"); |
| 121 | + setText(elements.outputSummary, detail); |
| 122 | + setText(elements.emptyState, "Sprites records cannot be loaded from the API yet."); |
| 123 | + setText(elements.errorState, detail); |
| 124 | + setText(elements.metadata, "Sprite metadata unavailable until the Sprites API responds."); |
| 125 | + setText(elements.paletteStatus, "Palette/Colors references unavailable until Sprites records load from the API."); |
| 126 | + setText(elements.updated, new Date().toLocaleTimeString()); |
| 127 | + setHidden(elements.emptyState, false); |
| 128 | + setHidden(elements.errorState, false); |
| 129 | + if (elements.tableBody) { |
| 130 | + const row = document.createElement("tr"); |
| 131 | + const cell = createCell("Sprites API unavailable."); |
| 132 | + cell.colSpan = 8; |
| 133 | + row.append(cell); |
| 134 | + elements.tableBody.replaceChildren(row); |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +function renderPaletteStatus(sprites) { |
| 139 | + const referencedKeys = new Set(); |
| 140 | + sprites.forEach((sprite) => { |
| 141 | + paletteKeysFor(sprite).forEach((key) => referencedKeys.add(key)); |
| 142 | + }); |
| 143 | + if (referencedKeys.size === 0) { |
| 144 | + setText(elements.paletteStatus, "No Palette/Colors references in current Sprites records."); |
| 145 | + return; |
| 146 | + } |
| 147 | + setText(elements.paletteStatus, `${referencedKeys.size} Palette/Colors key reference${referencedKeys.size === 1 ? "" : "s"} surfaced from API records.`); |
| 148 | +} |
| 149 | + |
| 150 | +function renderRows(sprites) { |
| 151 | + if (!elements.tableBody) { |
| 152 | + return; |
| 153 | + } |
| 154 | + if (sprites.length === 0) { |
| 155 | + const row = document.createElement("tr"); |
| 156 | + const cell = createCell("No Sprites records returned by the API."); |
| 157 | + cell.colSpan = 8; |
| 158 | + row.append(cell); |
| 159 | + elements.tableBody.replaceChildren(row); |
| 160 | + return; |
| 161 | + } |
| 162 | + |
| 163 | + const rows = sprites.map((sprite) => { |
| 164 | + const row = document.createElement("tr"); |
| 165 | + const paletteKeys = paletteKeysFor(sprite); |
| 166 | + row.dataset.spritesRowKey = normalizeText(sprite?.key, ""); |
| 167 | + row.append( |
| 168 | + createHeaderCell(normalizeText(sprite?.name)), |
| 169 | + createCell(normalizeText(sprite?.status)), |
| 170 | + createCell(normalizeText(sprite?.category, "None")), |
| 171 | + createCell(formatSource(sprite)), |
| 172 | + createCell(formatDimensions(sprite)), |
| 173 | + createCell(paletteKeys.length ? paletteKeys.join(", ") : "None"), |
| 174 | + createCell(formatTimestamp(sprite?.updatedAt ?? sprite?.updated_at)), |
| 175 | + createCell(usageCountFor(sprite)) |
| 176 | + ); |
| 177 | + row.addEventListener("click", () => { |
| 178 | + const key = normalizeText(sprite?.key, "Unavailable"); |
| 179 | + const mimeType = normalizeText(sprite?.mimeType ?? sprite?.mime_type, "Unavailable"); |
| 180 | + const sizeBytes = normalizeText(sprite?.sizeBytes ?? sprite?.size_bytes, "Unavailable"); |
| 181 | + setText(elements.metadata, `${normalizeText(sprite?.name)} (${key}) | ${mimeType} | ${formatDimensions(sprite)} | ${sizeBytes} bytes`); |
| 182 | + }); |
| 183 | + return row; |
| 184 | + }); |
| 185 | + elements.tableBody.replaceChildren(...rows); |
| 186 | +} |
| 187 | + |
| 188 | +function renderSprites(payload) { |
| 189 | + const sprites = spriteRowsFromPayload(payload); |
| 190 | + const count = sprites.length; |
| 191 | + setText(elements.apiStatus, "Ready"); |
| 192 | + setText(elements.libraryStatus, count > 0 ? "Ready" : "Empty"); |
| 193 | + setText(elements.count, String(count)); |
| 194 | + setText(elements.outputStatus, count > 0 ? "Ready" : "Empty"); |
| 195 | + setText(elements.outputSummary, count > 0 ? `${count} sprite record${count === 1 ? "" : "s"} loaded from the API.` : "Sprites API responded with no records."); |
| 196 | + setText(elements.emptyState, count > 0 ? "" : "No Sprites records returned by the API."); |
| 197 | + setText(elements.updated, new Date().toLocaleTimeString()); |
| 198 | + setText(elements.metadata, count > 0 ? "Select a sprite row to review its metadata." : "No sprite metadata available yet."); |
| 199 | + setHidden(elements.emptyState, count > 0); |
| 200 | + setHidden(elements.errorState, true); |
| 201 | + renderPaletteStatus(sprites); |
| 202 | + renderRows(sprites); |
| 203 | +} |
| 204 | + |
| 205 | +async function loadSprites() { |
| 206 | + renderLoading(); |
| 207 | + try { |
| 208 | + const response = await fetch(SPRITES_API_PATH, { |
| 209 | + cache: "no-store", |
| 210 | + headers: { accept: "application/json" }, |
| 211 | + }); |
| 212 | + let payload = null; |
| 213 | + try { |
| 214 | + payload = await response.json(); |
| 215 | + } catch { |
| 216 | + payload = null; |
| 217 | + } |
| 218 | + if (!response.ok || payload?.ok === false) { |
| 219 | + const message = payload?.error?.message || payload?.message || `Sprites API returned ${response.status}.`; |
| 220 | + renderUnavailable(message); |
| 221 | + return; |
| 222 | + } |
| 223 | + renderSprites(payload || {}); |
| 224 | + } catch (error) { |
| 225 | + renderUnavailable(error instanceof Error ? error.message : "Sprites API request failed."); |
| 226 | + } |
| 227 | +} |
| 228 | + |
| 229 | +elements.refresh?.addEventListener("click", () => { |
| 230 | + void loadSprites(); |
| 231 | +}); |
| 232 | + |
| 233 | +void loadSprites(); |
0 commit comments