feat: redesign mobile simulator into full-page Web Dashboard

- Removed DaisyUI phone mockup from `mobile-sim.html`.
- Implemented a responsive full-desktop app shell layout (sidebar + grid).
- Maintained core WebRTC and streaming logic in `mobile-sim.js`.
- Fixed the System Logs container to allow proper independent vertical scrolling.
- Updated docs/changelog.md.
This commit is contained in:
2026-02-20 17:20:00 +00:00
parent 37d7c27ba0
commit b807409f24
3 changed files with 570 additions and 305 deletions

View File

@@ -355,7 +355,7 @@ const openRecordingModal = (downloadUrl, title) => {
videoEl.src = downloadUrl;
modal.classList.remove('hidden');
modal.classList.add('flex');
void videoEl.play().catch(() => {});
void videoEl.play().catch(() => { });
};
const closeRecordingModal = () => {
@@ -624,7 +624,7 @@ const ensurePeerConnection = async ({
if (videoEl) {
videoEl.srcObject = stream;
setClientStreamMode('video');
void videoEl.play().catch(() => {});
void videoEl.play().catch(() => { });
}
};
@@ -893,7 +893,7 @@ const connectSocket = () => {
}
await peerConnection.setRemoteDescription(new RTCSessionDescription(payload.data));
await applyQueuedCandidates(peerConnection, payload.streamSessionId, payload.fromDeviceId);
addActivity('WebRTC', 'Answer received');
addActivity('WebRTC', 'Answer received and applied');
return;
}
@@ -1183,6 +1183,7 @@ const render = (state) => {
const target = btn.dataset.target;
const isActive = target === state.screen || (target === 'home' && (state.screen === 'home-camera' || state.screen === 'home-client'));
btn.setAttribute('data-active', isActive);
// Optional: Force styles for Web/Desktop mode if needed, though data-active is handled by Tailwind variants
});
} else {
nav.classList.add('hidden');
@@ -1225,9 +1226,8 @@ const render = (state) => {
list.innerHTML = state.linkedCameras.map(link => `
<div class="min-w-[240px] max-w-[240px] bg-gray-900/60 rounded-xl border border-white/5 overflow-hidden">
<div class="relative overflow-hidden bg-black/40 border-b border-white/5 aspect-video">
${
state.activeCameraDeviceId === link.cameraDeviceId
? `
${state.activeCameraDeviceId === link.cameraDeviceId
? `
<video id="clientStreamVideo" class="absolute inset-0 w-full h-full object-cover hidden" autoplay playsinline></video>
<img id="clientStreamImage" class="absolute inset-0 w-full h-full object-cover hidden" alt="Live stream preview" />
<div id="clientStreamPlaceholder" class="absolute inset-0 flex flex-col items-center justify-center gap-2 text-gray-500">
@@ -1237,7 +1237,7 @@ const render = (state) => {
<p class="text-[10px]">${state.activeStreamSessionId ? 'Connecting stream...' : 'Waiting for stream'}</p>
</div>
`
: `
: `
<div class="absolute inset-0 flex flex-col items-center justify-center gap-2 text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
@@ -1245,7 +1245,7 @@ const render = (state) => {
<p class="text-[10px]">Stand by</p>
</div>
`
}
}
</div>
<div class="px-3 py-2">
<div>
@@ -1261,8 +1261,11 @@ const render = (state) => {
if (videoEl && videoEl.srcObject !== remoteClientStream) {
videoEl.srcObject = remoteClientStream;
setClientStreamMode('video');
void videoEl.play().catch(() => {});
$('clientLiveDot')?.classList.remove('hidden');
void videoEl.play().catch(() => { });
}
} else {
$('clientLiveDot')?.classList.add('hidden');
}
const imageEl = $('clientStreamImage');