Restructure layout

This commit is contained in:
2025-12-09 17:24:19 +01:00
parent 0aac788eda
commit 2269c67a8a
3 changed files with 77 additions and 11 deletions

View File

@@ -399,6 +399,13 @@ body {
background: #0b1220;
}
.assets-panel {
margin-top: 16px;
background: #0b1220;
border-top: 1px solid #1f2937;
padding: 18px;
}
.controls ul {
list-style: none;
padding: 0;
@@ -588,6 +595,11 @@ body {
box-shadow: 0 0 0 1px rgba(124, 58, 237, 0.6);
}
.asset-item.is-hidden {
opacity: 0.72;
border-style: dashed;
}
.asset-row {
display: flex;
align-items: center;
@@ -614,6 +626,10 @@ body {
gap: 8px;
}
.asset-meta-badges {
margin-top: 4px;
}
.asset-detail {
margin-top: 4px;
}

View File

@@ -29,14 +29,27 @@ const selectedAssetName = document.getElementById('selected-asset-name');
const selectedZLabel = document.getElementById('asset-z-level');
const selectedTypeLabel = document.getElementById('asset-type-label');
const selectedVisibilityBadge = document.getElementById('selected-asset-visibility');
const selectedZBadge = document.getElementById('asset-z-badge');
const selectedAspectBadge = document.getElementById('asset-aspect-label');
const selectedToggleBtn = document.getElementById('selected-asset-toggle');
const selectedDeleteBtn = document.getElementById('selected-asset-delete');
const playbackSection = document.getElementById('playback-section');
const controlsPlaceholder = document.getElementById('asset-controls-placeholder');
const aspectLockState = new Map();
const commitSizeChange = debounce(() => applyTransformFromInputs(), 180);
function debounce(fn, wait = 150) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), wait);
};
}
if (widthInput) widthInput.addEventListener('input', () => handleSizeInputChange('width'));
if (widthInput) widthInput.addEventListener('change', () => commitSizeChange());
if (heightInput) heightInput.addEventListener('input', () => handleSizeInputChange('height'));
if (heightInput) heightInput.addEventListener('change', () => commitSizeChange());
if (speedInput) speedInput.addEventListener('input', updatePlaybackFromInputs);
if (muteInput) muteInput.addEventListener('change', updateMuteFromInput);
if (selectedToggleBtn) selectedToggleBtn.addEventListener('click', (event) => {
@@ -642,9 +655,7 @@ function renderAssetList() {
if (asset.id === selectedAssetId) {
li.classList.add('selected');
}
if (asset.hidden) {
li.classList.add('hidden');
}
li.classList.toggle('is-hidden', !!asset.hidden);
const row = document.createElement('div');
row.className = 'asset-row';
@@ -656,10 +667,21 @@ function renderAssetList() {
const name = document.createElement('strong');
name.textContent = asset.name || `Asset ${asset.id.slice(0, 6)}`;
const details = document.createElement('small');
details.textContent = `Z ${asset.zIndex ?? 1} · ${Math.round(asset.width)}x${Math.round(asset.height)} · ${getDisplayMediaType(asset)} · ${asset.hidden ? 'Hidden' : 'Visible'}`;
details.textContent = `${Math.round(asset.width)}x${Math.round(asset.height)}`;
meta.appendChild(name);
meta.appendChild(details);
const badges = document.createElement('div');
badges.className = 'badge-row asset-meta-badges';
badges.appendChild(createBadge(asset.hidden ? 'Hidden' : 'Visible', asset.hidden ? 'danger' : ''));
badges.appendChild(createBadge(getDisplayMediaType(asset)));
badges.appendChild(createBadge(`Z ${asset.zIndex ?? 1}`));
const aspectLabel = formatAspectRatioLabel(asset);
if (aspectLabel) {
badges.appendChild(createBadge(aspectLabel, 'subtle'));
}
meta.appendChild(badges);
const actions = document.createElement('div');
actions.className = 'actions';
@@ -712,6 +734,13 @@ function renderAssetList() {
});
}
function createBadge(label, extraClass = '') {
const badge = document.createElement('span');
badge.className = `badge ${extraClass}`.trim();
badge.textContent = label;
return badge;
}
function createPreviewElement(asset) {
if (isVideoAsset(asset)) {
const video = document.createElement('video');
@@ -751,6 +780,12 @@ function updateSelectedAssetControls(asset = getSelectedAsset()) {
if (selectedTypeLabel) {
selectedTypeLabel.textContent = getDisplayMediaType(asset);
}
if (selectedZBadge) {
selectedZBadge.textContent = `Z ${asset.zIndex ?? 1}`;
}
if (selectedAspectBadge) {
selectedAspectBadge.textContent = formatAspectRatioLabel(asset) || 'Aspect —';
}
if (selectedVisibilityBadge) {
selectedVisibilityBadge.textContent = asset.hidden ? 'Hidden' : 'Visible';
selectedVisibilityBadge.classList.toggle('danger', !!asset.hidden);
@@ -925,6 +960,15 @@ function getAssetAspectRatio(asset) {
return null;
}
function formatAspectRatioLabel(asset) {
const ratio = getAssetAspectRatio(asset);
if (!ratio) {
return '';
}
const normalized = ratio >= 1 ? `${ratio.toFixed(2)}:1` : `1:${(1 / ratio).toFixed(2)}`;
return `AR ${normalized}`;
}
function setAspectLock(assetId, locked) {
aspectLockState.set(assetId, locked);
}
@@ -936,7 +980,11 @@ function isAspectLocked(assetId) {
function handleSizeInputChange(type) {
lastSizeInputChanged = type;
const asset = getSelectedAsset();
if (!asset || !isAspectLocked(asset.id)) {
if (!asset) {
return;
}
if (!isAspectLocked(asset.id)) {
commitSizeChange();
return;
}
const ratio = getAssetAspectRatio(asset);
@@ -954,6 +1002,7 @@ function handleSizeInputChange(type) {
widthInput.value = Math.round(height * ratio);
}
}
commitSizeChange();
}
function updateVisibility(asset, hidden) {

View File

@@ -19,7 +19,11 @@
</form>
</div>
</header>
<section class="controls">
<section class="overlay" id="admin-overlay">
<iframe th:src="${'https://player.twitch.tv/?channel=' + broadcaster + '&parent=localhost'}" allowfullscreen></iframe>
<canvas id="admin-canvas"></canvas>
</section>
<section class="controls assets-panel">
<div>
<h3>Overlay assets</h3>
<p>Upload overlay visuals and adjust them inline.</p>
@@ -44,6 +48,8 @@
<div class="badge-row stacked">
<span class="badge subtle" id="selected-asset-visibility">Visible</span>
<span class="badge subtle" id="asset-type-label"></span>
<span class="badge subtle" id="asset-z-badge"></span>
<span class="badge subtle" id="asset-aspect-label"></span>
</div>
<div class="panel-section">
@@ -101,15 +107,10 @@
<div class="control-actions filled compact">
<button type="button" onclick="nudgeRotation(-5)" class="secondary" title="Rotate left"><i class="fa-solid fa-rotate-left"></i></button>
<button type="button" onclick="nudgeRotation(5)" class="secondary" title="Rotate right"><i class="fa-solid fa-rotate-right"></i></button>
<button type="button" onclick="applyTransformFromInputs()" title="Apply size"><i class="fa-solid fa-floppy-disk"></i><span class="sr-only">Apply size</span></button>
</div>
</div>
</div>
</section>
<section class="overlay" id="admin-overlay">
<iframe th:src="${'https://player.twitch.tv/?channel=' + broadcaster + '&parent=localhost'}" allowfullscreen></iframe>
<canvas id="admin-canvas"></canvas>
</section>
</div>
<script th:inline="javascript">
const broadcaster = /*[[${broadcaster}]]*/ '';