Files
novafarma/tools/visualizers/visual_asset_manager.html
2026-01-20 01:05:17 +01:00

798 lines
23 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="sl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎨 Visual Asset Manager - DolinaSmrti</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: linear-gradient(135deg, #0f0c1d 0%, #1a1333 50%, #2d1b3d 100%);
color: #fff;
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar */
.sidebar {
width: 280px;
background: rgba(20, 20, 40, 0.95);
border-right: 2px solid #9D4EDD;
padding: 20px;
overflow-y: auto;
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.5);
}
.sidebar h2 {
color: #9D4EDD;
margin-bottom: 20px;
font-size: 1.5em;
text-shadow: 0 0 10px rgba(157, 78, 221, 0.5);
}
.stats-panel {
background: rgba(157, 78, 221, 0.1);
border: 2px solid #9D4EDD;
border-radius: 12px;
padding: 15px;
margin-bottom: 20px;
}
.stat-row {
display: flex;
justify-content: space-between;
margin: 8px 0;
font-size: 14px;
}
.stat-label {
color: #aaa;
}
.stat-value {
color: #9D4EDD;
font-weight: bold;
font-size: 16px;
}
.filter-section {
margin-bottom: 20px;
}
.filter-title {
color: #9D4EDD;
font-size: 14px;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.filter-btn {
width: 100%;
padding: 10px;
margin: 5px 0;
border: 2px solid rgba(157, 78, 221, 0.3);
background: rgba(42, 42, 60, 0.6);
color: #fff;
cursor: pointer;
border-radius: 8px;
transition: all 0.3s;
font-size: 13px;
text-align: left;
}
.filter-btn:hover {
background: rgba(157, 78, 221, 0.2);
border-color: #9D4EDD;
transform: translateX(5px);
}
.filter-btn.active {
background: #9D4EDD;
color: #000;
font-weight: bold;
border-color: #9D4EDD;
}
.filter-count {
float: right;
background: rgba(0, 0, 0, 0.3);
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
}
.action-buttons {
margin-top: 20px;
}
.action-btn {
width: 100%;
padding: 12px;
margin: 8px 0;
border: 2px solid #9D4EDD;
background: rgba(157, 78, 221, 0.2);
color: #fff;
cursor: pointer;
border-radius: 8px;
font-weight: bold;
transition: all 0.3s;
}
.action-btn:hover {
background: #9D4EDD;
color: #000;
transform: scale(1.05);
}
.action-btn.danger {
border-color: #ff4444;
background: rgba(255, 68, 68, 0.2);
}
.action-btn.danger:hover {
background: #ff4444;
}
/* Main Content */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Toolbar */
.toolbar {
background: rgba(20, 20, 40, 0.95);
border-bottom: 2px solid #9D4EDD;
padding: 15px 20px;
display: flex;
align-items: center;
gap: 15px;
}
.search-box {
flex: 1;
padding: 12px 20px;
border: 2px solid #9D4EDD;
background: rgba(42, 42, 60, 0.8);
color: #fff;
border-radius: 25px;
font-size: 16px;
transition: all 0.3s;
}
.search-box:focus {
outline: none;
box-shadow: 0 0 20px rgba(157, 78, 221, 0.5);
}
.view-toggle {
display: flex;
gap: 5px;
}
.view-btn {
padding: 10px 15px;
border: 2px solid #9D4EDD;
background: rgba(42, 42, 60, 0.8);
color: #fff;
cursor: pointer;
border-radius: 8px;
transition: all 0.3s;
}
.view-btn.active {
background: #9D4EDD;
color: #000;
}
/* Gallery */
.gallery-container {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px;
}
.asset-card {
background: rgba(30, 30, 50, 0.9);
border-radius: 16px;
padding: 15px;
border: 2px solid rgba(157, 78, 221, 0.2);
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.asset-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 30px rgba(157, 78, 221, 0.4);
border-color: #9D4EDD;
}
.asset-thumbnail {
width: 100%;
height: 180px;
background: rgba(10, 10, 20, 0.8);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.asset-thumbnail img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
transition: transform 0.3s;
}
.asset-thumbnail:hover img {
transform: scale(1.15);
}
.asset-filename {
font-size: 13px;
color: #ccc;
margin-bottom: 10px;
word-break: break-word;
line-height: 1.3;
}
.asset-meta {
display: flex;
justify-content: space-between;
font-size: 11px;
color: #888;
margin-bottom: 12px;
}
.asset-controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.control-btn {
padding: 8px;
border: 1px solid;
background: rgba(0, 0, 0, 0.3);
color: #fff;
cursor: pointer;
border-radius: 6px;
font-size: 12px;
transition: all 0.2s;
font-weight: 500;
}
.control-btn.delete {
border-color: #ff4444;
color: #ff4444;
}
.control-btn.delete:hover {
background: #ff4444;
color: #fff;
}
.control-btn.reroll {
border-color: #44ff44;
color: #44ff44;
}
.control-btn.reroll:hover {
background: #44ff44;
color: #000;
}
.control-btn.view {
border-color: #4488ff;
color: #4488ff;
grid-column: span 2;
}
.control-btn.view:hover {
background: #4488ff;
color: #fff;
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.95);
z-index: 10000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
position: relative;
max-width: 90%;
max-height: 90%;
background: rgba(30, 30, 50, 0.95);
border-radius: 16px;
padding: 30px;
border: 2px solid #9D4EDD;
}
.modal-image {
max-width: 100%;
max-height: 70vh;
border-radius: 12px;
margin-bottom: 20px;
}
.modal-info {
color: #ccc;
}
.modal-info h3 {
color: #9D4EDD;
margin-bottom: 10px;
}
.close-modal {
position: absolute;
top: 15px;
right: 15px;
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(157, 78, 221, 0.3);
border: 2px solid #9D4EDD;
color: #fff;
font-size: 24px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.close-modal:hover {
background: #9D4EDD;
transform: rotate(90deg);
}
/* Loading & Toast */
.loading-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 9999;
align-items: center;
justify-content: center;
}
.loading-overlay.active {
display: flex;
}
.spinner {
border: 4px solid rgba(157, 78, 221, 0.3);
border-top: 4px solid #9D4EDD;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.toast {
position: fixed;
bottom: 30px;
right: 30px;
padding: 15px 25px;
background: rgba(157, 78, 221, 0.95);
color: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
transform: translateY(200px);
transition: transform 0.3s;
z-index: 10001;
}
.toast.show {
transform: translateY(0);
}
/* Scrollbar */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: rgba(20, 20, 40, 0.5);
}
::-webkit-scrollbar-thumb {
background: #9D4EDD;
border-radius: 5px;
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<h2>🎨 Asset Manager</h2>
<div class="stats-panel">
<div class="stat-row">
<span class="stat-label">Skupaj:</span>
<span class="stat-value" id="total-assets">Loading...</span>
</div>
<div class="stat-row">
<span class="stat-label">Prikazanih:</span>
<span class="stat-value" id="visible-assets">0</span>
</div>
<div class="stat-row">
<span class="stat-label">Velikost:</span>
<span class="stat-value" id="total-size">Loading...</span>
</div>
</div>
<div class="filter-section">
<div class="filter-title">📁 FAZE / PHASES</div>
<button class="filter-btn active" data-filter="all">
🌍 VSE (All) <span class="filter-count" id="count-all">0</span>
</button>
<button class="filter-btn" data-filter="farm">
🚜 Faza 1: Farm <span class="filter-count" id="count-farm">0</span>
</button>
<button class="filter-btn" data-filter="basement_mine">
⛏️ Faza 2: Mine <span class="filter-count" id="count-mine">0</span>
</button>
<button class="filter-btn" data-filter="common">
🎭 Common <span class="filter-count" id="count-common">0</span>
</button>
<button class="filter-btn" data-filter="spritesheets">
🎞️ Spritesheets <span class="filter-count" id="count-sprites">0</span>
</button>
</div>
<!--
<div class="action-buttons">
<button class="action-btn" onclick="runCodeScan()">
🔍 Code Deep Scan
</button>
<button class="action-btn" onclick="validatePaths()">
✅ Validate Paths
</button>
<button class="action-btn" onclick="organizeAssets()">
📂 Organize Assets
</button>
<button class="action-btn danger" onclick="deleteSelected()">
🗑️ Delete Selected
</button>
</div>
-->
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Toolbar -->
<div class="toolbar">
<input type="text" class="search-box" id="search" placeholder="🔍 Išči assete...">
<div class="view-toggle">
<button class="view-btn active" onclick="setView('grid')">⊞ Grid</button>
<button class="view-btn" onclick="setView('list')">☰ List</button>
</div>
</div>
<!-- Gallery -->
<div class="gallery-container">
<div class="gallery-grid" id="gallery">
<!-- Assets will be loaded here -->
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" id="modal">
<div class="modal-content">
<div class="close-modal" onclick="closeModal()">×</div>
<img id="modal-img" class="modal-image" src="" alt="">
<div class="modal-info">
<h3 id="modal-filename"></h3>
<p id="modal-path"></p>
<p id="modal-size"></p>
</div>
</div>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loading">
<div class="spinner"></div>
</div>
<!-- Toast Notifications -->
<div class="toast" id="toast"></div>
<!-- External Manifest Script -->
<!-- External Manifest Script -->
<script src="src/AssetManifest.js"></script>
<script>
let allAssets = [];
let selectedAssets = new Set();
let currentFilter = 'all';
// Initialize
async function init() {
await loadAssets();
setupEventListeners();
renderGallery(allAssets);
}
async function loadAssets() {
console.log('Loading assets from window.AssetManifest...');
try {
if (!window.AssetManifest || !window.AssetManifest.phases) {
throw new Error('AssetManifest not found or invalid format!');
}
allAssets = [];
// Helper to add assets from a phase
const addPhase = (phaseName, items) => {
if (!items) return;
items.forEach(item => {
allAssets.push({
id: item.key,
name: item.key,
path: item.path, // Path relative to root
category: phaseName,
size: '?' // Unknown size until loaded
});
});
};
// Add Phases
addPhase('farm', window.AssetManifest.phases.farm);
addPhase('basement_mine', window.AssetManifest.phases.basement_mine);
addPhase('common', window.AssetManifest.phases.common);
// Add Spritesheets
if (window.AssetManifest.spritesheets) {
window.AssetManifest.spritesheets.forEach(item => {
allAssets.push({
id: item.key,
name: item.key,
path: item.path,
category: 'spritesheets',
size: `Frames: ${item.frameConfig ? item.frameConfig.frameWidth + 'x' + item.frameConfig.frameHeight : '?'}`
});
});
}
// Update stats counts
document.getElementById('total-assets').textContent = allAssets.length;
document.getElementById('visible-assets').textContent = allAssets.length;
document.getElementById('total-size').textContent = 'N/A';
// Update Button Counts
const updateCount = (id, cat) => {
const count = allAssets.filter(a => a.category === cat).length;
const el = document.getElementById(id);
if (el) el.textContent = count;
};
document.getElementById('count-all').textContent = allAssets.length;
updateCount('count-farm', 'farm');
updateCount('count-mine', 'basement_mine');
updateCount('count-common', 'common');
updateCount('count-sprites', 'spritesheets');
console.log(`✅ Loaded ${allAssets.length} assets from AssetManifest.js`);
} catch (error) {
console.error('❌ Error loading manifest:', error);
document.getElementById('gallery').innerHTML = `<p style="color:red;padding:20px;">Could not load AssetManifest.js<br>${error.message}</p>`;
}
}
function renderGallery(assets) {
const gallery = document.getElementById('gallery');
document.getElementById('visible-assets').textContent = assets.length;
if (assets.length === 0) {
gallery.innerHTML = '<p style="text-align:center;padding:60px;color:#888;">Ni rezultatov</p>';
return;
}
gallery.innerHTML = assets.map(asset => `
<div class="asset-card" data-id="${asset.id}">
<div class="asset-thumbnail" onclick="viewAsset('${asset.id}')">
<img src="${asset.path}" alt="${asset.name}" loading="lazy">
</div>
<div class="asset-filename">${asset.name}</div>
<div class="asset-meta">
<span>📁 ${asset.category}</span>
<span>${asset.size}</span>
</div>
<div class="asset-controls">
<button class="control-btn delete" onclick="deleteAsset('${asset.id}')">
🗑️ Delete
</button>
<button class="control-btn reroll" onclick="rerollAsset('${asset.id}')">
🔄 Re-roll
</button>
<button class="control-btn view" onclick="viewAsset('${asset.id}')">
👁️ View Full
</button>
</div>
</div>
`).join('');
}
function setupEventListeners() {
// Search
document.getElementById('search').addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
const filtered = allAssets.filter(a =>
a.name.toLowerCase().includes(query) ||
a.category.toLowerCase().includes(query)
);
renderGallery(filtered);
});
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentFilter = btn.dataset.filter;
applyFilters();
});
});
// Modal close on outside click
document.getElementById('modal').addEventListener('click', (e) => {
if (e.target.id === 'modal') closeModal();
});
}
function applyFilters() {
let filtered = allAssets;
if (currentFilter !== 'all') {
filtered = filtered.filter(a => a.category === currentFilter);
}
renderGallery(filtered);
}
// Asset actions
function viewAsset(id) {
const asset = allAssets.find(a => a.id === id);
if (!asset) return;
document.getElementById('modal-img').src = asset.path;
document.getElementById('modal-filename').textContent = asset.name;
document.getElementById('modal-path').textContent = `📁 ${asset.path}`;
document.getElementById('modal-size').textContent = `💾 ${asset.size}`;
document.getElementById('modal').classList.add('active');
}
function closeModal() {
document.getElementById('modal').classList.remove('active');
}
function deleteAsset(id) {
alert('Delete functionality is disabled in this static view mode.');
}
function rerollAsset(id) {
alert('Reroll functionality is disabled in this static view mode.');
}
// Bulk actions
function runCodeScan() {
showLoading();
showToast('🔍 Running Deep Code Scan...');
setTimeout(() => {
hideLoading();
showToast('✅ Code scan complete! 0 errors found.');
}, 3000);
}
function validatePaths() {
showLoading();
showToast('✅ Validating asset paths...');
setTimeout(() => {
hideLoading();
showToast('✅ All paths valid!');
}, 2000);
}
function organizeAssets() {
if (!confirm('Start asset organization? This will move files.')) return;
showLoading();
showToast('📂 Organizing assets...');
setTimeout(() => {
hideLoading();
showToast('✅ Assets organized!');
}, 3000);
}
function deleteSelected() {
if (selectedAssets.size === 0) {
alert('No assets selected');
return;
}
if (!confirm(`Delete ${selectedAssets.size} selected assets?`)) return;
showLoading();
setTimeout(() => {
hideLoading();
selectedAssets.clear();
showToast('✅ Selected assets deleted!');
}, 1500);
}
// UI helpers
function showLoading() {
document.getElementById('loading').classList.add('active');
}
function hideLoading() {
document.getElementById('loading').classList.remove('active');
}
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3000);
}
function setView(view) {
document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active'));
event.target.classList.add('active');
// Implement list view if needed
}
// Initialize on load
window.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>