Mercurial > code > home > repos > video
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 > ${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; |