refactor: Refactored mobile UI
This commit is contained in:
@@ -47,6 +47,52 @@ const store = new Store({
|
||||
loading: false, // global loading spinner state if needed
|
||||
});
|
||||
|
||||
const PAGE_PATHS = {
|
||||
auth: '/sim/mobile-sim-auth.html',
|
||||
onboarding: '/sim/mobile-sim-onboarding.html',
|
||||
camera: '/sim/mobile-sim-camera.html',
|
||||
client: '/sim/mobile-sim-client.html',
|
||||
activity: '/sim/mobile-sim-activity.html',
|
||||
settings: '/sim/mobile-sim-settings.html',
|
||||
};
|
||||
|
||||
const currentPageKey = document.body?.dataset?.page || '';
|
||||
const multiPageMode = Boolean(currentPageKey);
|
||||
|
||||
const getHomePageKeyForRole = (role) => (role === 'camera' ? 'camera' : 'client');
|
||||
|
||||
const getPathForScreen = (screen, role) => {
|
||||
if (screen === 'home') {
|
||||
return PAGE_PATHS[getHomePageKeyForRole(role)];
|
||||
}
|
||||
return PAGE_PATHS[screen] || null;
|
||||
};
|
||||
|
||||
const navigateToScreen = (screen, options = {}) => {
|
||||
const { replace = false, role = store.get().device?.role } = options;
|
||||
const targetPath = getPathForScreen(screen, role);
|
||||
|
||||
if (multiPageMode && targetPath && window.location.pathname !== targetPath) {
|
||||
if (replace) {
|
||||
window.location.replace(targetPath);
|
||||
} else {
|
||||
window.location.assign(targetPath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
store.update({ screen });
|
||||
return false;
|
||||
};
|
||||
|
||||
const getScreenForCurrentPage = () => {
|
||||
if (currentPageKey === 'activity') return 'activity';
|
||||
if (currentPageKey === 'settings') return 'settings';
|
||||
if (currentPageKey === 'onboarding') return 'onboarding';
|
||||
if (currentPageKey === 'camera' || currentPageKey === 'client') return 'home';
|
||||
return 'auth';
|
||||
};
|
||||
|
||||
// --- 2. UI Utilities ---
|
||||
const $ = (selector) => {
|
||||
// If it looks like a simple ID (no spaces, dots, hash), use getElementById
|
||||
@@ -209,27 +255,68 @@ const init = async () => {
|
||||
if (session && session.session) {
|
||||
store.update({ session });
|
||||
if (store.get().deviceToken) {
|
||||
// If we have a token, skip onboarding
|
||||
navigateBasedOnRole();
|
||||
const role = store.get().device?.role;
|
||||
if (multiPageMode && (currentPageKey === 'auth' || currentPageKey === 'onboarding')) {
|
||||
if (navigateToScreen('home', { replace: true, role })) return;
|
||||
}
|
||||
if (multiPageMode && (currentPageKey === 'camera' || currentPageKey === 'client')) {
|
||||
const expectedHome = getHomePageKeyForRole(role);
|
||||
if (expectedHome !== currentPageKey) {
|
||||
if (navigateToScreen('home', { replace: true, role })) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (multiPageMode) {
|
||||
store.update({ screen: getScreenForCurrentPage() });
|
||||
} else {
|
||||
navigateBasedOnRole();
|
||||
}
|
||||
connectSocket();
|
||||
startPolling();
|
||||
} else {
|
||||
store.update({ screen: 'onboarding' });
|
||||
if (multiPageMode) {
|
||||
if (currentPageKey !== 'onboarding') {
|
||||
if (navigateToScreen('onboarding', { replace: true })) return;
|
||||
} else {
|
||||
store.update({ screen: 'onboarding' });
|
||||
}
|
||||
} else {
|
||||
store.update({ screen: 'onboarding' });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (multiPageMode) {
|
||||
if (currentPageKey !== 'auth') {
|
||||
if (navigateToScreen('auth', { replace: true })) return;
|
||||
} else {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
} else {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if (multiPageMode) {
|
||||
if (currentPageKey !== 'auth') {
|
||||
if (navigateToScreen('auth', { replace: true })) return;
|
||||
} else {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
} else {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
} catch {
|
||||
store.update({ screen: 'auth' });
|
||||
}
|
||||
};
|
||||
|
||||
const navigateBasedOnRole = () => {
|
||||
const { device } = store.get();
|
||||
if (!device) return store.update({ screen: 'onboarding' });
|
||||
if (!device) {
|
||||
navigateToScreen('onboarding');
|
||||
return;
|
||||
}
|
||||
|
||||
// Default home screen based on role
|
||||
store.update({ screen: 'home' });
|
||||
navigateToScreen('home', { role: device.role });
|
||||
};
|
||||
|
||||
const startCameraPreview = async () => {
|
||||
@@ -1258,11 +1345,16 @@ const Actions = {
|
||||
|
||||
// Proceed
|
||||
if (store.get().deviceToken) {
|
||||
navigateBasedOnRole();
|
||||
const role = store.get().device?.role;
|
||||
if (multiPageMode && currentPageKey === 'auth') {
|
||||
if (navigateToScreen('home', { replace: true, role })) return;
|
||||
} else {
|
||||
navigateBasedOnRole();
|
||||
}
|
||||
connectSocket();
|
||||
startPolling();
|
||||
} else {
|
||||
store.update({ screen: 'onboarding' });
|
||||
if (navigateToScreen('onboarding')) return;
|
||||
}
|
||||
} catch (e) {
|
||||
// handled by API wrapper toast
|
||||
@@ -1325,6 +1417,7 @@ const Actions = {
|
||||
teardownPeerConnection();
|
||||
stopCameraPreview();
|
||||
localStorage.removeItem('mobileSimDevice');
|
||||
if (navigateToScreen('auth', { replace: true })) return;
|
||||
Toast.show('Signed Out', 'info');
|
||||
},
|
||||
|
||||
@@ -1470,7 +1563,7 @@ const Actions = {
|
||||
return;
|
||||
}
|
||||
|
||||
store.update({ screen: 'home' });
|
||||
if (navigateToScreen('home')) return;
|
||||
await Actions.requestStream(cameraDeviceId);
|
||||
},
|
||||
};
|
||||
@@ -1481,31 +1574,45 @@ const render = (state) => {
|
||||
// 1. Screen Visibility
|
||||
$$('section[id^="screen-"]').forEach(el => el.classList.add('hidden'));
|
||||
|
||||
const showSectionById = (id) => {
|
||||
const element = $(id);
|
||||
if (!element) return false;
|
||||
element.classList.remove('hidden');
|
||||
return true;
|
||||
};
|
||||
|
||||
if (state.screen === 'home') {
|
||||
const homeId = state.device?.role === 'camera' ? 'screen-home-camera' : 'screen-home-client';
|
||||
$(homeId).classList.remove('hidden');
|
||||
const preferredHomeId = state.device?.role === 'camera' ? 'screen-home-camera' : 'screen-home-client';
|
||||
if (!showSectionById(preferredHomeId)) {
|
||||
const fallbackHomeId = preferredHomeId === 'screen-home-camera' ? 'screen-home-client' : 'screen-home-camera';
|
||||
showSectionById(fallbackHomeId);
|
||||
}
|
||||
} else {
|
||||
$(`screen-${state.screen}`).classList.remove('hidden');
|
||||
showSectionById(`screen-${state.screen}`);
|
||||
}
|
||||
|
||||
// 2. Top Bar Status
|
||||
const statusDot = $('#connectionStatus .status-dot');
|
||||
const statusText = $('#connectionStatus span:last-child');
|
||||
if (state.socketConnected) {
|
||||
statusDot.className = 'status-dot status-online transition-colors duration-300';
|
||||
statusText.textContent = 'ONLINE';
|
||||
} else {
|
||||
statusDot.className = 'status-dot status-offline transition-colors duration-300';
|
||||
statusText.textContent = 'OFFLINE';
|
||||
if (statusDot && statusText) {
|
||||
if (state.socketConnected) {
|
||||
statusDot.className = 'status-dot status-online transition-colors duration-300';
|
||||
statusText.textContent = 'ONLINE';
|
||||
} else {
|
||||
statusDot.className = 'status-dot status-offline transition-colors duration-300';
|
||||
statusText.textContent = 'OFFLINE';
|
||||
}
|
||||
}
|
||||
|
||||
const authBadge = $('authStatusBadge');
|
||||
if (state.session?.user) {
|
||||
authBadge.textContent = state.session.user.email;
|
||||
authBadge.classList.add('text-blue-400');
|
||||
} else {
|
||||
authBadge.textContent = 'Signed Out';
|
||||
authBadge.classList.remove('text-blue-400');
|
||||
if (authBadge) {
|
||||
if (state.session?.user) {
|
||||
authBadge.textContent = state.session.user.email;
|
||||
authBadge.classList.add('text-blue-400');
|
||||
} else {
|
||||
authBadge.textContent = 'Signed Out';
|
||||
authBadge.classList.remove('text-blue-400');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Bottom Nav Visibility & State
|
||||
@@ -1513,36 +1620,41 @@ const render = (state) => {
|
||||
const unreadNotifications = state.motionNotifications.filter((notification) => !notification.isRead).length;
|
||||
updateNotificationDot(unreadNotifications > 0);
|
||||
|
||||
if (state.session && state.device) {
|
||||
nav.classList.remove('hidden');
|
||||
$$('.nav-btn').forEach(btn => {
|
||||
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');
|
||||
if (nav) {
|
||||
if (state.session && state.device) {
|
||||
nav.classList.remove('hidden');
|
||||
$$('.nav-btn').forEach(btn => {
|
||||
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);
|
||||
});
|
||||
} else {
|
||||
nav.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Camera Mode specifics
|
||||
if (state.device?.role === 'camera') {
|
||||
if (state.device?.role === 'camera' && state.screen === 'home') {
|
||||
const preview = $('cameraPreview');
|
||||
const offlineOverlay = $('cameraOfflineOverlay');
|
||||
const startMotionBtn = $('startMotionBtn');
|
||||
const endMotionBtn = $('endMotionBtn');
|
||||
|
||||
if (!preview || !offlineOverlay || !startMotionBtn || !endMotionBtn) return;
|
||||
|
||||
if (state.socketConnected) {
|
||||
offlineOverlay.classList.add('hidden');
|
||||
if (state.isMotionActive) {
|
||||
preview.classList.remove('bg-black/50');
|
||||
preview.classList.add('bg-red-900/20');
|
||||
$('startMotionBtn').classList.add('hidden');
|
||||
$('endMotionBtn').classList.remove('hidden');
|
||||
$('endMotionBtn').disabled = false;
|
||||
startMotionBtn.classList.add('hidden');
|
||||
endMotionBtn.classList.remove('hidden');
|
||||
endMotionBtn.disabled = false;
|
||||
} else {
|
||||
preview.classList.add('bg-black/50');
|
||||
preview.classList.remove('bg-red-900/20');
|
||||
$('startMotionBtn').classList.remove('hidden');
|
||||
$('endMotionBtn').classList.add('hidden');
|
||||
startMotionBtn.classList.remove('hidden');
|
||||
endMotionBtn.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
offlineOverlay.classList.remove('hidden');
|
||||
@@ -1552,10 +1664,11 @@ const render = (state) => {
|
||||
// 5. Client Mode Lists
|
||||
if (state.device?.role === 'client' && state.screen === 'home') {
|
||||
const list = $('linkedCamerasList');
|
||||
if (state.linkedCameras.length === 0) {
|
||||
list.innerHTML = `<div class="min-w-full text-center py-8 bg-gray-900/30 rounded-xl border border-dashed border-gray-800"><p class="text-gray-600 text-xs">No cameras linked yet</p></div>`;
|
||||
} else {
|
||||
list.innerHTML = state.linkedCameras.map(link => {
|
||||
if (list) {
|
||||
if (state.linkedCameras.length === 0) {
|
||||
list.innerHTML = `<div class="min-w-full text-center py-8 bg-gray-900/30 rounded-xl border border-dashed border-gray-800"><p class="text-gray-600 text-xs">No cameras linked yet</p></div>`;
|
||||
} else {
|
||||
list.innerHTML = state.linkedCameras.map(link => {
|
||||
const cameraName = getCameraLabel(link.cameraDeviceId, link.cameraName);
|
||||
const escapedCameraName = escapeHtml(cameraName);
|
||||
const cameraStatus = (link.cameraStatus || '').toLowerCase() === 'online' ? 'Online' : 'Offline';
|
||||
@@ -1632,65 +1745,67 @@ const render = (state) => {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}).join('');
|
||||
|
||||
// Show/hide main wrapper
|
||||
const viewerWrapper = $('clientStreamViewerWrapper');
|
||||
if (viewerWrapper) {
|
||||
if (state.activeCameraDeviceId) {
|
||||
viewerWrapper.classList.remove('hidden');
|
||||
const title = $('clientStreamViewerTitle');
|
||||
if (title) title.textContent = `Live Feed: ${getCameraLabel(state.activeCameraDeviceId)}`;
|
||||
} else {
|
||||
viewerWrapper.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide main wrapper
|
||||
const viewerWrapper = $('clientStreamViewerWrapper');
|
||||
if (viewerWrapper) {
|
||||
if (state.activeCameraDeviceId) {
|
||||
viewerWrapper.classList.remove('hidden');
|
||||
const title = $('clientStreamViewerTitle');
|
||||
if (title) title.textContent = `Live Feed: ${getCameraLabel(state.activeCameraDeviceId)}`;
|
||||
} else {
|
||||
viewerWrapper.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
// Find session ID for active camera if known
|
||||
let foundSessionId = state.activeStreamSessionId;
|
||||
const sessions = state.cameraSessions || {};
|
||||
if (!foundSessionId && sessions[state.activeCameraDeviceId]) {
|
||||
foundSessionId = sessions[state.activeCameraDeviceId];
|
||||
}
|
||||
|
||||
if (state.activeCameraDeviceId) {
|
||||
// Find session ID for active camera if known
|
||||
let foundSessionId = state.activeStreamSessionId;
|
||||
const sessions = state.cameraSessions || {};
|
||||
if (!foundSessionId && sessions[state.activeCameraDeviceId]) {
|
||||
foundSessionId = sessions[state.activeCameraDeviceId];
|
||||
}
|
||||
|
||||
const currentStream = foundSessionId ? remoteStreams.get(foundSessionId) : null;
|
||||
if (currentStream) {
|
||||
const videoEl = $('clientStreamVideo');
|
||||
if (videoEl && videoEl.srcObject !== currentStream) {
|
||||
videoEl.srcObject = currentStream;
|
||||
setClientStreamMode('video');
|
||||
$('clientLiveDot')?.classList.remove('hidden');
|
||||
// Only play if it's not already playing to prevent interruptions
|
||||
if (videoEl.paused) {
|
||||
void videoEl.play().catch(() => { });
|
||||
const currentStream = foundSessionId ? remoteStreams.get(foundSessionId) : null;
|
||||
if (currentStream) {
|
||||
const videoEl = $('clientStreamVideo');
|
||||
if (videoEl && videoEl.srcObject !== currentStream) {
|
||||
videoEl.srcObject = currentStream;
|
||||
setClientStreamMode('video');
|
||||
$('clientLiveDot')?.classList.remove('hidden');
|
||||
// Only play if it's not already playing to prevent interruptions
|
||||
if (videoEl.paused) {
|
||||
void videoEl.play().catch(() => { });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$('clientLiveDot')?.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
$('clientLiveDot')?.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
$('clientLiveDot')?.classList.add('hidden');
|
||||
}
|
||||
|
||||
const imageEl = $('clientStreamImage');
|
||||
if (imageEl && !imageEl.dataset.errorBound) {
|
||||
imageEl.dataset.errorBound = '1';
|
||||
imageEl.addEventListener('error', () => {
|
||||
const videoEl = $('clientStreamVideo');
|
||||
if (videoEl) {
|
||||
videoEl.classList.add('hidden');
|
||||
}
|
||||
setClientStreamMode('unavailable');
|
||||
});
|
||||
const imageEl = $('clientStreamImage');
|
||||
if (imageEl && !imageEl.dataset.errorBound) {
|
||||
imageEl.dataset.errorBound = '1';
|
||||
imageEl.addEventListener('error', () => {
|
||||
const videoEl = $('clientStreamVideo');
|
||||
if (videoEl) {
|
||||
videoEl.classList.add('hidden');
|
||||
}
|
||||
setClientStreamMode('unavailable');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const recList = $('recordingsList');
|
||||
if (state.recordings.length === 0) {
|
||||
recList.innerHTML = `<div class="text-center py-4 bg-gray-900/30 rounded-xl"><p class="text-gray-600 text-xs text-center">No recordings found</p></div>`;
|
||||
} else {
|
||||
recList.innerHTML = state.recordings.slice(0, 5).map(rec => `
|
||||
if (recList) {
|
||||
if (state.recordings.length === 0) {
|
||||
recList.innerHTML = `<div class="text-center py-4 bg-gray-900/30 rounded-xl"><p class="text-gray-600 text-xs text-center">No recordings found</p></div>`;
|
||||
} else {
|
||||
recList.innerHTML = state.recordings.slice(0, 5).map(rec => `
|
||||
<div class="flex items-center justify-between p-3 bg-gray-900/40 rounded-lg border border-white/5 hover:bg-gray-800 transition-colors">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-medium text-gray-300">${new Date(rec.createdAt).toLocaleString()}</span>
|
||||
@@ -1701,13 +1816,15 @@ const render = (state) => {
|
||||
</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.screen === 'activity') {
|
||||
const activityFeed = $('activityFeedList');
|
||||
if (state.motionNotifications.length === 0) {
|
||||
activityFeed.innerHTML = `
|
||||
if (activityFeed) {
|
||||
if (state.motionNotifications.length === 0) {
|
||||
activityFeed.innerHTML = `
|
||||
<div class="text-center py-10 opacity-50">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 mx-auto mb-2 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
@@ -1715,36 +1832,42 @@ const render = (state) => {
|
||||
<p class="text-sm text-gray-500">No notifications yet</p>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
activityFeed.innerHTML = state.motionNotifications.map((notification) => `
|
||||
} else {
|
||||
activityFeed.innerHTML = state.motionNotifications.map((notification) => `
|
||||
<button class="w-full text-left p-3 rounded-lg border border-white/5 ${notification.isRead ? 'bg-gray-900/30' : 'bg-blue-900/20'} motion-notification-btn" data-notification-id="${notification.id}" data-camera-device-id="${notification.cameraDeviceId}">
|
||||
<p class="text-xs font-medium text-gray-200">${notification.message}</p>
|
||||
<p class="text-[10px] text-gray-500 mt-1">${new Date(notification.createdAt).toLocaleString()}</p>
|
||||
</button>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Settings Screen
|
||||
if (state.session?.user && state.screen === 'settings') {
|
||||
$('profileName').textContent = state.session.user.name;
|
||||
$('profileEmail').textContent = state.session.user.email;
|
||||
$('profileInitials').textContent = state.session.user.name.charAt(0).toUpperCase();
|
||||
const profileName = $('profileName');
|
||||
const profileEmail = $('profileEmail');
|
||||
const profileInitials = $('profileInitials');
|
||||
if (profileName) profileName.textContent = state.session.user.name;
|
||||
if (profileEmail) profileEmail.textContent = state.session.user.email;
|
||||
if (profileInitials) profileInitials.textContent = state.session.user.name.charAt(0).toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
const addActivity = (type, msg) => {
|
||||
const list = $('activityFeedList');
|
||||
const item = document.createElement('div');
|
||||
item.className = 'p-3 rounded-lg bg-gray-900/40 border border-white/5 flex flex-col gap-1';
|
||||
item.innerHTML = `
|
||||
if (list) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'p-3 rounded-lg bg-gray-900/40 border border-white/5 flex flex-col gap-1';
|
||||
item.innerHTML = `
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase tracking-wider">${type}</span>
|
||||
<span class="text-[10px] text-gray-600">${new Date().toLocaleTimeString()}</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-300">${msg}</p>
|
||||
`;
|
||||
list.prepend(item);
|
||||
list.prepend(item);
|
||||
}
|
||||
|
||||
// Also update camera logs if applicable
|
||||
if ($('cameraLogs')) {
|
||||
@@ -1756,27 +1879,34 @@ const addActivity = (type, msg) => {
|
||||
|
||||
const updateNotificationDot = (show) => {
|
||||
const dot = $('notificationDot');
|
||||
if (!dot) return;
|
||||
if (show) dot.classList.remove('hidden');
|
||||
else dot.classList.add('hidden');
|
||||
};
|
||||
|
||||
// --- 6. Event Listeners ---
|
||||
|
||||
$('toggleAuthModeBtn').addEventListener('click', Actions.toggleAuthMode);
|
||||
$('signInBtn').addEventListener('click', Actions.submitAuth);
|
||||
$('registerBtn').addEventListener('click', Actions.registerDevice);
|
||||
$('loadSavedBtn').addEventListener('click', () => { /* Handle legacy loading if needed */ });
|
||||
const bind = (id, eventName, handler) => {
|
||||
const element = $(id);
|
||||
if (!element) return;
|
||||
element.addEventListener(eventName, handler);
|
||||
};
|
||||
|
||||
bind('toggleAuthModeBtn', 'click', Actions.toggleAuthMode);
|
||||
bind('signInBtn', 'click', Actions.submitAuth);
|
||||
bind('registerBtn', 'click', Actions.registerDevice);
|
||||
bind('loadSavedBtn', 'click', () => { /* Handle legacy loading if needed */ });
|
||||
$$('#screen-onboarding [data-role]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => Actions.selectRole(btn.dataset.role));
|
||||
});
|
||||
$('recordingsList').addEventListener('click', (event) => {
|
||||
bind('recordingsList', 'click', (event) => {
|
||||
const target = event.target.closest('.download-recording-btn');
|
||||
if (!target || target.disabled) return;
|
||||
const recordingId = target.dataset.recordingId;
|
||||
if (!recordingId) return;
|
||||
Actions.openRecording(recordingId);
|
||||
});
|
||||
$('activityFeedList').addEventListener('click', (event) => {
|
||||
bind('activityFeedList', 'click', (event) => {
|
||||
const target = event.target.closest('.motion-notification-btn');
|
||||
if (!target) return;
|
||||
const notificationId = target.dataset.notificationId;
|
||||
@@ -1857,31 +1987,31 @@ $$('.nav-btn').forEach(btn => {
|
||||
if (btn.dataset.target === 'activity') {
|
||||
markAllNotificationsRead();
|
||||
}
|
||||
store.update({ screen: btn.dataset.target });
|
||||
if (navigateToScreen(btn.dataset.target)) return;
|
||||
});
|
||||
});
|
||||
|
||||
// Camera Controls
|
||||
$('cameraGoOnlineBtn').addEventListener('click', async () => {
|
||||
bind('cameraGoOnlineBtn', 'click', async () => {
|
||||
if (store.get().device?.role === 'camera') {
|
||||
await startCameraPreview();
|
||||
}
|
||||
connectSocket();
|
||||
});
|
||||
$('startMotionBtn').addEventListener('click', Actions.startMotion);
|
||||
$('endMotionBtn').addEventListener('click', Actions.endMotion);
|
||||
bind('startMotionBtn', 'click', Actions.startMotion);
|
||||
bind('endMotionBtn', 'click', Actions.endMotion);
|
||||
|
||||
// Client Controls
|
||||
$('linkCameraBtn').addEventListener('click', Actions.linkCamera);
|
||||
$('refreshClientBtn').addEventListener('click', startPolling);
|
||||
bind('linkCameraBtn', 'click', Actions.linkCamera);
|
||||
bind('refreshClientBtn', 'click', startPolling);
|
||||
|
||||
// Settings
|
||||
$('signOutBtn').addEventListener('click', Actions.signOut);
|
||||
$('clearActivityBtn').addEventListener('click', () => {
|
||||
bind('signOutBtn', 'click', Actions.signOut);
|
||||
bind('clearActivityBtn', 'click', () => {
|
||||
store.update({ motionNotifications: [] });
|
||||
});
|
||||
$('recordingModalCloseBtn').addEventListener('click', Actions.closeRecordingModal);
|
||||
$('recordingModal').addEventListener('click', (event) => {
|
||||
bind('recordingModalCloseBtn', 'click', Actions.closeRecordingModal);
|
||||
bind('recordingModal', 'click', (event) => {
|
||||
if (event.target === $('recordingModal')) {
|
||||
Actions.closeRecordingModal();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user