mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Separate asset select panel
This commit is contained in:
@@ -408,6 +408,35 @@ body {
|
|||||||
padding: 18px;
|
padding: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.asset-management {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.25fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 14px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-column.inspector {
|
||||||
|
position: sticky;
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.asset-management {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-column.inspector {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.controls ul {
|
.controls ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -503,6 +532,15 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.asset-inspector {
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-controls-placeholder {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.selected-asset-banner {
|
.selected-asset-banner {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
@@ -680,6 +718,11 @@ body {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.asset-inspector .selected-asset-actions .icon-button,
|
||||||
|
.asset-inspector .selected-asset-actions .icon-button:disabled {
|
||||||
|
background: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
.asset-meta-badges {
|
.asset-meta-badges {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ const audioPitchInput = document.getElementById('asset-audio-pitch');
|
|||||||
const audioVolumeInput = document.getElementById('asset-audio-volume');
|
const audioVolumeInput = document.getElementById('asset-audio-volume');
|
||||||
const controlsPlaceholder = document.getElementById('asset-controls-placeholder');
|
const controlsPlaceholder = document.getElementById('asset-controls-placeholder');
|
||||||
const fileNameLabel = document.getElementById('asset-file-name');
|
const fileNameLabel = document.getElementById('asset-file-name');
|
||||||
|
const assetInspector = document.getElementById('asset-inspector');
|
||||||
|
const selectedAssetName = document.getElementById('selected-asset-name');
|
||||||
|
const selectedAssetMeta = document.getElementById('selected-asset-meta');
|
||||||
|
const selectedAssetBadges = document.getElementById('selected-asset-badges');
|
||||||
|
const selectedVisibilityBtn = document.getElementById('selected-asset-visibility');
|
||||||
|
const selectedDeleteBtn = document.getElementById('selected-asset-delete');
|
||||||
const audioPlaceholder = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="320" height="80"><rect width="100%" height="100%" fill="#1f2937" rx="8"/><g fill="#fbbf24" transform="translate(20 20)"><circle cx="15" cy="20" r="6"/><rect x="28" y="5" width="12" height="30" rx="2"/><rect x="45" y="10" width="140" height="5" fill="#fef3c7"/><rect x="45" y="23" width="110" height="5" fill="#fef3c7"/></g><text x="20" y="70" fill="#e5e7eb" font-family="sans-serif" font-size="14">Audio</text></svg>');
|
const audioPlaceholder = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="320" height="80"><rect width="100%" height="100%" fill="#1f2937" rx="8"/><g fill="#fbbf24" transform="translate(20 20)"><circle cx="15" cy="20" r="6"/><rect x="28" y="5" width="12" height="30" rx="2"/><rect x="45" y="10" width="140" height="5" fill="#fef3c7"/><rect x="45" y="23" width="110" height="5" fill="#fef3c7"/></g><text x="20" y="70" fill="#e5e7eb" font-family="sans-serif" font-size="14">Audio</text></svg>');
|
||||||
const aspectLockState = new Map();
|
const aspectLockState = new Map();
|
||||||
const commitSizeChange = debounce(() => applyTransformFromInputs(), 180);
|
const commitSizeChange = debounce(() => applyTransformFromInputs(), 180);
|
||||||
@@ -59,6 +65,20 @@ if (audioDelayInput) audioDelayInput.addEventListener('input', updateAudioSettin
|
|||||||
if (audioSpeedInput) audioSpeedInput.addEventListener('input', updateAudioSettingsFromInputs);
|
if (audioSpeedInput) audioSpeedInput.addEventListener('input', updateAudioSettingsFromInputs);
|
||||||
if (audioPitchInput) audioPitchInput.addEventListener('input', updateAudioSettingsFromInputs);
|
if (audioPitchInput) audioPitchInput.addEventListener('input', updateAudioSettingsFromInputs);
|
||||||
if (audioVolumeInput) audioVolumeInput.addEventListener('input', updateAudioSettingsFromInputs);
|
if (audioVolumeInput) audioVolumeInput.addEventListener('input', updateAudioSettingsFromInputs);
|
||||||
|
if (selectedVisibilityBtn) {
|
||||||
|
selectedVisibilityBtn.addEventListener('click', () => {
|
||||||
|
const asset = getSelectedAsset();
|
||||||
|
if (!asset) return;
|
||||||
|
updateVisibility(asset, !asset.hidden);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (selectedDeleteBtn) {
|
||||||
|
selectedDeleteBtn.addEventListener('click', () => {
|
||||||
|
const asset = getSelectedAsset();
|
||||||
|
if (!asset) return;
|
||||||
|
deleteAsset(asset);
|
||||||
|
});
|
||||||
|
}
|
||||||
function connect() {
|
function connect() {
|
||||||
const socket = new SockJS('/ws');
|
const socket = new SockJS('/ws');
|
||||||
stompClient = Stomp.over(socket);
|
stompClient = Stomp.over(socket);
|
||||||
@@ -824,6 +844,9 @@ function renderAssetList() {
|
|||||||
|
|
||||||
if (!assets.size) {
|
if (!assets.size) {
|
||||||
selectedAssetId = null;
|
selectedAssetId = null;
|
||||||
|
if (assetInspector) {
|
||||||
|
assetInspector.classList.add('hidden');
|
||||||
|
}
|
||||||
const empty = document.createElement('li');
|
const empty = document.createElement('li');
|
||||||
empty.textContent = 'No assets yet. Upload to get started.';
|
empty.textContent = 'No assets yet. Upload to get started.';
|
||||||
list.appendChild(empty);
|
list.appendChild(empty);
|
||||||
@@ -831,6 +854,10 @@ function renderAssetList() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (assetInspector) {
|
||||||
|
assetInspector.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
const sortedAssets = getChronologicalAssets();
|
const sortedAssets = getChronologicalAssets();
|
||||||
sortedAssets.forEach((asset) => {
|
sortedAssets.forEach((asset) => {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
@@ -903,18 +930,10 @@ function renderAssetList() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
li.appendChild(row);
|
li.appendChild(row);
|
||||||
|
|
||||||
if (asset.id === selectedAssetId && controlsPanel) {
|
|
||||||
controlsPanel.classList.remove('hidden');
|
|
||||||
const detail = document.createElement('div');
|
|
||||||
detail.className = 'asset-detail';
|
|
||||||
detail.appendChild(controlsPanel);
|
|
||||||
li.appendChild(detail);
|
|
||||||
updateSelectedAssetControls(asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
list.appendChild(li);
|
list.appendChild(li);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateSelectedAssetControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBadge(label, extraClass = '') {
|
function createBadge(label, extraClass = '') {
|
||||||
@@ -957,6 +976,12 @@ function getSelectedAsset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectedAssetControls(asset = getSelectedAsset()) {
|
function updateSelectedAssetControls(asset = getSelectedAsset()) {
|
||||||
|
if (controlsPlaceholder && controlsPanel && controlsPanel.parentElement !== controlsPlaceholder) {
|
||||||
|
controlsPlaceholder.appendChild(controlsPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedAssetSummary(asset);
|
||||||
|
|
||||||
if (!controlsPanel || !asset) {
|
if (!controlsPanel || !asset) {
|
||||||
if (controlsPanel) controlsPanel.classList.add('hidden');
|
if (controlsPanel) controlsPanel.classList.add('hidden');
|
||||||
return;
|
return;
|
||||||
@@ -1001,6 +1026,43 @@ function updateSelectedAssetControls(asset = getSelectedAsset()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSelectedAssetSummary(asset) {
|
||||||
|
if (assetInspector) {
|
||||||
|
assetInspector.classList.toggle('hidden', !asset && !assets.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedAssetName) {
|
||||||
|
selectedAssetName.textContent = asset ? (asset.name || `Asset ${asset.id.slice(0, 6)}`) : 'Choose an asset';
|
||||||
|
}
|
||||||
|
if (selectedAssetMeta) {
|
||||||
|
selectedAssetMeta.textContent = asset
|
||||||
|
? `${Math.round(asset.width)}x${Math.round(asset.height)} · Layer ${asset.zIndex ?? 1}`
|
||||||
|
: 'Pick an asset in the list to adjust its placement and playback.';
|
||||||
|
}
|
||||||
|
if (selectedAssetBadges) {
|
||||||
|
selectedAssetBadges.innerHTML = '';
|
||||||
|
if (asset) {
|
||||||
|
selectedAssetBadges.appendChild(createBadge(asset.hidden ? 'Hidden' : 'Visible', asset.hidden ? 'danger' : ''));
|
||||||
|
selectedAssetBadges.appendChild(createBadge(getDisplayMediaType(asset)));
|
||||||
|
const aspectLabel = formatAspectRatioLabel(asset);
|
||||||
|
if (aspectLabel) {
|
||||||
|
selectedAssetBadges.appendChild(createBadge(aspectLabel, 'subtle'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedVisibilityBtn) {
|
||||||
|
selectedVisibilityBtn.disabled = !asset;
|
||||||
|
selectedVisibilityBtn.title = asset ? (asset.hidden ? 'Show asset' : 'Hide asset') : 'Toggle visibility';
|
||||||
|
selectedVisibilityBtn.innerHTML = asset
|
||||||
|
? `<i class="fa-solid ${asset.hidden ? 'fa-eye' : 'fa-eye-slash'}"></i>`
|
||||||
|
: '<i class="fa-solid fa-eye-slash"></i>';
|
||||||
|
}
|
||||||
|
if (selectedDeleteBtn) {
|
||||||
|
selectedDeleteBtn.disabled = !asset;
|
||||||
|
selectedDeleteBtn.title = asset ? 'Delete asset' : 'Delete asset';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyTransformFromInputs() {
|
function applyTransformFromInputs() {
|
||||||
const asset = getSelectedAsset();
|
const asset = getSelectedAsset();
|
||||||
if (!asset) return;
|
if (!asset) return;
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
<div class="controls-full panel">
|
<div class="controls-full panel">
|
||||||
<h3>Overlay assets</h3>
|
<h3>Overlay assets</h3>
|
||||||
<p>Upload overlay visuals and adjust them inline.</p>
|
<p>Upload overlay visuals and adjust them inline.</p>
|
||||||
|
<div class="asset-management">
|
||||||
|
<div class="asset-column">
|
||||||
<div class="upload-row">
|
<div class="upload-row">
|
||||||
<input id="asset-file" class="file-input-field" type="file" accept="image/*,video/*,audio/*" onchange="handleFileSelection(this)" />
|
<input id="asset-file" class="file-input-field" type="file" accept="image/*,video/*,audio/*" onchange="handleFileSelection(this)" />
|
||||||
<label for="asset-file" class="file-input-trigger">
|
<label for="asset-file" class="file-input-trigger">
|
||||||
@@ -39,7 +41,27 @@
|
|||||||
<button onclick="uploadAsset()">Upload</button>
|
<button onclick="uploadAsset()">Upload</button>
|
||||||
</div>
|
</div>
|
||||||
<ul id="asset-list" class="asset-list"></ul>
|
<ul id="asset-list" class="asset-list"></ul>
|
||||||
<div id="asset-controls-placeholder" class="hidden"></div>
|
</div>
|
||||||
|
<div class="asset-column inspector">
|
||||||
|
<div id="asset-inspector" class="asset-inspector panel-section hidden">
|
||||||
|
<div class="selected-asset-banner">
|
||||||
|
<div class="selected-asset-main">
|
||||||
|
<div class="title-row">
|
||||||
|
<strong id="selected-asset-name">Choose an asset</strong>
|
||||||
|
</div>
|
||||||
|
<p class="meta-text" id="selected-asset-meta">Pick an asset in the list to adjust its placement and playback.</p>
|
||||||
|
<div class="badge-row asset-meta-badges" id="selected-asset-badges"></div>
|
||||||
|
</div>
|
||||||
|
<div class="selected-asset-actions">
|
||||||
|
<button type="button" class="ghost icon-button" id="selected-asset-visibility" title="Toggle visibility">
|
||||||
|
<i class="fa-solid fa-eye-slash"></i><span class="sr-only">Toggle visibility</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="ghost danger icon-button" id="selected-asset-delete" title="Delete asset">
|
||||||
|
<i class="fa-solid fa-trash"></i><span class="sr-only">Delete asset</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="asset-controls-placeholder" class="asset-controls-placeholder">
|
||||||
<div id="asset-controls" class="hidden asset-settings">
|
<div id="asset-controls" class="hidden asset-settings">
|
||||||
<div class="panel-section">
|
<div class="panel-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -127,6 +149,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
|
|||||||
Reference in New Issue
Block a user