comparison src/VideoPage.ts @ 39:b5b29f6ef5cb

cleanup and refactor
author drewp@bigasterisk.com
date Thu, 05 Dec 2024 17:01:53 -0800
parents 0aea9e55899b
children 44bd161e4779
comparison
equal deleted inserted replaced
38:0aea9e55899b 39:b5b29f6ef5cb
1 import { LitElement, PropertyValues, css, html, unsafeCSS } from "lit"; 1 import { LitElement, PropertyValues, TemplateResult, css, html, unsafeCSS } from "lit";
2 import { customElement, property } from "lit/decorators.js"; 2 import { customElement, property } from "lit/decorators.js";
3 import { PagePlayer, ShakaVideoElement } from "./PagePlayer"; 3 import { PagePlayer, ShakaVideoElement } from "./PagePlayer";
4 import maincss from "./main.css?inline"; 4 import maincss from "./main.css?inline";
5 export { SlProgressBar } from "@shoelace-style/shoelace"; 5 export { SlProgressBar } from "@shoelace-style/shoelace";
6 export { PagePlayer } from "./PagePlayer"; 6 export { PagePlayer } from "./PagePlayer";
7 export { VideoSection } from "./VideoSection"; 7 export { VideoSection } from "./VideoSection";
8 import { Routes, Router } from "@lit-labs/router"; 8 import { Routes, Router } from "@lit-labs/router";
9 9
10 interface VideoFile { 10 interface VideoFile {
11 webRelPath: string; 11 webRelPath: string;
12 webDataPath: string;
12 label: string; 13 label: string;
13 thumbRelPath: string; 14 }
14 webDataPath: string; 15
15 }
16 interface Subdir { 16 interface Subdir {
17 label: string; 17 label: string;
18 path: string; 18 path: string;
19 } 19 }
20 20
21 interface VideoListings {
22 videos: VideoFile[];
23 subdirs: Subdir[];
24 }
25
21 function subdirQuery(subdir: string): string { 26 function subdirQuery(subdir: string): string {
22 return "subdir=" + encodeURIComponent(subdir); 27 return "subdir=" + encodeURIComponent(subdir);
28 }
29
30 class route {
31 path = "/video/*";
32 videoListings: VideoListings | null = null;
33 showVid: string | null = null;
34 dirName?: string;
35
36 link(wrp: string): string {
37 return "/video/" + wrp;
38 }
39
40 async enter(params: { [key: string]: string | undefined }): Promise<boolean> {
41 const webRelPath = "/" + params[0]!;
42 this.dirName = webRelPath.replace(/.*\/([^/]+)/, "$1");
43 const resp = await fetch("/video/api/videos?" + subdirQuery(webRelPath));
44 if (resp.status == 404) {
45 return false;
46 }
47 this.videoListings = await resp.json();
48
49 if (webRelPath.endsWith("/")) {
50 this.showVid = null;
51 } else {
52 this.showVid = webRelPath;
53 }
54
55 return true;
56 }
57
58 render(p: { [key: string]: string | undefined }) {
59 return html`<video-page2
60 .link=${this.link.bind(this)}
61 .showVid=${this.showVid}
62 .videoListings=${this.videoListings}
63 .dirName=${this.dirName}
64 ></video-page2>`;
65 }
23 } 66 }
24 67
25 @customElement("video-page") 68 @customElement("video-page")
26 export class VideoPage extends LitElement { 69 export class VideoPage extends LitElement {
27 static styles = [unsafeCSS(maincss)]; 70 static styles = [unsafeCSS(maincss)];
28 private _router = new Router( 71 private _router = new Router(this, [new route()], {});
29 this,
30 [
31 {
32 path: "/video/*",
33 enter: async (params: { [key: string]: string | undefined }): Promise<boolean> => {
34 const webRelPath = '/' + params[0]!;
35 console.log("enter", webRelPath);
36 // this.updatePathSegs(subdir);
37 const resp = await (await fetch("/video/api/videos?" + subdirQuery(webRelPath))).json();
38 console.log("resp", resp);
39 // 404 page goes here
40 this.resp = resp;
41
42
43 let vid: string | null;
44 let dir: string;
45
46 if (webRelPath.endsWith("/")) {
47 vid = null;
48 dir = webRelPath;
49 } else {
50 vid = webRelPath;
51 dir = webRelPath.slice(0, webRelPath.lastIndexOf("/"));
52 }
53 this.showVid = vid
54 this.linkx = (wrp: string) => { return '/video/' + wrp; };
55
56 return true;
57 },
58 render: (p: { [key: string]: string | undefined }) => {
59 return html`<video-page2 .link=${this.linkx.bind(this)} .showVid=${this.showVid} .resp=${this.resp}></video-page2>`;
60 },
61 },
62 ],
63 {}
64 );
65 72
66 render() { 73 render() {
67 const requestedPath = this._router.params[0]; 74 const requestedPath = this._router.params[0];
75 const segs = ["."].concat(requestedPath || "".split("/"));
76 const crumbs = [];
77 // todo: goal is to have '🏠 TOP' and every level not including current dir
78 for (let i = 0; i < segs.length; i++) {
79 const seg = segs[i];
80 const href = "../".repeat(segs.length - i - 1);
81 const label = i == 0 ? html`<sl-icon name="house"></sl-icon>` : seg;
82 console.log(href, label);
83 crumbs.push(html`<sl-breadcrumb-item><a href=${href}>${label}</a></sl-breadcrumb-item>`);
84 }
68 return html` 85 return html`
69 <header><img src="${this._router.link("/video/logo1.png")}" title="JelloBello" /> <span class="breadcrumbs2">TOP &gt; ${requestedPath}</span></header> 86 <header>
70 <main> 87 <img src="${this._router.link("/video/logo1.png")}" title="JelloBello" />
71 ${this._router.outlet()} 88 <sl-breadcrumb>${crumbs}</sl-breadcrumb>
72 </main> 89 </header>
90 <main>${this._router.outlet()}</main>
73 <footer>foot</footer> 91 <footer>foot</footer>
74 `; 92 `;
75 } 93 }
76 } 94 }
77 95
78 @customElement("video-page2") 96 @customElement("video-page2")
79 export class VideoPage2 extends LitElement { 97 export class VideoPage2 extends LitElement {
80 @property() showVid?: string; 98 @property() showVid?: string;
81 @property() resp?: any; 99 @property() videoListings?: VideoListings;
82 @property() link!: (s: string) => string; 100 @property() link!: (s: string) => string;
83 videos: VideoFile[]; 101 @property() dirName?: string;
84 subdirs: Subdir[];
85 @property() pathSegs: { label: string; subdir: string }[];
86 constructor() {
87 super();
88 this.videos = [];
89 this.subdirs = [];
90 this.pathSegs = [];
91 }
92 102
93 protected firstUpdated(_changedProperties: PropertyValues): void { 103 protected firstUpdated(_changedProperties: PropertyValues): void {
94 document.addEventListener("keydown", (e) => { 104 document.addEventListener("keydown", (e) => {
95 if (e.key == "Escape") { 105 if (e.key == "Escape") {
96 this.closePlayer(); 106 this.closePlayer();
97 } 107 }
98 }); 108 });
99 } 109 }
110
100 protected update(changedProperties: PropertyValues<this>): void { 111 protected update(changedProperties: PropertyValues<this>): void {
101 const resp = changedProperties.has('resp'); 112 const resp = changedProperties.has("videoListings");
102 if (resp) { 113 if (resp) {
103 this.videos = (this.resp.videos || []) as VideoFile[];
104 this.subdirs = (this.resp.subdirs || []) as Subdir[];
105 console.log('set', this.videos, this.subdirs);
106 // if (this.showVid) { 114 // if (this.showVid) {
107 // this.openPlayer(); 115 // this.openPlayer();
108 // } else { 116 // } else {
109 // this.closePlayer(); 117 // this.closePlayer();
110 // } 118 // }
111 } 119 }
112 super.update(changedProperties); 120 super.update(changedProperties);
113 } 121 }
114 updatePathSegs(subdir: string) {
115 this.pathSegs = [{ label: "TOP", subdir: "/" }];
116 if (subdir != "/") {
117 let acc = "";
118 const segs = subdir.split("/");
119 segs.splice(0, 1); // the root
120 for (let seg of segs) {
121 acc += "/" + seg;
122 this.pathSegs.push({ label: seg, subdir: acc });
123 }
124 }
125 }
126
127 122
128 static styles = [ 123 static styles = [
129 unsafeCSS(maincss), 124 unsafeCSS(maincss),
130 css` 125 css`
131 :host { 126 :host {
153 inset: 0; 148 inset: 0;
154 display: none; 149 display: none;
155 } 150 }
156 `, 151 `,
157 ]; 152 ];
153
154 thumbSrc(v: VideoFile) {
155 return "/video/api/thumbnail?webRelPath=" + encodeURIComponent(v.webRelPath);
156 }
157
158 renderSubdir(subdir: Subdir): TemplateResult {
159 return html`<div class="subdir"><a href="${this.link(subdir.path) + "/"}">${subdir.label}</a></div>`;
160 }
161
162 renderVideoListing(video: VideoFile) {
163 return html`<video-section
164 @playVideo=${this.playVideo}
165 thumbRelPath=${this.thumbSrc(video)}
166 title="${video.label}"
167 manifest="/video/files/${video.webDataPath}"
168 ></video-section>`;
169 }
170
158 render() { 171 render() {
159 const thumbSrc = (v: VideoFile) => { 172 if (this.videoListings == null) {
160 return "/video/api/thumbnail?webRelPath=" + encodeURIComponent(v.webRelPath); 173 return html`<div>Loading...</div>`;
161 }; 174 }
175 const listings = [
176 html`${this.videoListings.subdirs.map((s) => this.renderSubdir(s))}`, //
177 html`${this.videoListings.videos.map((v) => this.renderVideoListing(v))}`,
178 ];
162 return html` 179 return html`
163 <sl-breadcrumb> 180 <h2>${this.dirName}</h2>
164 ${this.pathSegs.map( 181 <div class="listing">${listings}</div>
165 (seg, i) => 182
166 html`<sl-breadcrumb-item>
167 <a href="./?${subdirQuery(seg.subdir)}"> ${i == 0 ? html`<sl-icon name="house"></sl-icon>` : ""} ${seg.label} </a></sl-breadcrumb-item
168 >`
169 )}
170 </sl-breadcrumb>
171
172 <div class="listing">
173 ${this.subdirs.map((s) => html`<div class="subdir"><a href="${this.link(s.path) + "/"}">${s.label}</a></div>`)}
174 ${this.videos.map(
175 (v) =>
176 html`<video-section
177 @playVideo=${this.playVideo}
178 thumbRelPath=${thumbSrc(v)}
179 title="${v.label}"
180 manifest="/video/files/${v.webDataPath}"
181 ></video-section>`
182 )}
183 </div>
184 <p><a href="ingest/">Add new videos...</a></p> 183 <p><a href="ingest/">Add new videos...</a></p>
185 184
186 <div id="scrim" @click=${this.closePlayer}></div> 185 <div id="scrim" @click=${this.closePlayer}></div>
187 <page-player manifest=""></page-player> 186 <page-player manifest=""></page-player>
188 `; 187 `;
189 } 188 }
189
190 escapeALittle(fileUri: string): string { 190 escapeALittle(fileUri: string): string {
191 return fileUri.replace("#", encodeURIComponent("#")); 191 return fileUri.replace("#", encodeURIComponent("#"));
192 } 192 }
193
193 playVideo(ev: CustomEvent) { 194 playVideo(ev: CustomEvent) {
194 const player = this.shadowRoot!.querySelector("page-player")! as PagePlayer; 195 const player = this.shadowRoot!.querySelector("page-player")! as PagePlayer;
195 196
196 player.manifest = this.escapeALittle(ev.detail.manifest); 197 player.manifest = this.escapeALittle(ev.detail.manifest);
197 const sv = player.shadowRoot!.querySelector("shaka-video")! as ShakaVideoElement; 198 const sv = player.shadowRoot!.querySelector("shaka-video")! as ShakaVideoElement;