diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bb4614 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.code-workspace \ No newline at end of file diff --git a/README.md b/README.md index a86b926..39d7958 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ -# Resonite-Inventory-Viewer +# Resonite Storage Browser -A web-app that neatly displays the contents of a Resonite inventory. \ No newline at end of file +This is a simple in-browser inventory viewer for Resonite. The goal for this app is to make it easier to find out what is eating so much storage space in a given account. + +## Instructions + +This website uses a JSON file produced by Resonite. In order to use this tool, you need to get your "Record Usage JSON" file. + +To obtain this file you need to login to your Resonite account and send the Resonite bot the message `/requestRecordUsageJSON`. After you submit this command, Resonite will email you shortly after with an attachment that contains the file you will need. + +Once you have your file, continue to [the website](https://armored-dragon.github.io/Resonite-Inventory-Viewer/) and select the `.json` file in the file browser input, and this app should take care of the rest. diff --git a/index.css b/index.css new file mode 100644 index 0000000..1482443 --- /dev/null +++ b/index.css @@ -0,0 +1,266 @@ +body { + margin: 0; + background-color: #11151d; + height: 100vh; + display: flex; + color: #e1e1e0; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.rounded-corners { + border-radius: 10px; +} + +.page { + width: 1200px; + margin: 0 auto; + display: flex; + flex-direction: column; +} +.page .import-notice { + margin: auto; + width: 600px; + min-height: 100px; + background-color: #2b2f35; + padding: 5px; + box-sizing: border-box; + display: flex; + flex-direction: column; +} +.page .import-notice h1 { + color: #e1e1e0; + font-weight: bold; + font-size: 22px; + text-align: center; +} +.page .import-notice input { + margin: auto; + color: #cecece; + text-align: center; + padding: 5px; + box-sizing: border-box; + background-color: rgba(0, 0, 0, 0.3137254902); + margin-bottom: 15px; +} +.page .import-notice sub { + text-align: center; + color: #e1e1e0; +} +.page .import-notice sub a { + color: #ff6600; +} +.page .file-list { + display: flex; + flex-direction: column; +} +.page .file-list .storage-overview { + background-color: #2b2f35; + width: 100%; + margin-top: 20px; + padding: 10px; + box-sizing: border-box; + display: flex; + flex-direction: column; +} +.page .file-list .storage-overview .title { + font-size: 24px; + font-weight: bold; + margin-bottom: 10px; + display: flex; + flex-direction: row; +} +.page .file-list .storage-overview .title .measurement { + margin-left: auto; + font-size: 22px; +} +.page .file-list .storage-overview .title .measurement .storage-quota { + font-size: 16px; + color: #86888b; +} +.page .file-list .storage-overview .storage-bar { + height: 30px; + width: 100%; + background-color: rgba(0, 0, 0, 0.6392156863); + padding: 5px; + box-sizing: border-box; + border: 0; +} +.page .file-list .storage-overview .storage-bar::-moz-progress-bar, +.page .file-list .storage-overview .storage-bar::-webkit-progress-bar { + background-color: rgb(0, 162, 255); + border-radius: 5px; +} +.page .file-list .filters { + width: 100%; + height: 30px; + margin-top: 20px; + display: flex; + flex-direction: row; +} +.page .file-list .filters select { + background-color: #2b2f35; + border: 0; + padding: 0 10px; + width: 200px; + height: 100%; + color: white; + margin-right: 5px; +} +.page .file-list .filters label { + margin: auto 0; +} +.page .file-list .filters .search { + width: 50%; + height: 100%; + background-color: #2b2f35; + margin-left: auto; +} +.page .file-list .filters .search input { + background-color: rgba(0, 0, 0, 0.6392156863); + border: 0; + padding: 0; + outline: 0; + color: white; + height: 100%; + width: 100%; + text-indent: 10px; +} +.page .file-list .filters .search input::-moz-placeholder { + font-style: italic; +} +.page .file-list .filters .search input::placeholder { + font-style: italic; +} +.page .file-list .item-list { + display: flex; + flex-flow: row wrap; + place-content: space-around; + width: 100%; + height: -moz-fit-content; + height: fit-content; + margin-top: 20px; +} +.page .file-list .item-list .entry { + width: 200px; + background-color: #2b2f35; + padding: 5px; + margin-bottom: 20px; +} +.page .file-list .item-list .entry .thumbnail { + width: 200px; + height: 200px; + margin: 0 auto 5px auto; + display: flex; +} +.page .file-list .item-list .entry .thumbnail img { + max-height: 100%; + max-width: 100%; + margin: auto; +} +.page .file-list .item-list .entry .info .title { + margin-bottom: 10px; + color: #61d1fa; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.page .file-list .item-list .entry .info .info-blob { + color: gray; + display: flex; + flex-direction: row; + margin-bottom: 5px; +} +.page .file-list .item-list .entry .info .info-blob .value { + color: white; + margin-left: auto; +} + +dialog { + background-color: #2b2f35; + border: 0; + outline: 0; +} +dialog .content { + display: flex; + flex-direction: column; + width: -moz-fit-content; + width: fit-content; +} +dialog .content .info { + display: flex; + flex-direction: row; + width: 700px; +} +dialog .content .info .left { + margin-right: 10px; +} +dialog .content .info .left .thumbnail { + width: 200px; + height: 200px; +} +dialog .content .info .left .thumbnail img { + max-width: 100%; + max-height: 100%; +} +dialog .content .info .right { + flex-grow: 1; +} +dialog .content .info .right .entry { + display: flex; + flex-direction: row; + color: white; + width: 100%; + background-color: #1d2024; + padding: 5px; + box-sizing: border-box; +} +dialog .content .info .right .entry .title { + color: #bbb; +} +dialog .content .info .right .entry .value { + margin-left: auto; + padding-left: 20px; +} +dialog .content .info .right .entry:nth-child(even) { + background-color: #191c20; +} +dialog .content .controls { + display: flex; + margin-top: 15px; +} +dialog .content .controls button { + margin: auto; + padding: 5px; + box-sizing: border-box; + flex-grow: 1; + cursor: pointer; + background-color: #ff7676; + border: 0; + font-weight: bold; +} + +dialog::backdrop { + background-color: rgba(0, 0, 0, 0.6392156863); +} + +.hidden { + display: none !important; +} + +@media screen and (max-width: 1200px) { + .page { + width: 95%; + } +} +@media screen and (max-width: 800px) { + dialog .content .info { + flex-direction: column; + width: 100%; + } + dialog .content .info .left .thumbnail { + margin: auto; + width: 100%; + margin-bottom: 10px; + } +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..13ae421 --- /dev/null +++ b/index.html @@ -0,0 +1,106 @@ + + + + + + + Resonite Inventory Viewer + + + +
+
+
+
+ +
+
+
+
+ Name + +
+
+ ID + +
+
+ Size + +
+
+ Path + +
+
+ Assets + +
+
+ Unique Assets + +
+
+ Message Item? + +
+
+
+
+ +
+
+
+ +
+
+

Select your inventory .JSON file

+ + Inventory browser created by Armored Dragon +
+ + +
+ + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..3adfb66 --- /dev/null +++ b/index.js @@ -0,0 +1,147 @@ +let resonite_item_list = []; +let asset_list = []; // An "Asset" is a linked asset per each item. One item can have lots of assets. +let total_bytes = 0; + +// QOL functions +const qs = (selector) => document.querySelector(selector); +const qsa = (selector) => document.querySelectorAll(selector); + +// Elements +const file_import_input = qs("#file-import"); +const item_list = qs("#file-browser .item-list"); +const item_template = qs("#entry-template"); + +// Modal +const item_modal = qs("#item-info-modal"); +const modal_name = qs("#item-info-modal [data-name]"); +const modal_id = qs("#item-info-modal [data-id]"); +const modal_size = qs("#item-info-modal [data-size]"); +const modal_path = qs("#item-info-modal [data-path]"); +const modal_assets = qs("#item-info-modal [data-assets]"); +const modal_unique_assets = qs("#item-info-modal [data-unique-assets]"); +const modal_message_item = qs("#item-info-modal [data-message-item]"); +const modal_thumbnail = qs("#item-info-modal [data-thumbnail]"); + +const only_message_items_setting = qs("#only-message-items"); +const search_setting = qs("#search"); + +// Event Listeners +file_import_input.addEventListener("change", newInventoryFile); +only_message_items_setting.addEventListener("change", filterChange); +search_setting.addEventListener("change", filterChange); + +// Logic +function newInventoryFile() { + // Show the file list + qs("#file-selection-modal").classList.add("hidden"); + qs("#file-browser").classList.remove("hidden"); + + // Get the file + if (file_import_input.files.length <= 0) return; + let reader = new FileReader(); + reader.addEventListener("load", () => handleFileContents(JSON.parse(reader.result))); + reader.readAsText(file_import_input.files[0]); +} + +function handleFileContents(inventory_json) { + asset_list = []; // Reset the asset list + item_list.innerHTML = ""; // Clear the asset list + + inventory_json.forEach(newInventoryEntry); + + // Calculate storage overview values + qs("#total-used-space").innerText = `${(total_bytes / 1000000).toFixed(2)} MB`; + qs(".storage-overview progress").value = total_bytes / 1000000000; +} + +function filterChange() { + let settings = { + only_message_items: qs("#only-message-items").checked, + search: search_setting.value.toLowerCase(), + }; + + let filtered_list = []; + + resonite_item_list.forEach((item) => { + if (settings.only_message_items && !item.message_item) return; + if (search && !item.name.toLowerCase().includes(settings.search)) return; + + filtered_list.push(item); + }); + + item_list.innerHTML = ""; // Clear the asset list + + filtered_list.forEach(addItemToView); +} + +function newInventoryEntry(item) { + let estimate_size = 0; + let assets = 0; + let unique_assets = 0; + let thumbnail_url = ""; + + // Get the linked assets of the item + item.assetManifest.forEach((asset) => { + const in_array = asset_list.some((obj) => obj.hash === asset.hash); + assets++; + + if (!in_array) { + asset_list.push(asset); + estimate_size += asset.bytes; + total_bytes += asset.bytes; + unique_assets++; + } + }); + + // Get the thumbnail + if (item.thumbnailUri) thumbnail_url = `https://assets.resonite.com/${item.thumbnailUri.replace("resdb:///", "").replace(".webp", "")}`; + + // Format the list to allow the user to easily add additional filters + const item_info = { + name: item.name, + directory: item.path, + estimate_size: (estimate_size / 1000000).toFixed(2), + assets: assets, + unique_assets: unique_assets, + message_item: item.tags.includes("message_item"), + id: item.id, + thumbnail_url: thumbnail_url, + }; + + resonite_item_list.push(item_info); + addItemToView(item_info); +} +function addItemToView(item_info) { + // Add the item to the view + const item_html = item_template.content.cloneNode(true); + + // Edit the contents of the template + item_html.querySelector(".thumbnail img").src = item_info.thumbnail_url; + item_html.querySelector(".title").innerText = `${item_info.name}`; + item_html.querySelectorAll(".info-blob .value")[0].innerText = `${item_info.estimate_size} MB`; + item_html.querySelectorAll(".info-blob .value")[1].innerText = `${item_info.assets}`; + item_html.querySelectorAll(".info-blob .value")[2].innerText = `${item_info.unique_assets}`; + item_html.querySelector(".id").innerText = item_info.id; + + // Add to the list + item_list.appendChild(item_html); + + item_list.children[item_list.children.length - 1].setAttribute("onclick", `showItemInModal(${JSON.stringify(item_info)})`); +} + +function showItemInModal(item_info) { + modal_name.innerText = item_info.name; + modal_id.innerText = item_info.id; + modal_size.innerText = `${item_info.estimate_size} MB`; + modal_path.innerText = item_info.directory; + modal_assets.innerText = item_info.assets; + modal_unique_assets.innerText = item_info.unique_assets; + modal_message_item.innerText = item_info.message_item; + modal_thumbnail.src = item_info.thumbnail_url; + + item_modal.showModal(); +} + +function closeItemModal() { + item_modal.close(); +} diff --git a/index.scss b/index.scss new file mode 100644 index 0000000..4b93549 --- /dev/null +++ b/index.scss @@ -0,0 +1,316 @@ +$n-primary: #11151d; +$n-secondary: #2b2f35; +$n-tertiary: #86888b; +$n-quaternary: #e1e1e0; // WHY IS IT 0? OCD #triggered + +$h-yellow: #f8f770; +$h-green: #59eb5c; +$h-red: #ff7676; +$h-purple: #ba64f2; +$h-blue: #61d1fa; +$h-orange: #e69e50; + +$bg-stacker: #000000a3; + +body { + margin: 0; + background-color: #11151d; + height: 100vh; + display: flex; + color: $n-quaternary; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.rounded-corners { + border-radius: 10px; +} + +.page { + width: 1200px; + margin: 0 auto; + display: flex; + flex-direction: column; + + .import-notice { + margin: auto; + width: 600px; + min-height: 100px; + background-color: #2b2f35; + + padding: 5px; + box-sizing: border-box; + display: flex; + flex-direction: column; + + h1 { + color: $n-quaternary; + font-weight: bold; + font-size: 22px; + text-align: center; + } + + input { + margin: auto; + color: #cecece; + text-align: center; + padding: 5px; + box-sizing: border-box; + background-color: #00000050; + margin-bottom: 15px; + } + + sub { + text-align: center; + color: $n-quaternary; + + a { + color: #ff6600; + } + } + } + + .file-list { + display: flex; + flex-direction: column; + + .storage-overview { + background-color: $n-secondary; + width: 100%; + margin-top: 20px; + padding: 10px; + box-sizing: border-box; + display: flex; + flex-direction: column; + + .title { + font-size: 24px; + font-weight: bold; + margin-bottom: 10px; + display: flex; + flex-direction: row; + + .measurement { + margin-left: auto; + font-size: 22px; + + .storage-quota { + font-size: 16px; + color: $n-tertiary; + } + } + } + + .storage-bar { + height: 30px; + width: 100%; + background-color: $bg-stacker; + padding: 5px; + box-sizing: border-box; + border: 0; + } + .storage-bar::-moz-progress-bar, + .storage-bar::-webkit-progress-bar { + background-color: rgb(0, 162, 255); + border-radius: 5px; + } + } + + .filters { + width: 100%; + height: 30px; + margin-top: 20px; + display: flex; + flex-direction: row; + + select { + background-color: $n-secondary; + border: 0; + padding: 0 10px; + width: 200px; + height: 100%; + color: white; + margin-right: 5px; + } + + label { + margin: auto 0; + } + + .search { + width: 50%; + height: 100%; + background-color: $n-secondary; + margin-left: auto; + + input { + background-color: $bg-stacker; + border: 0; + padding: 0; + outline: 0; + color: white; + height: 100%; + width: 100%; + text-indent: 10px; + } + + input::placeholder { + font-style: italic; + } + } + } + + .item-list { + display: flex; + flex-flow: row wrap; + place-content: space-around; + width: 100%; + height: fit-content; + margin-top: 20px; + + .entry { + width: 200px; + // height: 200px; + background-color: $n-secondary; + padding: 5px; + margin-bottom: 20px; + + .thumbnail { + width: 200px; + height: 200px; + margin: 0 auto 5px auto; + display: flex; + + img { + max-height: 100%; + max-width: 100%; + margin: auto; + } + } + + .info { + .title { + margin-bottom: 10px; + color: $h-blue; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .info-blob { + color: gray; + display: flex; + flex-direction: row; + margin-bottom: 5px; + + .value { + color: white; + margin-left: auto; + } + } + } + } + } + } +} + +dialog { + background-color: $n-secondary; + border: 0; + outline: 0; + .content { + display: flex; + flex-direction: column; + width: fit-content; + + .info { + display: flex; + flex-direction: row; + width: 700px; + + .left { + margin-right: 10px; + .thumbnail { + width: 200px; + height: 200px; + + img { + max-width: 100%; + max-height: 100%; + } + } + } + + .right { + flex-grow: 1; + .entry { + display: flex; + flex-direction: row; + color: white; + width: 100%; + background-color: #1d2024; + padding: 5px; + box-sizing: border-box; + + .title { + color: #bbb; + } + .value { + margin-left: auto; + padding-left: 20px; + } + } + + .entry:nth-child(even) { + background-color: #191c20; + } + } + } + + .controls { + display: flex; + margin-top: 15px; + button { + margin: auto; + padding: 5px; + box-sizing: border-box; + flex-grow: 1; + cursor: pointer; + background-color: $h-red; + border: 0; + font-weight: bold; + } + } + } +} +dialog::backdrop { + background-color: $bg-stacker; +} + +.hidden { + display: none !important; +} + +@media screen and (max-width: 1200px) { + .page { + width: 95%; + } +} + +@media screen and (max-width: 800px) { + dialog { + .content { + .info { + flex-direction: column; + width: 100%; + + .left { + .thumbnail { + margin: auto; + width: 100%; + margin-bottom: 10px; + } + } + } + } + } +}