diff --git a/src/appmain.js b/src/appmain.ts similarity index 90% rename from src/appmain.js rename to src/appmain.ts index b978e43..368934b 100644 --- a/src/appmain.js +++ b/src/appmain.ts @@ -1,4 +1,6 @@ import { LitElement, html, css } from "lit"; +import { property, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import { wrapCss, rwpLogo, @@ -14,24 +16,60 @@ import { SWManager } from "./swmanager"; // =========================================================================== class ReplayWebApp extends LitElement { + @property({ type: Boolean }) + inited = false; + + @property({ type: Object }) + pageParams: URLSearchParams = new URLSearchParams(); + + @property({ type: String }) + sourceUrl: string | null = null; + + @property({ type: Boolean }) + navMenuShown = false; + + @property({ type: Boolean }) + showAbout = false; + + @property({ type: Boolean }) + showFileDropOverlay = false; + + @property({ type: String }) + collTitle: string | null = null; + + @property({ type: Object }) + loadInfo: any = null; + + @property({ type: String }) + embed: any = null; + + @property({ type: String }) + collPageUrl = ""; + + @property({ type: String }) + pageTitle = ""; + + @property({ type: Boolean }) + pageReplay = false; + + @property({ type: String }) + source: any = null; + + @property({ type: Boolean }) + skipRuffle = false; + + @property({ type: Object }) + swErrorMsg: any = null; + + private swName?: string; + private swmanager: SWManager | null; + private useRuffle = false; + + private droppedFile: File | null = null; + // eslint-disable-next-line no-undef constructor(swName = __SW_NAME__) { super(); - this.sourceUrl = null; - this.collTitle = null; - this.showAbout = false; - this.showFileDropOverlay = false; - - this.pageParams = new URLSearchParams(); - - this.inited = false; - this.navMenuShown = false; - - this.collPageUrl = ""; - this.pageTitle = ""; - this.pageReplay = false; - - this.loadInfo = null; this.swName = swName; this.swmanager = null; @@ -58,21 +96,21 @@ class ReplayWebApp extends LitElement { }); this.addEventListener("drop", (dragEvent) => { - this.droppedFile = dragEvent.dataTransfer.files[0]; + this.droppedFile = dragEvent.dataTransfer?.files[0] || null; this.showFileDropOverlay = false; dragEvent.preventDefault(); }); - - this.maybeStartFileDrop = (dragEvent) => { - if (this.sourceUrl) { - // A source is already loaded. Don't allow dropping a file. - return; - } - this.showFileDropOverlay = true; - dragEvent.preventDefault(); - }; } + private maybeStartFileDrop = (dragEvent) => { + if (this.sourceUrl) { + // A source is already loaded. Don't allow dropping a file. + return; + } + this.showFileDropOverlay = true; + dragEvent.preventDefault(); + }; + get appName() { return "ReplayWeb.page"; } @@ -81,27 +119,6 @@ class ReplayWebApp extends LitElement { return window.location.pathname; } - static get properties() { - return { - inited: { type: Boolean }, - pageParams: { type: Object }, - sourceUrl: { type: String }, - navMenuShown: { type: Boolean }, - showAbout: { type: Boolean }, - showFileDropOverlay: { type: Boolean }, - collTitle: { type: String }, - loadInfo: { type: Object }, - embed: { type: String }, - collPageUrl: { type: String }, - pageTitle: { type: String }, - pageReplay: { type: Boolean }, - source: { type: String }, - skipRuffle: { type: Boolean }, - - swErrorMsg: { type: Object }, - }; - } - static get styles() { return wrapCss(ReplayWebApp.appStyles); } @@ -364,10 +381,10 @@ class ReplayWebApp extends LitElement { renderColl() { return html` - (this.swErrorMsg = this.swmanager.renderErrorReport(this.mainLogo)), + (this.swErrorMsg = this.swmanager?.renderErrorReport(this.mainLogo)), ); window.addEventListener("popstate", () => { @@ -451,7 +468,7 @@ class ReplayWebApp extends LitElement { } onFavIcons(event) { - const head = document.querySelector("head"); + const head = document.querySelector("head")!; const oldLinks = document.querySelectorAll("link[rel*='icon']"); for (const link of oldLinks) { @@ -470,7 +487,7 @@ class ReplayWebApp extends LitElement { // This is a workaround, since this app's routing doesn't permit normal // following of in-page anchors. event.preventDefault(); - this.renderRoot.querySelector("#skip-main-target").focus(); + (this.renderRoot.querySelector("#skip-main-target") as HTMLElement).focus(); } onNavMenu(event) { @@ -487,7 +504,9 @@ class ReplayWebApp extends LitElement { (event) => { event.preventDefault(); this.navMenuShown = false; - this.renderRoot.querySelector("#menu-button").focus(); + ( + this.renderRoot.querySelector("#menu-button") as HTMLElement + ).focus(); }, { once: true }, ); @@ -497,7 +516,9 @@ class ReplayWebApp extends LitElement { if (event.key == "Escape") { event.preventDefault(); this.navMenuShown = false; - this.renderRoot.querySelector("#menu-button").focus(); + ( + this.renderRoot.querySelector("#menu-button") as HTMLElement + ).focus(); } }, { once: true }, @@ -510,7 +531,7 @@ class ReplayWebApp extends LitElement { this.pageParams = new URLSearchParams(window.location.search); // Google Drive - let state = this.pageParams.get("state"); + let state: any = this.pageParams.get("state"); if (state) { try { state = JSON.parse(state); @@ -557,7 +578,9 @@ class ReplayWebApp extends LitElement { if (this.pageParams.get("config")) { try { - this.loadInfo.extraConfig = JSON.parse(this.pageParams.get("config")); + this.loadInfo.extraConfig = JSON.parse( + this.pageParams.get("config") || "", + ); } catch (e) { console.log("invalid config: " + e); } @@ -596,7 +619,7 @@ class ReplayWebApp extends LitElement { } if (this.pageParams.get("swName")) { - this.swName = this.pageParams.get("swName"); + this.swName = this.pageParams.get("swName") || undefined; } if (IS_APP && this.sourceUrl.startsWith("file://")) { diff --git a/src/coll-index.js b/src/coll-index.ts similarity index 94% rename from src/coll-index.js rename to src/coll-index.ts index 30b715b..ee0b665 100644 --- a/src/coll-index.js +++ b/src/coll-index.ts @@ -1,4 +1,5 @@ import { LitElement, html, css } from "lit"; +import { property, state } from "lit/decorators.js"; import { wrapCss, apiPrefix } from "./misc"; import { map } from "lit/directives/map.js"; @@ -11,26 +12,41 @@ import fasArrowDown from "@fortawesome/fontawesome-free/svgs/solid/angle-double- import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg"; +import type { Coll } from "./types"; + // =========================================================================== class CollIndex extends LitElement { + @property({ type: Array }) + colls: Coll[] = []; + + @property({ type: String }) + query = ""; + + @property({ type: Array }) + filteredColls: any[] = []; + + @property({ type: Array }) + sortedColls: any[] | null = null; + + @property({ type: Boolean }) + hideHeader: any = null; + + @property({ type: String }) + dateName = "Date Loaded"; + + @property({ type: String }) + headerName = "Loaded Archives"; + + @state() + private _deleting: any = {}; + + private typeFilter = ""; + private indexParams = ""; + constructor() { super(); - this.colls = []; - this.filteredColls = []; - this.sortedColls = null; - - this.query = ""; - this.hideHeader = localStorage.getItem("index:hideHeader") === "1"; - - this._deleting = {}; - - this.dateName = "Date Loaded"; - this.headerName = "Loaded Archives"; - - this.typeFilter = ""; - this.indexParams = ""; } get sortKeys() { @@ -45,25 +61,6 @@ class CollIndex extends LitElement { ]; } - static get properties() { - return { - colls: { type: Array }, - - query: { type: String }, - - filteredColls: { type: Array }, - - sortedColls: { type: Array }, - - hideHeader: { type: Boolean }, - - _deleting: { type: Object }, - - dateName: { type: String }, - headerName: { type: String }, - }; - } - firstUpdated() { this.loadColls(); } @@ -363,19 +360,14 @@ class CollIndex extends LitElement { // =========================================================================== class CollInfo extends LitElement { - constructor() { - super(); - this.detailed = false; - this.canDelete = false; - } + @property({ type: Object }) + coll!: Coll | Record; - static get properties() { - return { - coll: { type: Object }, - detailed: { type: Boolean }, - canDelete: { type: Boolean }, - }; - } + @property({ type: Boolean }) + detailed = false; + + @property({ type: Boolean }) + canDelete = false; static get styles() { return wrapCss(CollInfo.compStyles); @@ -526,7 +518,7 @@ class CollInfo extends LitElement {
    ${map( coll.resources, - (resource) => + (resource: any) => html`
  1. ${resource.name + "\n"}
  2. `, diff --git a/src/coll.ts b/src/coll.ts index c1ccb4e..87d2974 100644 --- a/src/coll.ts +++ b/src/coll.ts @@ -1,6 +1,5 @@ import { LitElement, html, css } from "lit"; import { property, state } from "lit/decorators.js"; -import { map } from "lit/directives/map.js"; import type { SlDropdown, SlMenu, @@ -47,6 +46,8 @@ import fasCaretDown from "@fortawesome/fontawesome-free/svgs/solid/caret-down.sv import { RWPEmbedReceipt } from "./embed-receipt.js"; import Split from "split.js"; + +import type { Coll as CollInfo } from "./types"; import type { Replay } from "./replay"; const RWP_SCHEME = "search://"; @@ -66,7 +67,7 @@ class Coll extends LitElement { showSidebar: boolean | null = null; @property({ type: Object, attribute: false }) - collInfo: any = null; + collInfo: CollInfo | Record | null = null; @property({ type: String }) coll = ""; @@ -96,13 +97,13 @@ class Coll extends LitElement { ts = ""; @property({ type: Boolean }) - isFullscreen: any; + isFullscreen: boolean | null = null; @property({ type: Boolean }) menuActive = false; @property({ type: String }) - embed: any; + embed: string | null = null; @property({ type: Boolean }) embedDropdownActive = false; @@ -135,24 +136,30 @@ class Coll extends LitElement { autoUpdateInterval = 10; @property({ type: String }) - swName: any; + swName: any = null; - private tabNames = ["pages", "story", "resources", "info"]; - private tabLabels = { + private splitter: any = null; + + private _replaceLoc = false; + private _locUpdateNeeded = false; + + private _locationHash = ""; + + private _autoUpdater: null | Promise = null; + + private observer?: IntersectionObserver; + + private readonly tabNames = ["pages", "story", "resources", "info"]; + private readonly tabLabels = { pages: "Pages", story: "Story", resources: "URLs", info: "Archive Info", }; - private splitter: any = null; - private _replaceLoc = false; - private _locUpdateNeeded = false; - private _locationHash = ""; - private _autoUpdater: Promise | null = null; - private observer?: IntersectionObserver; constructor() { super(); + this.showSidebar = localStorage.getItem("pages:showSidebar") === "1"; } @@ -301,7 +308,9 @@ class Coll extends LitElement { configureSplitter() { if (this.tabData.url && this.showSidebar) { - const contents = this.renderRoot.querySelector("#contents"); + const contents = this.renderRoot.querySelector( + "#contents", + ) as HTMLElement; const replay = this.renderRoot.querySelector("wr-coll-replay") as Replay; if (contents && replay && !this.splitter) { @@ -379,15 +388,17 @@ class Coll extends LitElement { }); } - if (!this.collInfo.title) { - this.collInfo.title = this.collInfo.filename; + if (!this.collInfo!.title) { + this.collInfo!.title = this.collInfo!.filename; } if (this.embed === "replayonly" || this.embed === "replay-with-info") { this.showSidebar = false; } - this.hasStory = this.collInfo.desc || this.collInfo.lists.length; + this.hasStory = Boolean( + this.collInfo!.desc || this.collInfo!.lists?.length, + ); this.dispatchEvent( new CustomEvent("coll-loaded", { @@ -435,12 +446,12 @@ class Coll extends LitElement { } if ( - this.collInfo.coll && + this.collInfo?.coll && (!this.tabData.view || !this.tabNames.includes(this.tabData.view)) ) { const view = this.hasStory ? "story" - : this.editable || this.collInfo.pages.length + : this.editable || this.collInfo.pages?.length ? "pages" : "resources"; @@ -452,7 +463,11 @@ class Coll extends LitElement { if (this.tabData.url && this.tabData.url.startsWith("page:")) { const pageIndex = Number(this.tabData.url.slice("page:".length)); - if (!isNaN(pageIndex) && pageIndex < this.collInfo.pages.length) { + if ( + this.collInfo?.pages && + !isNaN(pageIndex) && + pageIndex < this.collInfo.pages.length + ) { const page = this.collInfo.pages[pageIndex]; this.tabData.url = page.url; this.tabData.ts = getPageDateTS(page).timestamp; @@ -852,7 +867,7 @@ class Coll extends LitElement { if (this.collInfo && !this.collInfo.coll) { return html` | null = null; - this.showAuth = false; - this.reauthWait = null; - this.authFileHandle = null; - } + @property({ type: String }) + sourceUrl: any = null; - static get properties() { - return { - collInfo: { type: Object }, - sourceUrl: { type: String }, + // external url set from parent + @property({ type: String }) + url = ""; - // external url set from parent - url: { type: String }, - ts: { type: String }, + @property({ type: String }) + ts = ""; - // actual replay url - replayUrl: { type: String }, - replayTS: { type: String }, - actualTS: { type: String }, - title: { type: String }, + // actual replay url + @property({ type: String }) + replayUrl = ""; - iframeUrl: { type: String }, + @property({ type: String }) + replayTS = ""; - showAuth: { type: Boolean }, - authFileHandle: { type: Object }, - }; - } + @property({ type: String }) + actualTS = ""; + + @property({ type: String }) + title = ""; + + @property({ type: String }) + iframeUrl: any = null; + + @property({ type: Boolean }) + showAuth = false; + + @property({ type: Object }) + authFileHandle: any = null; + + private reauthWait: null | Promise = null; + + private _loadPoll: null | number = null; firstUpdated() { window.addEventListener("message", (event) => this.onReplayMessage(event)); @@ -81,9 +86,10 @@ class Replay extends LitElement { } doSetIframeUrl() { - this.iframeUrl = this.url - ? `${this.collInfo.replayPrefix}/${this.ts || ""}mp_/${this.url}` - : ""; + this.iframeUrl = + this.url && this.collInfo + ? `${this.collInfo.replayPrefix}/${this.ts || ""}mp_/${this.url}` + : ""; } updated(changedProperties) { @@ -199,7 +205,7 @@ class Replay extends LitElement { // google drive reauth const headers = event.detail.headers; - await fetch(`${this.collInfo.apiPrefix}/updateAuth`, { + await fetch(`${this.collInfo!.apiPrefix}/updateAuth`, { method: "POST", body: JSON.stringify({ headers }), }); @@ -231,7 +237,7 @@ class Replay extends LitElement { !iframe.contentDocument || !iframe.contentWindow || (iframe.contentDocument.readyState === "complete" && - !iframe.contentWindow._WBWombat) + !(iframe.contentWindow as Window & { _WBWombat: any })._WBWombat) ) { this.clearLoading(iframe && iframe.contentWindow); } @@ -273,7 +279,7 @@ class Replay extends LitElement { this.doSetIframeUrl(); if (oldIframeUrl === this.iframeUrl || this.url === this.replayUrl) { this.waitForLoad(); - iframe.contentWindow.location.reload(); + iframe.contentWindow?.location.reload(); } } @@ -394,7 +400,7 @@ class Replay extends LitElement { ` : html` `} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..3ea8f16 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,17 @@ +export interface Coll { + filename: string; + sourceUrl: string; + replayPrefix: string; + apiPrefix: string; + coll?: string; + name?: string; + title?: string; + loadUrl?: string; + desc?: string; + description?: string; + resources?: { path: string; name: string }[]; + verify?: any; + onDemand?: any; + pages?: any[]; + lists?: any[]; +}