diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index 08dd981..7b23dab 100644 --- a/src/main/resources/static/css/styles.css +++ b/src/main/resources/static/css/styles.css @@ -537,6 +537,59 @@ body { margin: 6px 0; } +.upload-row { + display: flex; + align-items: center; + gap: 10px; + margin: 12px 0 4px; +} + +.file-input-field { + display: none; +} + +.file-input-trigger { + flex: 1; + display: inline-flex; + align-items: center; + gap: 12px; + padding: 10px 12px; + border: 1px dashed rgba(124, 58, 237, 0.45); + border-radius: 10px; + background: rgba(124, 58, 237, 0.08); + cursor: pointer; + transition: border-color 120ms ease, background 120ms ease; +} + +.file-input-trigger:hover { + border-color: rgba(124, 58, 237, 0.8); + background: rgba(124, 58, 237, 0.14); +} + +.file-input-icon { + width: 38px; + height: 38px; + border-radius: 8px; + display: grid; + place-items: center; + background: rgba(124, 58, 237, 0.16); + color: #c4b5fd; +} + +.file-input-copy { + display: flex; + flex-direction: column; + gap: 4px; +} + +.file-input-copy strong { + font-size: 14px; +} + +.file-input-copy small { + color: #cbd5e1; +} + .title-row { display: flex; align-items: center; diff --git a/src/main/resources/static/js/admin.js b/src/main/resources/static/js/admin.js index adf8d0c..685072e 100644 --- a/src/main/resources/static/js/admin.js +++ b/src/main/resources/static/js/admin.js @@ -28,6 +28,7 @@ const muteInput = document.getElementById('asset-muted'); const selectedZLabel = document.getElementById('asset-z-level'); const playbackSection = document.getElementById('playback-section'); const controlsPlaceholder = document.getElementById('asset-controls-placeholder'); +const fileNameLabel = document.getElementById('asset-file-name'); const aspectLockState = new Map(); const commitSizeChange = debounce(() => applyTransformFromInputs(), 180); @@ -102,8 +103,10 @@ function renderAssets(list) { function storeAsset(asset) { if (!asset) return; asset.zIndex = Math.max(1, asset.zIndex ?? 1); - if (asset.createdAt && typeof asset.createdAtMs === 'undefined') { - asset.createdAtMs = new Date(asset.createdAt).getTime(); + const parsedCreatedAt = asset.createdAt ? new Date(asset.createdAt).getTime() : NaN; + const hasCreatedAtMs = typeof asset.createdAtMs === 'number' && Number.isFinite(asset.createdAtMs); + if (!hasCreatedAtMs) { + asset.createdAtMs = Number.isFinite(parsedCreatedAt) ? parsedCreatedAt : Date.now(); } assets.set(asset.id, asset); zOrderDirty = true; @@ -177,6 +180,10 @@ function zComparator(a, b) { return (a?.createdAtMs || 0) - (b?.createdAtMs || 0); } +function getChronologicalAssets() { + return Array.from(assets.values()).sort((a, b) => (a?.createdAtMs || 0) - (b?.createdAtMs || 0)); +} + function drawAsset(asset) { const renderState = smoothState(asset); const halfWidth = renderState.width / 2; @@ -626,7 +633,7 @@ function renderAssetList() { return; } - const sortedAssets = getZOrderedAssets().reverse(); + const sortedAssets = getChronologicalAssets(); sortedAssets.forEach((asset) => { const li = document.createElement('li'); li.className = 'asset-item'; @@ -854,7 +861,7 @@ function recenterSelectedAsset() { function bringForward() { const asset = getSelectedAsset(); if (!asset) return; - const ordered = getZOrderedAssets(); + const ordered = [...getZOrderedAssets()]; const index = ordered.findIndex((item) => item.id === asset.id); if (index === -1 || index === ordered.length - 1) return; [ordered[index], ordered[index + 1]] = [ordered[index + 1], ordered[index]]; @@ -864,7 +871,7 @@ function bringForward() { function bringBackward() { const asset = getSelectedAsset(); if (!asset) return; - const ordered = getZOrderedAssets(); + const ordered = [...getZOrderedAssets()]; const index = ordered.findIndex((item) => item.id === asset.id); if (index <= 0) return; [ordered[index], ordered[index - 1]] = [ordered[index - 1], ordered[index]]; @@ -987,6 +994,14 @@ function deleteAsset(asset) { }); } +function handleFileSelection(input) { + if (!input) return; + const name = input.files && input.files.length ? input.files[0].name : ''; + if (fileNameLabel) { + fileNameLabel.textContent = name || 'No file chosen'; + } +} + function uploadAsset() { const fileInput = document.getElementById('asset-file'); if (!fileInput || !fileInput.files || fileInput.files.length === 0) { @@ -1000,6 +1015,7 @@ function uploadAsset() { body: data }).then(() => { fileInput.value = ''; + handleFileSelection(fileInput); }); } @@ -1027,7 +1043,7 @@ function isPointOnAsset(asset, x, y) { } function findAssetAtPoint(x, y) { - const ordered = getZOrderedAssets().reverse(); + const ordered = [...getZOrderedAssets()].reverse(); return ordered.find((asset) => isPointOnAsset(asset, x, y)) || null; } diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html index 66edf6d..4d79ee4 100644 --- a/src/main/resources/templates/admin.html +++ b/src/main/resources/templates/admin.html @@ -27,8 +27,17 @@
Upload overlay visuals and adjust them inline.
- - +