feat: implement video_protocol.rs, commands.rs, wire up main.rs, and index.html

- video_protocol.rs: tutdock:// custom protocol with HTTP Range support
  for video streaming, subtitle/font serving with path traversal protection
- commands.rs: all 26 Tauri command handlers as thin wrappers
- main.rs: full Tauri bootstrap with state management, window restore,
  async font caching, and ffmpeg discovery
- index.html: complete HTML markup extracted from Python app
- lib.rs: updated with all module declarations and AppPaths struct
This commit is contained in:
Your Name
2026-02-19 11:23:37 +02:00
parent 9c8474d24f
commit 4e454084a8
6 changed files with 1274 additions and 20 deletions

View File

@@ -1,14 +1,251 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TutorialDock</title>
</head>
<body>
<div id="zoomRoot">
<div class="app"></div>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>TutorialDock</title>
<link rel="stylesheet" href="tutdock://localhost/fonts.css">
<link rel="stylesheet" href="tutdock://localhost/fa.css">
</head>
<body>
<div id="zoomRoot">
<div class="app">
<div class="topbar">
<div class="brand">
<div class="appIcon" aria-hidden="true"><div class="appIconGlow"></div><i class="fa-solid fa-graduation-cap"></i></div>
<div class="brandText">
<div class="appName">TutorialDock</div>
<div class="tagline">Watch local tutorials, resume instantly, and actually finish them.</div>
</div>
</div>
<div class="actions">
<div class="actionGroup">
<label class="switch" data-tooltip="On Top" data-tooltip-desc="Keep this window above others (global)">
<input type="checkbox" id="onTopChk">
<span class="track"><span class="knob"></span></span>
<span>On top</span>
</label>
<label class="switch" data-tooltip="Autoplay" data-tooltip-desc="Autoplay next/prev (saved per folder)">
<input type="checkbox" id="autoplayChk">
<span class="track"><span class="knob"></span></span>
<span>Autoplay</span>
</label>
</div>
<div class="actionDivider"></div>
<div class="actionGroup">
<div class="zoomControl" data-tooltip="UI Zoom" data-tooltip-desc="Adjust the interface zoom level">
<button class="zoomBtn" id="zoomOutBtn"><i class="fa-solid fa-minus"></i></button>
<span class="zoomValue" id="zoomResetBtn">100%</span>
<button class="zoomBtn" id="zoomInBtn"><i class="fa-solid fa-plus"></i></button>
</div>
</div>
<div class="actionDivider"></div>
<div class="actionGroup">
<div class="splitBtn primary">
<button class="btn primary" id="chooseBtn" data-tooltip="Open Folder" data-tooltip-desc="Browse and select a folder containing videos"><i class="fa-solid fa-folder-open"></i> Open folder</button>
<button class="btn drop" id="chooseDropBtn" data-tooltip="Recent Folders" data-tooltip-desc="Open a recently used folder"><i class="fa-solid fa-chevron-down"></i></button>
</div>
</div>
<div class="actionDivider"></div>
<div class="actionGroup">
<button class="toolbarBtn" id="resetProgBtn" data-tooltip="Reset Progress" data-tooltip-desc="Reset DONE / NOW progress for this folder (keeps notes, volume, etc.)"><i class="fa-solid fa-clock-rotate-left"></i></button>
<button class="toolbarBtn" id="refreshBtn" data-tooltip="Reload" data-tooltip-desc="Reload the current folder"><i class="fa-solid fa-arrows-rotate"></i></button>
</div>
</div>
</div>
<script type="module" src="/main.ts"></script>
</body>
<div class="dropdownPortal" id="recentMenu"></div>
<div class="content" id="contentGrid">
<div class="panel">
<div class="panelHeader">
<div style="min-width:0;">
<div class="nowTitle" id="nowTitle">No video loaded</div>
<div class="nowSub" id="nowSub">-</div>
</div>
<div class="progressPill" data-tooltip="Overall Progress" data-tooltip-desc="Folder completion (time-based, using cached durations when available)">
<div class="progressLabel">Overall</div>
<div class="progressBar"><div id="overallBar"></div></div>
<div class="progressPct" id="overallPct">-</div>
</div>
</div>
<div class="videoWrap">
<video id="player" preload="metadata"></video>
<div class="videoOverlay" id="videoOverlay">
<div class="overlayIcon" id="overlayIcon">
<i class="fa-solid fa-play" id="overlayIconI"></i>
</div>
</div>
</div>
<div class="controls">
<div class="controlsRow">
<div class="group">
<button class="iconBtn" id="prevBtn" data-tooltip="Previous" data-tooltip-desc="Go to previous video"><i class="fa-solid fa-backward-step"></i></button>
<button class="iconBtn primary" id="playPauseBtn" data-tooltip="Play/Pause" data-tooltip-desc="Toggle video playback">
<i class="fa-solid fa-play" id="ppIcon"></i>
</button>
<button class="iconBtn" id="nextBtn" data-tooltip="Next" data-tooltip-desc="Go to next video"><i class="fa-solid fa-forward-step"></i></button>
<div class="timeChip" data-tooltip="Time" data-tooltip-desc="Current position / Total duration">
<div class="timeDot"></div>
<div><span id="timeNow">00:00</span> <span style="color:rgba(165,172,196,.65)">/</span> <span id="timeDur">00:00</span></div>
</div>
</div>
<div class="group">
<div class="subsBox">
<button class="iconBtn" id="subsBtn" data-tooltip="Subtitles" data-tooltip-desc="Load or select subtitles"><i class="fa-regular fa-closed-captioning"></i></button>
<div class="subsMenu" id="subsMenu" role="menu"></div>
</div>
<div class="miniCtl" data-tooltip="Volume" data-tooltip-desc="Adjust volume (saved per folder)">
<i class="fa-solid fa-volume-high"></i>
<div class="volWrap">
<div class="volTrack">
<div class="volFill" id="volFill"></div>
</div>
<input type="range" id="volSlider" class="vol" min="0" max="1" step="0.01" value="1">
</div>
<div class="volTooltip" id="volTooltip">100%</div>
</div>
<div class="miniCtl" data-tooltip="Speed" data-tooltip-desc="Playback speed (saved per folder)">
<svg id="speedIcon" width="14" height="14" viewBox="0 0 24 24" style="overflow:visible; color:var(--iconStrong);">
<path d="M12 22C6.5 22 2 17.5 2 12S6.5 2 12 2s10 4.5 10 10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity=".5"/>
<path d="M12 22c5.5 0 10-4.5 10-10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity=".3"/>
<g transform="rotate(0, 12, 13)">
<line x1="12" y1="13" x2="12" y2="5" stroke="rgba(255,255,255,.85)" stroke-width="2.5" stroke-linecap="round"/>
</g>
<circle cx="12" cy="13" r="2" fill="currentColor" opacity=".7"/>
</svg>
<div class="speedBox">
<button class="speedBtn" id="speedBtn" aria-label="Playback speed">
<span id="speedBtnText">1.0x</span>
<span class="speedCaret" aria-hidden="true"><i class="fa-solid fa-chevron-up"></i></span>
</button>
<div class="speedMenu" id="speedMenu" role="menu" aria-label="Speed menu"></div>
</div>
</div>
<button class="iconBtn" id="fsBtn" data-tooltip="Fullscreen" data-tooltip-desc="Toggle fullscreen mode"><i class="fa-solid fa-expand"></i></button>
</div>
</div>
<div class="seekWrap">
<div class="seekTrack">
<div class="seekFill" id="seekFill"></div>
</div>
<input type="range" id="seek" class="seek" min="0" max="1000" step="1" value="0" aria-label="Seek">
</div>
</div>
<div class="dock" id="dockGrid">
<div class="dockPane">
<div class="dockInner">
<div class="dockHeader" id="notesHeader" data-tooltip="Notes" data-tooltip-desc="Your notes are automatically saved for each video file. Write timestamps, TODOs, or reminders.">
<div class="dockTitle"><i class="fa-solid fa-note-sticky"></i> Notes</div>
</div>
<div class="notesArea">
<textarea class="notes" id="notesBox" placeholder="Write timestamps, TODOs, reminders…"></textarea>
<div class="notesSaved" id="notesSaved"><i class="fa-solid fa-check"></i> Saved</div>
</div>
</div>
</div>
<div class="dockDividerWrap">
<div class="dockDivider" id="dockDivider" data-tooltip="Resize" data-tooltip-desc="Drag to resize panels"></div>
</div>
<div class="dockPane">
<div class="dockInner">
<div class="dockHeader" id="infoHeader" data-tooltip="Info" data-tooltip-desc="Metadata and progress info for the current folder and video. Updates automatically.">
<div class="dockTitle"><i class="fa-solid fa-circle-info"></i> Info</div>
</div>
<div class="infoGrid" id="infoGrid">
<div class="kv">
<div class="k">Folder</div><div class="v" id="infoFolder">-</div>
<div class="k">Next up</div><div class="v" id="infoNext">-</div>
<div class="k">Structure</div><div class="v mono" id="infoStruct">-</div>
</div>
<div class="kv">
<div class="k">Title</div><div class="v" id="infoTitle">-</div>
<div class="k">Relpath</div><div class="v mono" id="infoRel">-</div>
<div class="k">Position</div><div class="v mono" id="infoPos">-</div>
</div>
<div class="kv">
<div class="k">File</div><div class="v mono" id="infoFileBits">-</div>
<div class="k">Video</div><div class="v mono" id="infoVidBits">-</div>
<div class="k">Audio</div><div class="v mono" id="infoAudBits">-</div>
<div class="k">Subtitles</div><div class="v mono" id="infoSubsBits">-</div>
</div>
<div class="kv">
<div class="k">Finished</div><div class="v mono" id="infoFinished">-</div>
<div class="k">Remaining</div><div class="v mono" id="infoRemaining">-</div>
<div class="k">ETA</div><div class="v mono" id="infoEta">-</div>
</div>
<div class="kv">
<div class="k">Volume</div><div class="v mono" id="infoVolume">-</div>
<div class="k">Speed</div><div class="v mono" id="infoSpeed">-</div>
<div class="k">Durations</div><div class="v mono" id="infoKnown">-</div>
</div>
<div class="kv">
<div class="k">Top folders</div><div class="v mono" id="infoTop">-</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="dividerWrap">
<div class="divider" id="divider" data-tooltip="Resize" data-tooltip-desc="Drag to resize panels"></div>
</div>
<div class="panel">
<div class="panelHeader" style="align-items:center;">
<div class="playlistHeader" id="plistHeader" data-tooltip="Playlist" data-tooltip-desc="Drag items to reorder. The blue line shows where it will drop."><i class="fa-solid fa-list"></i> Playlist</div>
<div style="flex:1 1 auto;"></div>
</div>
<div class="listWrap">
<div class="list" id="list"></div>
<div class="listScrollbar" id="listScrollbar"><div class="listScrollbarThumb" id="listScrollbarThumb"></div></div>
</div>
<div class="empty" id="emptyHint" style="display:none;">
No videos found (searched recursively).<br/>Native playback is happiest with MP4 (H.264/AAC) or WebM.
</div>
</div>
</div>
</div>
</div>
<div id="toast" aria-live="polite">
<div class="toastInner">
<div class="toastIcon"><i class="fa-solid fa-circle-info"></i></div>
<div class="toastMsg" id="toastMsg">-</div>
</div>
</div>
<div class="tooltip" id="fancyTooltip"><div class="tooltip-title"></div><div class="tooltip-desc"></div></div>
<script type="module" src="/main.ts"></script>
</body>
</html>