Phase 6 WIP refactor: migrate track analysis UI from overlay to sidebar header
This commit is contained in:
parent
d0f93e5c55
commit
40188493ac
@ -18,11 +18,6 @@
|
|||||||
<div class="dz-hint">mp3 · flac · wav · ogg</div>
|
<div class="dz-hint">mp3 · flac · wav · ogg</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="analysing" hidden>
|
|
||||||
<div class="an-title">analysing</div>
|
|
||||||
<div class="an-step">—</div>
|
|
||||||
<div class="an-bar"><div class="an-fill"></div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="hud" hidden></div>
|
<div id="hud" hidden></div>
|
||||||
</div>
|
</div>
|
||||||
@ -56,6 +51,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside id="panel">
|
<aside id="panel">
|
||||||
|
<div id="track-header">
|
||||||
|
<div class="th-top-row">
|
||||||
|
<div class="th-info">
|
||||||
|
<span id="th-label" class="th-label">no track loaded</span>
|
||||||
|
<span id="th-name" class="th-name" hidden>no track</span>
|
||||||
|
</div>
|
||||||
|
<button id="btn-change-track" class="th-btn" title="Choose or drop an audio file">choose track</button>
|
||||||
|
</div>
|
||||||
|
<div id="th-progress" class="th-progress" hidden>
|
||||||
|
<div id="th-step" class="th-step">decoding…</div>
|
||||||
|
<div class="an-bar"><div id="th-fill" class="an-fill"></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="panel-tabs">
|
<div id="panel-tabs">
|
||||||
<button data-tab="look" class="active">look</button>
|
<button data-tab="look" class="active">look</button>
|
||||||
<button data-tab="scene">scene</button>
|
<button data-tab="scene">scene</button>
|
||||||
|
|||||||
@ -17,9 +17,6 @@ const dom = {
|
|||||||
stage: document.getElementById('stage'),
|
stage: document.getElementById('stage'),
|
||||||
overlay: document.getElementById('overlay'),
|
overlay: document.getElementById('overlay'),
|
||||||
dropzone: document.getElementById('dropzone'),
|
dropzone: document.getElementById('dropzone'),
|
||||||
analysing: document.getElementById('analysing'),
|
|
||||||
anStep: document.querySelector('#analysing .an-step'),
|
|
||||||
anFill: document.querySelector('#analysing .an-fill'),
|
|
||||||
fileInput: document.getElementById('file-input'),
|
fileInput: document.getElementById('file-input'),
|
||||||
audio: document.getElementById('audio'),
|
audio: document.getElementById('audio'),
|
||||||
timelineCanvas: document.getElementById('timeline-canvas'),
|
timelineCanvas: document.getElementById('timeline-canvas'),
|
||||||
@ -29,6 +26,12 @@ const dom = {
|
|||||||
panelTabs: document.getElementById('panel-tabs'),
|
panelTabs: document.getElementById('panel-tabs'),
|
||||||
hud: document.getElementById('hud'),
|
hud: document.getElementById('hud'),
|
||||||
play: document.getElementById('btn-play'),
|
play: document.getElementById('btn-play'),
|
||||||
|
thLabel: document.getElementById('th-label'),
|
||||||
|
thName: document.getElementById('th-name'),
|
||||||
|
thProgress: document.getElementById('th-progress'),
|
||||||
|
thStep: document.getElementById('th-step'),
|
||||||
|
thFill: document.getElementById('th-fill'),
|
||||||
|
changeTrack: document.getElementById('btn-change-track'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
@ -52,17 +55,32 @@ const paramPanel = new ParamPanel(document.createElement('div'), onParamChange);
|
|||||||
async function loadFile(file) {
|
async function loadFile(file) {
|
||||||
if (state.busy) return;
|
if (state.busy) return;
|
||||||
state.busy = true;
|
state.busy = true;
|
||||||
dom.dropzone.hidden = true;
|
|
||||||
dom.analysing.hidden = false;
|
if (dom.overlay) {
|
||||||
|
dom.overlay.hidden = true;
|
||||||
|
dom.overlay.style.display = 'none';
|
||||||
|
}
|
||||||
|
dom.thLabel.textContent = 'loading track';
|
||||||
|
dom.thName.hidden = false;
|
||||||
|
dom.thName.textContent = file.name.replace(/\.[^/.]+$/, '');
|
||||||
|
dom.thName.title = file.name;
|
||||||
|
dom.thProgress.hidden = false;
|
||||||
|
dom.changeTrack.hidden = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await state.show.load(file, (stage, fraction) => {
|
await state.show.load(file, (stage, fraction) => {
|
||||||
dom.anStep.textContent = stage;
|
dom.thStep.textContent = stage;
|
||||||
dom.anFill.style.width = `${Math.round((fraction || 0) * 100)}%`;
|
dom.thFill.style.width = `${Math.round((fraction || 0) * 100)}%`;
|
||||||
});
|
});
|
||||||
|
|
||||||
dom.audio.src = URL.createObjectURL(file);
|
dom.audio.src = URL.createObjectURL(file);
|
||||||
dom.overlay.hidden = true;
|
dom.thLabel.textContent = 'loaded track';
|
||||||
|
dom.thName.textContent = state.show.fileName;
|
||||||
|
dom.thName.title = state.show.fileName;
|
||||||
|
dom.thProgress.hidden = true;
|
||||||
|
dom.changeTrack.hidden = false;
|
||||||
|
dom.changeTrack.textContent = 'change track';
|
||||||
|
|
||||||
strip.setShow(state.show);
|
strip.setShow(state.show);
|
||||||
resize();
|
resize();
|
||||||
seekTo(0);
|
seekTo(0);
|
||||||
@ -70,18 +88,22 @@ async function loadFile(file) {
|
|||||||
document.title = `flow-state · ${state.show.fileName}`;
|
document.title = `flow-state · ${state.show.fileName}`;
|
||||||
console.info('[flow-state]', describeLook(state.show.look));
|
console.info('[flow-state]', describeLook(state.show.look));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dom.anStep.textContent = `failed: ${err.message}`;
|
dom.thLabel.textContent = 'analysis failed';
|
||||||
|
dom.thStep.textContent = `failed: ${err.message}`;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
setTimeout(() => {
|
dom.changeTrack.hidden = false;
|
||||||
dom.analysing.hidden = true;
|
dom.changeTrack.textContent = 'try again';
|
||||||
dom.dropzone.hidden = false;
|
if (dom.overlay) {
|
||||||
}, 4000);
|
dom.overlay.hidden = false;
|
||||||
|
dom.overlay.style.display = 'flex';
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
state.busy = false;
|
state.busy = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dom.dropzone.addEventListener('click', () => dom.fileInput.click());
|
if (dom.dropzone) dom.dropzone.addEventListener('click', () => dom.fileInput.click());
|
||||||
|
if (dom.changeTrack) dom.changeTrack.addEventListener('click', () => dom.fileInput.click());
|
||||||
dom.fileInput.addEventListener('change', (e) => {
|
dom.fileInput.addEventListener('change', (e) => {
|
||||||
if (e.target.files[0]) loadFile(e.target.files[0]);
|
if (e.target.files[0]) loadFile(e.target.files[0]);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
[hidden] { display: none !important; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -38,32 +39,40 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#canvas { display: block; max-width: 100%; max-height: 100%; }
|
|
||||||
|
|
||||||
#overlay {
|
#overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dropzone {
|
#dropzone {
|
||||||
width: 100%; height: 100%;
|
padding: 32px 48px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(14, 16, 22, 0.85);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px dashed var(--line);
|
pointer-events: auto;
|
||||||
|
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
#dropzone:hover {
|
||||||
|
background: rgba(20, 24, 34, 0.95);
|
||||||
|
border-color: var(--accent);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.7);
|
||||||
}
|
}
|
||||||
#dropzone:hover { background: rgba(255,255,255,0.02); }
|
|
||||||
.dz-inner { text-align: center; }
|
.dz-inner { text-align: center; }
|
||||||
.dz-title { font-size: 26px; letter-spacing: .34em; text-transform: uppercase; margin-bottom: 12px; }
|
.dz-title { font-size: 22px; font-weight: 700; letter-spacing: .28em; text-transform: uppercase; margin-bottom: 8px; color: #fff; }
|
||||||
.dz-sub { color: var(--dim); }
|
.dz-sub { color: var(--dim); font-size: 13px; }
|
||||||
.dz-hint { color: #4b5563; font-size: 11px; margin-top: 6px; }
|
.dz-hint { color: #5a6478; font-size: 11px; margin-top: 8px; }
|
||||||
|
|
||||||
#analysing { text-align: center; min-width: 260px; }
|
.an-bar { height: 3px; background: var(--line); overflow: hidden; margin-top: 4px; border-radius: 2px; }
|
||||||
.an-title { letter-spacing: .3em; text-transform: uppercase; color: var(--dim); }
|
|
||||||
.an-step { margin: 8px 0; color: var(--text); }
|
|
||||||
.an-bar { height: 3px; background: var(--line); overflow: hidden; margin-top: 8px; }
|
|
||||||
.an-fill { height: 100%; width: 0; background: var(--accent); transition: width .12s linear; }
|
.an-fill { height: 100%; width: 0; background: var(--accent); transition: width .12s linear; }
|
||||||
|
|
||||||
#hud {
|
#hud {
|
||||||
@ -109,6 +118,64 @@ button.wide { width: 100%; margin-top: 12px; }
|
|||||||
display: flex; flex-direction: column;
|
display: flex; flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
#track-header {
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: #090b10;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.th-top-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.th-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.th-label {
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .14em;
|
||||||
|
color: var(--dim);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.th-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--accent);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.th-btn {
|
||||||
|
background: rgba(74, 222, 128, 0.1);
|
||||||
|
border: 1px solid rgba(74, 222, 128, 0.3);
|
||||||
|
color: var(--accent);
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
.th-btn:hover {
|
||||||
|
background: rgba(74, 222, 128, 0.25);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.th-progress {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.th-step {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--dim);
|
||||||
|
}
|
||||||
#panel-tabs { display: flex; border-bottom: 1px solid var(--line); }
|
#panel-tabs { display: flex; border-bottom: 1px solid var(--line); }
|
||||||
#panel-tabs button { flex: 1; border: 0; border-right: 1px solid var(--line); background: transparent; color: var(--dim); }
|
#panel-tabs button { flex: 1; border: 0; border-right: 1px solid var(--line); background: transparent; color: var(--dim); }
|
||||||
#panel-tabs button.active { color: var(--text); background: #141821; }
|
#panel-tabs button.active { color: var(--text); background: #141821; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user