TutorialVault: complete Tauri v2 port with runtime fixes
Rename from TutorialDock to TutorialVault. Remove legacy Python app and scripts. Fix video playback, subtitles, metadata display, window state persistence, and auto-download of ffmpeg/ffprobe on first run. Bundle fonts via npm instead of runtime download.
This commit is contained in:
@@ -31,7 +31,7 @@ export const api = {
|
||||
invoke<OkResponse>('set_current', { index, timecode }),
|
||||
|
||||
tickProgress: (index: number, currentTime: number, duration: number | null, playing: boolean) =>
|
||||
invoke<OkResponse>('tick_progress', { index, current_time: currentTime, duration, playing }),
|
||||
invoke<OkResponse>('tick_progress', { index, currentTime, duration, playing }),
|
||||
|
||||
setFolderVolume: (volume: number) =>
|
||||
invoke<OkResponse>('set_folder_volume', { volume }),
|
||||
@@ -76,13 +76,13 @@ export const api = {
|
||||
invoke<EmbeddedSubsResponse>('get_embedded_subtitles'),
|
||||
|
||||
extractEmbeddedSubtitle: (trackIndex: number) =>
|
||||
invoke<SubtitleResponse>('extract_embedded_subtitle', { track_index: trackIndex }),
|
||||
invoke<SubtitleResponse>('extract_embedded_subtitle', { trackIndex }),
|
||||
|
||||
getAvailableSubtitles: () =>
|
||||
invoke<AvailableSubsResponse>('get_available_subtitles'),
|
||||
|
||||
loadSidecarSubtitle: (filePath: string) =>
|
||||
invoke<SubtitleResponse>('load_sidecar_subtitle', { file_path: filePath }),
|
||||
invoke<SubtitleResponse>('load_sidecar_subtitle', { filePath }),
|
||||
|
||||
chooseSubtitleFile: () =>
|
||||
invoke<SubtitleResponse>('choose_subtitle_file'),
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
<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">
|
||||
<title>TutorialVault</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="zoomRoot">
|
||||
@@ -14,7 +12,7 @@
|
||||
<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="appName">TutorialVault</div>
|
||||
<div class="tagline">Watch local tutorials, resume instantly, and actually finish them.</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,7 +78,7 @@
|
||||
</div>
|
||||
|
||||
<div class="videoWrap">
|
||||
<video id="player" preload="metadata"></video>
|
||||
<video id="player" preload="metadata" crossorigin="anonymous"></video>
|
||||
<div class="videoOverlay" id="videoOverlay">
|
||||
<div class="overlayIcon" id="overlayIcon">
|
||||
<i class="fa-solid fa-play" id="overlayIconI"></i>
|
||||
|
||||
13
src/main.ts
13
src/main.ts
@@ -2,6 +2,19 @@
|
||||
* TutorialDock frontend — boot sequence, tick loop, global wiring.
|
||||
* Orchestrates all modules and holds cross-module callbacks.
|
||||
*/
|
||||
import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||
import '@fontsource/sora/500.css';
|
||||
import '@fontsource/sora/600.css';
|
||||
import '@fontsource/sora/700.css';
|
||||
import '@fontsource/sora/800.css';
|
||||
import '@fontsource/manrope/400.css';
|
||||
import '@fontsource/manrope/500.css';
|
||||
import '@fontsource/manrope/600.css';
|
||||
import '@fontsource/manrope/700.css';
|
||||
import '@fontsource/manrope/800.css';
|
||||
import '@fontsource/ibm-plex-mono/400.css';
|
||||
import '@fontsource/ibm-plex-mono/500.css';
|
||||
import '@fontsource/ibm-plex-mono/600.css';
|
||||
import './styles/main.css';
|
||||
import './styles/player.css';
|
||||
import './styles/playlist.css';
|
||||
|
||||
@@ -296,7 +296,7 @@ export async function loadVideoSrc(
|
||||
const keepRate = clamp(Number(library.folder_rate ?? player.playbackRate ?? 1.0), 0.25, 3);
|
||||
|
||||
setSuppressTick(true);
|
||||
player.src = `tutdock://localhost/video/${idx}`;
|
||||
player.src = `http://tutdock.localhost/video/${idx}`;
|
||||
player.load();
|
||||
|
||||
player.onloadedmetadata = async () => {
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface LibraryInfo {
|
||||
finished_count?: number;
|
||||
remaining_count?: number;
|
||||
remaining_seconds_known?: number | null;
|
||||
top_folders?: [string, number][];
|
||||
top_folders?: { name: string; total: number; finished: number }[];
|
||||
next_up?: NextUp | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -328,9 +328,9 @@ export function updateOverall(): void {
|
||||
if (library.overall_progress === null || library.overall_progress === undefined) {
|
||||
overallBar.style.width = '0%'; overallPct.textContent = '-'; return;
|
||||
}
|
||||
const p = clamp(library.overall_progress, 0, 1);
|
||||
overallBar.style.width = `${(p * 100).toFixed(1)}%`;
|
||||
overallPct.textContent = `${(p * 100).toFixed(1)}%`;
|
||||
const p = clamp(library.overall_progress, 0, 100);
|
||||
overallBar.style.width = `${p.toFixed(1)}%`;
|
||||
overallPct.textContent = `${p.toFixed(1)}%`;
|
||||
}
|
||||
|
||||
export function updateInfoPanel(): void {
|
||||
@@ -350,7 +350,7 @@ export function updateInfoPanel(): void {
|
||||
infoRemaining.textContent = `${library.remaining_count ?? 0}`;
|
||||
infoEta.textContent = (library.remaining_seconds_known != null) ? fmtTime(library.remaining_seconds_known) : '-';
|
||||
infoKnown.textContent = `${library.durations_known || 0}/${library.count || 0}`;
|
||||
infoTop.textContent = (library.top_folders || []).map(([n, c]: [string, number]) => `${n}:${c}`).join(' \u2022 ') || '-';
|
||||
infoTop.textContent = (library.top_folders || []).map((f: any) => `${f.name}: ${f.finished}/${f.total}`).join(' \u2022 ') || '-';
|
||||
infoVolume.textContent = `${Math.round(clamp(Number(library.folder_volume ?? 1), 0, 1) * 100)}%`;
|
||||
infoSpeed.textContent = `${Number(library.folder_rate ?? 1).toFixed(2)}x`;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user