view src/ingest/IngestDrop.ts @ 49:1bd17c2e5517 default tip

video.py must sign video urls for serve-files.js to serve them
author drewp@bigasterisk.com
date Fri, 06 Dec 2024 17:13:51 -0800
parents 1a9a8af1aa19
children
line wrap: on
line source

import { LitElement, html, css, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";

interface SubTree {
  name: string;
  children?: SubTree[];
}

@customElement("ingest-drop")
export class IngestDrop extends LitElement {
  @property() folder: string = "/";
  @property() tree: SubTree = { name: "TOP" };
  static styles = [
    css`
      #drop {
        min-width: 10em;
        min-height: 10em;
        background: gray;
        border: 5px gray dashed;
        margin: 1% 10%;
      }
      #drop.dnd-hover {
        background: yellow;
      }
      .twocol {
        display: flex;
      }
      .twocol > div {
        background: #262626;
      }
    `,
  ];

  render() {
    const self = this;
    function renderTree(node: SubTree, pathToHere?: string): TemplateResult {
      const isTop = pathToHere === undefined;
      const toplessPath = isTop ? "" : pathToHere + "/" + node.name;
      const internalPath = toplessPath || "/";
      return html`<sl-tree-item expanded ?selected=${isTop} data-path="${internalPath}" @click=${self.setFolder}>
        ${node.name} ${(node.children || []).map((n: SubTree) => renderTree(n, toplessPath))}
      </sl-tree-item>`;
    }

    return html`
      <div class="twocol">
        <div>
          <h3>Select folder</h3>
          <sl-tree>${renderTree(this.tree)}</sl-tree>
        </div>
        <div>
          <div id="drop" @dragover=${this.dragover} @dragleave=${this.leave} @drop=${this.drop}>
            Drop video file or url here to upload to <em>TOP${this.folder}</em>
          </div>
        </div>
      </div>
    `;
  }

  connectedCallback(): void {
    super.connectedCallback();
    this.loadFolderTree();
  }

  async loadFolderTree() {
    this.tree = await (await fetch("../api/folderTree")).json();
  }

  setFolder(ev: MouseEvent) {
    for (let el of ev.composedPath()) {
      const ds = (el as HTMLElement).dataset;
      if (ds) {
        if (ds.path) {
          this.folder = ds.path;
          return;
        }
      }
    }
    console.error("didn't find data-path");
  }

  dragover(ev: DragEvent) {
    this.shadowRoot?.querySelector("#drop")?.classList.add("dnd-hover");
    ev.preventDefault();
    if (ev.dataTransfer) {
      ev.dataTransfer.dropEffect = "copy";
    }
  }

  leave() {
    this.shadowRoot?.querySelector("#drop")?.classList.remove("dnd-hover");
  }

  drop(ev: DragEvent) {
    ev.preventDefault();
    this.leave();

    if (!ev.dataTransfer) {
      return;
    }

    const url = ev.dataTransfer.getData("text/plain");
    if (url) {
      this.queueUrlToFetch(url);
    } else {
      for (let i = 0; i < ev.dataTransfer.files.length; i++) {
        this.uploadVideoFile(ev.dataTransfer.files[i]);
      }
    }
  }
  queueUrlToFetch(url: string) {
    fetch("../api/ingest/videoUrl?folder=" + encodeURIComponent(this.folder), {
      method: "POST",
      body: url,
    });
  }
  uploadVideoFile(f: File) {
    fetch("../api/ingest/videoUpload?name=" + encodeURIComponent(this.folder + "/" + f.name), {
      method: "POST",
      body: f.stream(),
      duplex: "half",
    } as any /* types don't include 'duplex' */ );
  }
}