video side: build the Dashboard page (mirrors music, isolated data)
Real first video page, reusing music's .dash-grid/.dash-card CSS for an identical look — but every value is driven by isolated video JS, no music code referenced. Sections mirror the music dashboard, adapted: - Service Status: Media Server / Download Client / Metadata Source - System Stats: swaps 'Active Syncs' -> 'Disk Usage'; keeps download/speed/ uptime/memory - Library: Movies / Shows / Episodes / Disk Size - Recent Syncs -> Recent Downloads (empty state for now) - Quick Actions: Add Movie/Show, Watchlist, Downloads (navigate via data-video-goto) - Recent Activity - No enrichment section, no header sweep animation (per plan) Mechanics: - #video-page-host now holds .video-subpage sections; controller toggles one at a time and falls back to #video-placeholder-slot for unbuilt pages. - video-side.js dispatches soulsync:video-page-shown; video-dashboard.js (new isolated IIFE) listens and applies a zeroed STUB until video.db exists. Single seam to swap for a real /api/video/dashboard fetch later. - All wiring via data-attrs + addEventListener; no inline onclick (keeps the script-split integrity contract intact). 73 tests green.
This commit is contained in:
parent
9330d66fcd
commit
c84231dd4f
5 changed files with 420 additions and 17 deletions
|
|
@ -16,6 +16,7 @@ from pathlib import Path
|
|||
_ROOT = Path(__file__).resolve().parent.parent
|
||||
_INDEX = (_ROOT / "webui" / "index.html").read_text(encoding="utf-8", errors="replace")
|
||||
_JS = (_ROOT / "webui" / "static" / "video" / "video-side.js").read_text(encoding="utf-8")
|
||||
_DASH_JS = (_ROOT / "webui" / "static" / "video" / "video-dashboard.js").read_text(encoding="utf-8")
|
||||
_CSS_PATH = _ROOT / "webui" / "static" / "video" / "video-side.css"
|
||||
|
||||
EXPECTED_VIDEO_PAGES = {
|
||||
|
|
@ -75,6 +76,39 @@ def test_music_sidebar_nav_still_intact():
|
|||
assert "Music Sync & Manager" in _INDEX
|
||||
|
||||
|
||||
# --- the video dashboard (first built page) -------------------------------
|
||||
|
||||
def test_video_dashboard_subpage_present_with_expected_cards():
|
||||
block = _block(
|
||||
_INDEX, r'<section class="video-subpage" data-video-subpage="video-dashboard"', "</section>")
|
||||
# Mirrors the music dashboard's sections (minus enrichment), reusing its CSS.
|
||||
for card in ("services", "stats", "library", "downloads", "tools", "activity"):
|
||||
assert f'data-card="{card}"' in block, f"video dashboard missing the '{card}' card"
|
||||
# Reuses music's dashboard classes so the look matches.
|
||||
assert 'class="dash-grid"' in block
|
||||
assert "stat-card-dashboard" in block
|
||||
# Driven by data, not music code: stat values carry data-video-stat hooks.
|
||||
assert "data-video-stat=" in block
|
||||
# Isolation + integrity contract: no inline handlers anywhere in the page.
|
||||
assert "onclick" not in block
|
||||
|
||||
|
||||
def test_video_dashboard_has_placeholder_slot_for_unbuilt_pages():
|
||||
assert 'id="video-placeholder-slot"' in _INDEX
|
||||
|
||||
|
||||
def test_video_dashboard_data_module_referenced_and_isolated():
|
||||
assert "video/video-dashboard.js" in _INDEX
|
||||
stripped = _DASH_JS.strip()
|
||||
assert stripped.startswith("/*") or stripped.startswith("(function")
|
||||
assert "(function" in _DASH_JS and "})();" in _DASH_JS
|
||||
# Decoupled from the controller: it listens for the page-shown event rather
|
||||
# than being called directly, and adds no globals / inline handlers.
|
||||
assert "soulsync:video-page-shown" in _DASH_JS
|
||||
assert "addEventListener" in _DASH_JS
|
||||
assert "window." not in _DASH_JS
|
||||
|
||||
|
||||
def test_controller_is_isolated_iife_with_no_globals():
|
||||
stripped = _JS.strip()
|
||||
# Wrapped in an IIFE → declares no module-level globals that could collide
|
||||
|
|
|
|||
242
webui/index.html
242
webui/index.html
|
|
@ -415,8 +415,244 @@
|
|||
<canvas id="page-particles-canvas"></canvas>
|
||||
<div id="webui-react-root" class="page" data-react-app="router"></div>
|
||||
|
||||
<!-- Video side content host (shell-only; filled by video/video-side.js) -->
|
||||
<div class="page video-page" id="video-page-host"></div>
|
||||
<!-- Video side content host. Real pages live here as .video-subpage
|
||||
sections (shown one at a time by video/video-side.js); pages
|
||||
without a built page fall back to #video-placeholder-slot.
|
||||
ISOLATION: reuses music's .dash-grid/.dash-card CSS for an
|
||||
identical look, but every value is driven by video/video-*.js
|
||||
off the (future) video DB — no music code is referenced. No
|
||||
inline onclick (data-attrs + addEventListener only). -->
|
||||
<div class="page video-page" id="video-page-host">
|
||||
<section class="video-subpage" data-video-subpage="video-dashboard">
|
||||
<div class="page-shell dashboard-container">
|
||||
<div class="dashboard-header">
|
||||
<div class="header-text">
|
||||
<h2 class="header-title"><span>Video Dashboard</span></h2>
|
||||
<p class="header-subtitle">Monitor your video library, downloads and services</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dash-grid">
|
||||
|
||||
<!-- Service Status -->
|
||||
<article class="dash-card" data-card="services">
|
||||
<header class="dash-card__head">
|
||||
<h3 class="dash-card__title">Service Status</h3>
|
||||
<p class="dash-card__sub">Connection health for the video services.</p>
|
||||
</header>
|
||||
<div class="dash-card__body">
|
||||
<div class="service-status-grid">
|
||||
<div class="service-card" data-status-ready="false">
|
||||
<div class="service-card-header">
|
||||
<span class="service-card-title">Media Server</span>
|
||||
<span class="service-card-indicator disconnected">●</span>
|
||||
</div>
|
||||
<p class="service-card-status-text">Not configured</p>
|
||||
<p class="service-card-response-time">Response: --</p>
|
||||
<div class="service-card-footer">
|
||||
<button class="service-card-button" type="button" data-video-test="media-server">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="service-card" data-status-ready="false">
|
||||
<div class="service-card-header">
|
||||
<span class="service-card-title">Download Client</span>
|
||||
<span class="service-card-indicator disconnected">●</span>
|
||||
</div>
|
||||
<p class="service-card-status-text">Not configured</p>
|
||||
<p class="service-card-response-time">Response: --</p>
|
||||
<div class="service-card-footer">
|
||||
<button class="service-card-button" type="button" data-video-test="download-client">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="service-card" data-status-ready="false">
|
||||
<div class="service-card-header">
|
||||
<span class="service-card-title">Metadata Source</span>
|
||||
<span class="service-card-indicator disconnected">●</span>
|
||||
</div>
|
||||
<p class="service-card-status-text">Not configured</p>
|
||||
<p class="service-card-response-time">Response: --</p>
|
||||
<div class="service-card-footer">
|
||||
<button class="service-card-button" type="button" data-video-test="metadata-source">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- System Stats -->
|
||||
<article class="dash-card" data-card="stats">
|
||||
<header class="dash-card__head">
|
||||
<h3 class="dash-card__title">System Stats</h3>
|
||||
<p class="dash-card__sub">Live performance metrics.</p>
|
||||
</header>
|
||||
<div class="dash-card__body">
|
||||
<div class="stats-grid-dashboard">
|
||||
<div class="stat-card-dashboard">
|
||||
<p class="stat-card-title">Active Downloads</p>
|
||||
<p class="stat-card-value" data-video-stat="active-downloads">0</p>
|
||||
<p class="stat-card-subtitle">Currently downloading</p>
|
||||
</div>
|
||||
<div class="stat-card-dashboard">
|
||||
<p class="stat-card-title">Finished Downloads</p>
|
||||
<p class="stat-card-value" data-video-stat="finished-downloads">0</p>
|
||||
<p class="stat-card-subtitle">Completed this session</p>
|
||||
</div>
|
||||
<div class="stat-card-dashboard">
|
||||
<p class="stat-card-title">Download Speed</p>
|
||||
<p class="stat-card-value" data-video-stat="download-speed">0 KB/s</p>
|
||||
<p class="stat-card-subtitle">Combined speed</p>
|
||||
</div>
|
||||
<div class="stat-card-dashboard">
|
||||
<p class="stat-card-title">Disk Usage</p>
|
||||
<p class="stat-card-value" data-video-stat="disk-usage">--</p>
|
||||
<p class="stat-card-subtitle">Video library size</p>
|
||||
</div>
|
||||
<div class="stat-card-dashboard">
|
||||
<p class="stat-card-title">System Uptime</p>
|
||||
<p class="stat-card-value" data-video-stat="uptime">0m</p>
|
||||
<p class="stat-card-subtitle">Application runtime</p>
|
||||
</div>
|
||||
<div class="stat-card-dashboard">
|
||||
<p class="stat-card-title">Memory Usage</p>
|
||||
<p class="stat-card-value" data-video-stat="memory">--</p>
|
||||
<p class="stat-card-subtitle">Current usage</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Library -->
|
||||
<article class="dash-card" data-card="library">
|
||||
<header class="dash-card__head">
|
||||
<h3 class="dash-card__title">Library</h3>
|
||||
<p class="dash-card__sub">Your collection at a glance.</p>
|
||||
</header>
|
||||
<div class="dash-card__body">
|
||||
<div class="library-status-card">
|
||||
<div class="library-status-glow"></div>
|
||||
<div class="library-status-header">
|
||||
<div class="library-status-info">
|
||||
<h4 class="library-status-title">Video Library</h4>
|
||||
<p class="library-status-subtitle">Not scanned yet</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="library-status-stats">
|
||||
<div class="library-status-stat">
|
||||
<div class="library-status-stat-text">
|
||||
<span class="library-status-stat-value" data-video-stat="movies">0</span>
|
||||
<span class="library-status-stat-label">Movies</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="library-status-stat">
|
||||
<div class="library-status-stat-text">
|
||||
<span class="library-status-stat-value" data-video-stat="shows">0</span>
|
||||
<span class="library-status-stat-label">Shows</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="library-status-stat">
|
||||
<div class="library-status-stat-text">
|
||||
<span class="library-status-stat-value" data-video-stat="episodes">0</span>
|
||||
<span class="library-status-stat-label">Episodes</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="library-status-stat">
|
||||
<div class="library-status-stat-text">
|
||||
<span class="library-status-stat-value" data-video-stat="library-size">--</span>
|
||||
<span class="library-status-stat-label">Disk Size</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Recent Downloads -->
|
||||
<article class="dash-card" data-card="downloads">
|
||||
<header class="dash-card__head">
|
||||
<h3 class="dash-card__title">Recent Downloads</h3>
|
||||
<p class="dash-card__sub">Your latest grabs.</p>
|
||||
</header>
|
||||
<div class="dash-card__body">
|
||||
<div class="sync-history-cards" data-video-recent-downloads>
|
||||
<p class="video-empty-note">No downloads yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<article class="dash-card dash-card--quick-actions" data-card="tools">
|
||||
<header class="dash-card__head">
|
||||
<h3 class="dash-card__title">Quick Actions</h3>
|
||||
<p class="dash-card__sub">Jump straight into the video side.</p>
|
||||
</header>
|
||||
<div class="dash-card__body qa-bento">
|
||||
<button class="qa-tile qa-tile--hero" type="button" data-video-goto="video-search" aria-label="Add a movie or show">
|
||||
<div class="qa-tile__topline">
|
||||
<span class="qa-tile__pulse" aria-hidden="true"></span>
|
||||
<span class="qa-tile__kicker">Find & add</span>
|
||||
</div>
|
||||
<div class="qa-tile__heading">
|
||||
<strong class="qa-tile__title">Add Movie or Show</strong>
|
||||
<p class="qa-tile__desc">Search across sources and start tracking something new.</p>
|
||||
</div>
|
||||
<div class="qa-tile__cta">
|
||||
<span class="qa-tile__cta-label">Search</span>
|
||||
<span class="qa-tile__cta-arrow" aria-hidden="true">→</span>
|
||||
</div>
|
||||
</button>
|
||||
<button class="qa-tile qa-tile--minor" type="button" data-video-goto="video-watchlist" aria-label="Open Watchlist">
|
||||
<div class="qa-tile__topline">
|
||||
<span class="qa-tile__kicker">Following</span>
|
||||
</div>
|
||||
<div class="qa-tile__heading">
|
||||
<strong class="qa-tile__title">Watchlist</strong>
|
||||
<p class="qa-tile__desc">Shows & channels you monitor.</p>
|
||||
</div>
|
||||
<div class="qa-tile__cta">
|
||||
<span class="qa-tile__cta-label">Open</span>
|
||||
<span class="qa-tile__cta-arrow" aria-hidden="true">→</span>
|
||||
</div>
|
||||
</button>
|
||||
<button class="qa-tile qa-tile--minor" type="button" data-video-goto="video-downloads" aria-label="Open Downloads">
|
||||
<div class="qa-tile__topline">
|
||||
<span class="qa-tile__kicker">Activity</span>
|
||||
</div>
|
||||
<div class="qa-tile__heading">
|
||||
<strong class="qa-tile__title">Downloads</strong>
|
||||
<p class="qa-tile__desc">Queue, progress and history.</p>
|
||||
</div>
|
||||
<div class="qa-tile__cta">
|
||||
<span class="qa-tile__cta-label">Open</span>
|
||||
<span class="qa-tile__cta-arrow" aria-hidden="true">→</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<article class="dash-card" data-card="activity">
|
||||
<header class="dash-card__head">
|
||||
<h3 class="dash-card__title">Recent Activity</h3>
|
||||
<p class="dash-card__sub">What just happened.</p>
|
||||
</header>
|
||||
<div class="dash-card__body">
|
||||
<div class="activity-feed-container" data-video-activity-feed>
|
||||
<div class="activity-item">
|
||||
<span class="activity-icon">🎥</span>
|
||||
<div class="activity-text-content">
|
||||
<p class="activity-title">Video side ready</p>
|
||||
<p class="activity-subtitle">Dashboard initialized successfully</p>
|
||||
</div>
|
||||
<p class="activity-time">Now</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="video-placeholder-slot" id="video-placeholder-slot" hidden></div>
|
||||
</div>
|
||||
|
||||
<!-- Dashboard Page -->
|
||||
<div class="page" id="dashboard-page">
|
||||
|
|
@ -8543,6 +8779,8 @@
|
|||
<script src="{{ url_for('static', filename='worker-orbs.js', v=static_v) }}"></script>
|
||||
<!-- Video side shell controller (isolated; music never references it) -->
|
||||
<script src="{{ url_for('static', filename='video/video-side.js', v=static_v) }}"></script>
|
||||
<!-- Video dashboard data layer (isolated; populates the dashboard from the video DB) -->
|
||||
<script src="{{ url_for('static', filename='video/video-dashboard.js', v=static_v) }}"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
76
webui/static/video/video-dashboard.js
Normal file
76
webui/static/video/video-dashboard.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* SoulSync — Video dashboard data layer.
|
||||
*
|
||||
* ISOLATION CONTRACT: like video-side.js this is a self-contained IIFE (no
|
||||
* globals, no inline handlers, lives under static/video/ which the script-split
|
||||
* integrity scan does not touch). It NEVER references music code, and music
|
||||
* never references it.
|
||||
*
|
||||
* It owns only the *data* of the video dashboard — the markup lives in
|
||||
* index.html and reuses music's .dash-card CSS for an identical look. It learns
|
||||
* when the dashboard becomes visible by listening for the
|
||||
* 'soulsync:video-page-shown' event that video-side.js dispatches (so the two
|
||||
* modules stay decoupled — no direct calls between them).
|
||||
*
|
||||
* TODO(video.db): STUB_STATS below is a placeholder. Once the video database +
|
||||
* its /api/video/dashboard endpoint exist, replace loadStats() with a fetch and
|
||||
* feed the same shape through applyStats(). Nothing else here needs to change.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var DASHBOARD_ID = 'video-dashboard';
|
||||
|
||||
// Zeroed placeholder until the video DB is wired. Keys match the markup's
|
||||
// data-video-stat attributes.
|
||||
var STUB_STATS = {
|
||||
'active-downloads': '0',
|
||||
'finished-downloads': '0',
|
||||
'download-speed': '0 KB/s',
|
||||
'disk-usage': '--',
|
||||
'uptime': '0m',
|
||||
'memory': '--',
|
||||
'movies': '0',
|
||||
'shows': '0',
|
||||
'episodes': '0',
|
||||
'library-size': '--'
|
||||
};
|
||||
|
||||
function applyStats(stats) {
|
||||
var nodes = document.querySelectorAll('[data-video-stat]');
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var key = nodes[i].getAttribute('data-video-stat');
|
||||
if (Object.prototype.hasOwnProperty.call(stats, key)) {
|
||||
nodes[i].textContent = stats[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadStats() {
|
||||
// TODO(video.db): replace with fetch('/api/video/dashboard') -> applyStats(json).
|
||||
applyStats(STUB_STATS);
|
||||
}
|
||||
|
||||
// Service "Test" buttons are inert until the video services exist. Mark them
|
||||
// honestly rather than firing a no-op that looks broken.
|
||||
var testWired = false;
|
||||
function wireTestButtons() {
|
||||
if (testWired) return;
|
||||
testWired = true;
|
||||
var buttons = document.querySelectorAll('[data-video-test]');
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
(function (btn) {
|
||||
btn.disabled = true;
|
||||
btn.title = 'Coming soon — video services not configured yet';
|
||||
})(buttons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function onPageShown(e) {
|
||||
if (!e || e.detail !== DASHBOARD_ID) return;
|
||||
wireTestButtons();
|
||||
loadStats();
|
||||
}
|
||||
|
||||
document.addEventListener('soulsync:video-page-shown', onPageShown);
|
||||
})();
|
||||
|
|
@ -108,11 +108,20 @@ body[data-side="video"] .status-section {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* ── Placeholder content (until the real video pages land) ─────────────── */
|
||||
.video-placeholder {
|
||||
/* ── Subpages: one built video page shown at a time ────────────────────── */
|
||||
/* Each real page is a .video-subpage inside #video-page-host; the controller
|
||||
toggles the [hidden] attribute. Be explicit so no reused .dash-* rule can
|
||||
resurrect a hidden page. */
|
||||
.video-subpage[hidden],
|
||||
.video-placeholder-slot[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* ── Placeholder content (for pages not built yet) ─────────────────────── */
|
||||
.video-placeholder-slot {
|
||||
padding: 48px 40px;
|
||||
}
|
||||
.video-placeholder .header-title {
|
||||
.video-placeholder-slot .header-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
|
@ -121,3 +130,11 @@ body[data-side="video"] .status-section {
|
|||
color: var(--text-secondary, #9aa0aa);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Empty-state line inside reused dashboard cards (e.g. Recent Downloads). */
|
||||
.video-empty-note {
|
||||
margin: 0;
|
||||
padding: 8px 2px;
|
||||
color: var(--text-secondary, #9aa0aa);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,18 +58,16 @@
|
|||
return VIDEO_PAGES[0];
|
||||
}
|
||||
|
||||
function showVideoPage(pageId) {
|
||||
var meta = pageMeta(pageId);
|
||||
function setActiveNav(pageId) {
|
||||
var navButtons = document.querySelectorAll('.video-nav .nav-button[data-video-page]');
|
||||
for (var i = 0; i < navButtons.length; i++) {
|
||||
navButtons[i].classList.toggle(
|
||||
'active', navButtons[i].getAttribute('data-video-page') === meta.id);
|
||||
'active', navButtons[i].getAttribute('data-video-page') === pageId);
|
||||
}
|
||||
var host = document.getElementById('video-page-host');
|
||||
if (!host) return;
|
||||
}
|
||||
|
||||
function renderPlaceholder(slot, meta) {
|
||||
// Built from our own static constants only — no user input.
|
||||
var shell = document.createElement('div');
|
||||
shell.className = 'page-shell video-placeholder';
|
||||
var h2 = document.createElement('h2');
|
||||
h2.className = 'header-title';
|
||||
var span = document.createElement('span');
|
||||
|
|
@ -78,9 +76,37 @@
|
|||
var note = document.createElement('p');
|
||||
note.className = 'video-placeholder-note';
|
||||
note.textContent = 'The ' + meta.label + ' page for the video side is coming soon.';
|
||||
host.textContent = '';
|
||||
host.appendChild(h2);
|
||||
host.appendChild(note);
|
||||
slot.textContent = '';
|
||||
slot.appendChild(h2);
|
||||
slot.appendChild(note);
|
||||
}
|
||||
|
||||
// Show one video page: reveal its built .video-subpage if one exists, else
|
||||
// fall back to the placeholder slot. Then announce it so per-page data
|
||||
// modules (e.g. video-dashboard.js) can populate themselves — they listen
|
||||
// for this event instead of being called directly, keeping each isolated.
|
||||
function showPage(pageId) {
|
||||
var meta = pageMeta(pageId);
|
||||
var host = document.getElementById('video-page-host');
|
||||
if (!host) return;
|
||||
var matched = null;
|
||||
var subpages = host.querySelectorAll('.video-subpage');
|
||||
for (var i = 0; i < subpages.length; i++) {
|
||||
var isMatch = subpages[i].getAttribute('data-video-subpage') === meta.id;
|
||||
subpages[i].hidden = !isMatch;
|
||||
if (isMatch) matched = subpages[i];
|
||||
}
|
||||
var slot = document.getElementById('video-placeholder-slot');
|
||||
if (slot) {
|
||||
slot.hidden = !!matched;
|
||||
if (!matched) renderPlaceholder(slot, meta);
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent('soulsync:video-page-shown', { detail: meta.id }));
|
||||
}
|
||||
|
||||
function navigate(pageId) {
|
||||
setActiveNav(pageId);
|
||||
showPage(pageId);
|
||||
}
|
||||
|
||||
function applySide(side) {
|
||||
|
|
@ -94,7 +120,7 @@
|
|||
}
|
||||
if (side === 'video') {
|
||||
var active = document.querySelector('.video-nav .nav-button.active');
|
||||
showVideoPage(active ? active.getAttribute('data-video-page') : DEFAULT_VIDEO_PAGE);
|
||||
navigate(active ? active.getAttribute('data-video-page') : DEFAULT_VIDEO_PAGE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,11 +145,23 @@
|
|||
(function (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
showVideoPage(btn.getAttribute('data-video-page'));
|
||||
navigate(btn.getAttribute('data-video-page'));
|
||||
});
|
||||
})(navButtons[j]);
|
||||
}
|
||||
|
||||
// In-page jumps (e.g. dashboard Quick Action tiles) navigate the same
|
||||
// way as the sidebar nav, via data-video-goto. No inline onclick.
|
||||
var gotos = document.querySelectorAll('[data-video-goto]');
|
||||
for (var k = 0; k < gotos.length; k++) {
|
||||
(function (el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
navigate(el.getAttribute('data-video-goto'));
|
||||
});
|
||||
})(gotos[k]);
|
||||
}
|
||||
|
||||
var defaultNav = document.querySelector(
|
||||
'.video-nav .nav-button[data-video-page="' + DEFAULT_VIDEO_PAGE + '"]');
|
||||
if (defaultNav) defaultNav.classList.add('active');
|
||||
|
|
|
|||
Loading…
Reference in a new issue