changeset 39:b5b29f6ef5cb

cleanup and refactor
author drewp@bigasterisk.com
date Thu, 05 Dec 2024 17:01:53 -0800
parents 0aea9e55899b
children f60e1ebe69da
files src/PagePlayer.ts src/VideoPage.ts video.py
diffstat 3 files changed, 105 insertions(+), 100 deletions(-) [+]
line wrap: on
line diff
--- a/src/PagePlayer.ts	Wed Dec 04 21:50:16 2024 -0800
+++ b/src/PagePlayer.ts	Thu Dec 05 17:01:53 2024 -0800
@@ -22,6 +22,7 @@
       }
       div.big {
         inset: 5em;
+        background: #2c2c2c;
       }
       div.hidden {
         display: none;
--- a/src/VideoPage.ts	Wed Dec 04 21:50:16 2024 -0800
+++ b/src/VideoPage.ts	Thu Dec 05 17:01:53 2024 -0800
@@ -1,4 +1,4 @@
-import { LitElement, PropertyValues, css, html, unsafeCSS } from "lit";
+import { LitElement, PropertyValues, TemplateResult, css, html, unsafeCSS } from "lit";
 import { customElement, property } from "lit/decorators.js";
 import { PagePlayer, ShakaVideoElement } from "./PagePlayer";
 import maincss from "./main.css?inline";
@@ -9,67 +9,85 @@
 
 interface VideoFile {
   webRelPath: string;
+  webDataPath: string;
   label: string;
-  thumbRelPath: string;
-  webDataPath: string;
 }
+
 interface Subdir {
   label: string;
   path: string;
 }
 
+interface VideoListings {
+  videos: VideoFile[];
+  subdirs: Subdir[];
+}
+
 function subdirQuery(subdir: string): string {
   return "subdir=" + encodeURIComponent(subdir);
 }
 
+class route {
+  path = "/video/*";
+  videoListings: VideoListings | null = null;
+  showVid: string | null = null;
+  dirName?: string;
+
+  link(wrp: string): string {
+    return "/video/" + wrp;
+  }
+
+  async enter(params: { [key: string]: string | undefined }): Promise<boolean> {
+    const webRelPath = "/" + params[0]!;
+    this.dirName = webRelPath.replace(/.*\/([^/]+)/, "$1");
+    const resp = await fetch("/video/api/videos?" + subdirQuery(webRelPath));
+    if (resp.status == 404) {
+      return false;
+    }
+    this.videoListings = await resp.json();
+
+    if (webRelPath.endsWith("/")) {
+      this.showVid = null;
+    } else {
+      this.showVid = webRelPath;
+    }
+
+    return true;
+  }
+
+  render(p: { [key: string]: string | undefined }) {
+    return html`<video-page2
+      .link=${this.link.bind(this)}
+      .showVid=${this.showVid}
+      .videoListings=${this.videoListings}
+      .dirName=${this.dirName}
+    ></video-page2>`;
+  }
+}
+
 @customElement("video-page")
 export class VideoPage extends LitElement {
   static styles = [unsafeCSS(maincss)];
-  private _router = new Router(
-    this,
-    [
-      {
-        path: "/video/*",
-        enter: async (params: { [key: string]: string | undefined }): Promise<boolean> => {
-          const webRelPath = '/' + params[0]!;
-          console.log("enter", webRelPath);
-          // this.updatePathSegs(subdir);
-          const resp = await (await fetch("/video/api/videos?" + subdirQuery(webRelPath))).json();
-          console.log("resp", resp);
-          // 404 page goes here
-          this.resp = resp;
-
-
-          let vid: string | null;
-          let dir: string;
-
-          if (webRelPath.endsWith("/")) {
-            vid = null;
-            dir = webRelPath;
-          } else {
-            vid = webRelPath;
-            dir = webRelPath.slice(0, webRelPath.lastIndexOf("/"));
-          }
-          this.showVid = vid
-          this.linkx = (wrp: string) => { return '/video/' + wrp; };
-
-          return true;
-        },
-        render: (p: { [key: string]: string | undefined }) => {
-          return html`<video-page2 .link=${this.linkx.bind(this)} .showVid=${this.showVid} .resp=${this.resp}></video-page2>`;
-        },
-      },
-    ],
-    {}
-  );
+  private _router = new Router(this, [new route()], {});
 
   render() {
     const requestedPath = this._router.params[0];
+    const segs = ["."].concat(requestedPath || "".split("/"));
+    const crumbs = [];
+    // todo: goal is to have '🏠 TOP' and every level not including current dir
+    for (let i = 0; i < segs.length; i++) {
+      const seg = segs[i];
+      const href = "../".repeat(segs.length - i - 1);
+      const label = i == 0 ? html`<sl-icon name="house"></sl-icon>` : seg;
+      console.log(href, label);
+      crumbs.push(html`<sl-breadcrumb-item><a href=${href}>${label}</a></sl-breadcrumb-item>`);
+    }
     return html`
-      <header><img src="${this._router.link("/video/logo1.png")}" title="JelloBello" /> <span class="breadcrumbs2">TOP &gt; ${requestedPath}</span></header>
-      <main>
-        ${this._router.outlet()}
-      </main>
+      <header>
+        <img src="${this._router.link("/video/logo1.png")}" title="JelloBello" />
+        <sl-breadcrumb>${crumbs}</sl-breadcrumb>
+      </header>
+      <main>${this._router.outlet()}</main>
       <footer>foot</footer>
     `;
   }
@@ -78,17 +96,9 @@
 @customElement("video-page2")
 export class VideoPage2 extends LitElement {
   @property() showVid?: string;
-  @property() resp?: any;
+  @property() videoListings?: VideoListings;
   @property() link!: (s: string) => string;
-  videos: VideoFile[];
-  subdirs: Subdir[];
-  @property() pathSegs: { label: string; subdir: string }[];
-  constructor() {
-    super();
-    this.videos = [];
-    this.subdirs = [];
-    this.pathSegs = [];
-  }
+  @property() dirName?: string;
 
   protected firstUpdated(_changedProperties: PropertyValues): void {
     document.addEventListener("keydown", (e) => {
@@ -97,12 +107,10 @@
       }
     });
   }
+
   protected update(changedProperties: PropertyValues<this>): void {
-    const resp = changedProperties.has('resp');
+    const resp = changedProperties.has("videoListings");
     if (resp) {
-      this.videos = (this.resp.videos || []) as VideoFile[];
-      this.subdirs = (this.resp.subdirs || []) as Subdir[];
-      console.log('set', this.videos, this.subdirs);
       // if (this.showVid) {
       //   this.openPlayer();
       // } else {
@@ -111,19 +119,6 @@
     }
     super.update(changedProperties);
   }
-  updatePathSegs(subdir: string) {
-    this.pathSegs = [{ label: "TOP", subdir: "/" }];
-    if (subdir != "/") {
-      let acc = "";
-      const segs = subdir.split("/");
-      segs.splice(0, 1); // the root
-      for (let seg of segs) {
-        acc += "/" + seg;
-        this.pathSegs.push({ label: seg, subdir: acc });
-      }
-    }
-  }
-
 
   static styles = [
     unsafeCSS(maincss),
@@ -155,41 +150,47 @@
       }
     `,
   ];
+
+  thumbSrc(v: VideoFile) {
+    return "/video/api/thumbnail?webRelPath=" + encodeURIComponent(v.webRelPath);
+  }
+
+  renderSubdir(subdir: Subdir): TemplateResult {
+    return html`<div class="subdir"><a href="${this.link(subdir.path) + "/"}">${subdir.label}</a></div>`;
+  }
+
+  renderVideoListing(video: VideoFile) {
+    return html`<video-section
+      @playVideo=${this.playVideo}
+      thumbRelPath=${this.thumbSrc(video)}
+      title="${video.label}"
+      manifest="/video/files/${video.webDataPath}"
+    ></video-section>`;
+  }
+
   render() {
-    const thumbSrc = (v: VideoFile) => {
-      return "/video/api/thumbnail?webRelPath=" + encodeURIComponent(v.webRelPath);
-    };
+    if (this.videoListings == null) {
+      return html`<div>Loading...</div>`;
+    }
+    const listings = [
+      html`${this.videoListings.subdirs.map((s) => this.renderSubdir(s))}`, //
+      html`${this.videoListings.videos.map((v) => this.renderVideoListing(v))}`,
+    ];
     return html`
-      <sl-breadcrumb>
-        ${this.pathSegs.map(
-      (seg, i) =>
-        html`<sl-breadcrumb-item>
-              <a href="./?${subdirQuery(seg.subdir)}"> ${i == 0 ? html`<sl-icon name="house"></sl-icon>` : ""} ${seg.label} </a></sl-breadcrumb-item
-            >`
-    )}
-      </sl-breadcrumb>
+      <h2>${this.dirName}</h2>
+      <div class="listing">${listings}</div>
 
-      <div class="listing">
-        ${this.subdirs.map((s) => html`<div class="subdir"><a href="${this.link(s.path) + "/"}">${s.label}</a></div>`)}
-        ${this.videos.map(
-      (v) =>
-        html`<video-section
-              @playVideo=${this.playVideo}
-              thumbRelPath=${thumbSrc(v)}
-              title="${v.label}"
-              manifest="/video/files/${v.webDataPath}"
-            ></video-section>`
-    )}
-      </div>
       <p><a href="ingest/">Add new videos...</a></p>
 
       <div id="scrim" @click=${this.closePlayer}></div>
       <page-player manifest=""></page-player>
     `;
   }
+
   escapeALittle(fileUri: string): string {
     return fileUri.replace("#", encodeURIComponent("#"));
   }
+
   playVideo(ev: CustomEvent) {
     const player = this.shadowRoot!.querySelector("page-player")! as PagePlayer;
 
--- a/video.py	Wed Dec 04 21:50:16 2024 -0800
+++ b/video.py	Thu Dec 05 17:01:53 2024 -0800
@@ -34,16 +34,16 @@
     if not subdir.endswith('/'):
         # raise ValueError(f'not a dir {subdir=}')
         # ok we'll list the parent dir underneath
-        subdir = '/'.join(subdir.split('/')[:-1]) # todo move to FE
+        subdir = '/'.join(subdir.split('/')[:-1])  # todo move to FE
     else:
         subdir = subdir[:-1]
-    if subdir=="":
+    if subdir == "":
         subdir = "./"
-    if not (subdir.startswith('/') or subdir=='./'):
+    if not (subdir.startswith('/') or subdir == './'):
         raise ValueError(f'not a dir {subdir=}')
     subdir = subdir[1:]
     log.debug(f'videos request corrected to: {subdir=}')
-    
+
     vfInDir = store.findInDir(subdir)
     return JSONResponse({
         "videos": [{
@@ -81,13 +81,16 @@
 
     return EventSourceResponse(g())
 
+
 def getDiskPath(fs, webRelPath):
     doc = fs.find_one({'webRelPath': webRelPath})
     if doc is None:
         raise ValueError
     return doc['diskPath']
 
-async def getThumbnail(db: pymongo.database.Database, req: Request) -> Response:
+
+async def getThumbnail(db: pymongo.database.Database,
+                       req: Request) -> Response:
     webRelPath = req.query_params['webRelPath']
     fs = db.get_collection('fs')
     diskPath = getDiskPath(fs, webRelPath)
@@ -95,7 +98,6 @@
     async with asyncio.timeout(10):
         data = await thumbnail.getThumbnailData(th, diskPath)
         return Response(content=data, media_type='image/jpeg')
-    
 
 
 db = open_mongo_or_die().get_database('video')
@@ -129,6 +131,7 @@
     app.state.processTask = asyncio.create_task(dl_queue.process())
     return app
 
+
 uvicorn.run(main,
             host="0.0.0.0",
             port=8004,