diff --git a/module.json b/module.json index 55052a4..263f879 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "kosmos-storage-audit", "title": "Kosmos Storage Audit", "description": "Analyzes media references and risky storage locations across Foundry data and public roots.", - "version": "0.0.13", + "version": "0.0.14", "compatibility": { "minimum": "13", "verified": "13" diff --git a/scripts/adapters/foundry-runtime.js b/scripts/adapters/foundry-runtime.js index b22fa60..79e0b3b 100644 --- a/scripts/adapters/foundry-runtime.js +++ b/scripts/adapters/foundry-runtime.js @@ -141,6 +141,12 @@ function resolveSourceTrail(document, candidatePath = []) { return trail; } + if (document.documentName === "Playlist") { + const soundNode = resolvePlaylistSoundTrail(document, path); + if (soundNode) trail.push(soundNode); + return trail; + } + if (document.documentName === "Scene") { const sceneTrail = resolveSceneTrail(document, path); if (sceneTrail.length) trail.push(...sceneTrail); @@ -150,14 +156,25 @@ function resolveSourceTrail(document, candidatePath = []) { } function resolveActorItemTrail(actor, path) { - if ((path[0] !== "items") || !Number.isInteger(path[1])) return null; - const itemData = actor.items?.contents?.[path[1]] ?? actor.toObject?.().items?.[path[1]] ?? null; + const itemIndex = findEmbeddedIndex(path, "items"); + if (itemIndex == null) return null; + const itemData = actor.items?.contents?.[itemIndex] ?? actor.toObject?.().items?.[itemIndex] ?? null; const itemId = itemData?.id ?? itemData?._id ?? null; if (!itemId) return null; const itemName = itemData?.name ?? `Item ${itemId}`; return createTrailNode(`${actor.uuid}.Item.${itemId}`, itemName); } +function resolvePlaylistSoundTrail(playlist, path) { + const soundIndex = findEmbeddedIndex(path, "sounds"); + if (soundIndex == null) return null; + const soundData = playlist.sounds?.contents?.[soundIndex] ?? playlist.toObject?.().sounds?.[soundIndex] ?? null; + const soundId = soundData?.id ?? soundData?._id ?? null; + if (!soundId) return null; + const soundName = soundData?.name ?? `PlaylistSound ${soundId}`; + return createTrailNode(`${playlist.uuid}.PlaylistSound.${soundId}`, soundName); +} + function resolveSceneTrail(scene, path) { if ((path[0] !== "tokens") || !Number.isInteger(path[1])) return []; const token = scene.tokens?.contents?.[path[1]] ?? scene.toObject?.().tokens?.[path[1]] ?? null; @@ -180,12 +197,10 @@ function resolveSceneTrail(scene, path) { } function extractTokenItemPath(path) { - if ((path[2] === "actorData") && (path[3] === "items") && Number.isInteger(path[4])) { - return { itemIndex: path[4] }; - } - if ((path[2] === "delta") && (path[3] === "items") && Number.isInteger(path[4])) { - return { itemIndex: path[4] }; - } + const actorDataIndex = findEmbeddedIndex(path.slice(2), "items"); + if ((path[2] === "actorData") && (actorDataIndex != null)) return { itemIndex: actorDataIndex }; + const deltaIndex = findEmbeddedIndex(path.slice(2), "items"); + if ((path[2] === "delta") && (deltaIndex != null)) return { itemIndex: deltaIndex }; return null; } @@ -199,3 +214,10 @@ function resolveTokenItemData(tokenData, itemIndex) { function createTrailNode(uuid, label) { return { uuid, label }; } + +function findEmbeddedIndex(path, segment) { + const index = path.indexOf(segment); + if (index === -1) return null; + const candidate = path[index + 1]; + return Number.isInteger(candidate) ? candidate : null; +} diff --git a/styles/audit.css b/styles/audit.css index a74411f..ab11cdc 100644 --- a/styles/audit.css +++ b/styles/audit.css @@ -120,13 +120,14 @@ .storage-audit__actions { display: flex; - flex-direction: column; + flex-direction: row; + flex-wrap: wrap; gap: 0.75rem; - align-items: stretch; + align-items: center; + justify-content: flex-end; min-width: 0; width: 100%; - max-width: 18rem; - justify-self: end; + max-width: none; } .storage-audit__actions .button { @@ -138,6 +139,7 @@ padding: 0.62rem 0.95rem; font-weight: 700; min-height: 2.55rem; + flex: 0 1 16rem; } .storage-audit__actions .button[disabled] { @@ -283,8 +285,11 @@ @media (max-width: 1100px) { .storage-audit__actions { width: 100%; - max-width: none; - justify-self: stretch; + justify-content: flex-start; + } + + .storage-audit__actions .button { + flex: 1 1 14rem; } }