import { LitElement, html, css } from "lit"; import { wrapCss, apiPrefix } from "./misc"; import prettyBytes from "pretty-bytes"; import fasCopy from "@fortawesome/fontawesome-free/svgs/regular/copy.svg"; import fasArrowUp from "@fortawesome/fontawesome-free/svgs/solid/angle-double-up.svg"; import fasArrowDown from "@fortawesome/fontawesome-free/svgs/solid/angle-double-down.svg"; import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg"; // =========================================================================== class CollIndex extends LitElement { 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() { return [ {key: "title", name: "Title"}, {key: "sourceUrl", name: "Source"}, {key: "ctime", name: this.dateName}, {key: "size", name: "Total Size"} ]; } 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(); } updated(changedProperties) { if (changedProperties.has("hideHeader")) { localStorage.setItem("index:hideHeader", this.hideHeader ? "1" : "0"); } if (changedProperties.has("colls") || changedProperties.has("query")) { this.filter(); } } filter() { if (!this.query) { this.filteredColls = this.colls; return; } this.filteredColls = []; for (const coll of this.colls) { if (coll.sourceUrl.indexOf(this.query) >= 0 || coll.filename.indexOf(this.query) >= 0 || (coll.loadUrl && coll.loadUrl.indexOf(this.query) >= 0) || (coll.title && coll.title.indexOf(this.query) >= 0)) { this.filteredColls.push(coll); } } } async loadColls() { const resp = await fetch(`${apiPrefix}/coll-index?${this.indexParams}`); try { if (resp.status !== 200) { throw new Error("Invalid API Response, Retry"); } const json = await resp.json(); this.colls = json.colls.map((coll) => { coll.title = coll.title || coll.filename; return coll; }); this._deleting = {}; this.sortedColls = []; } catch (e) { // likely no sw registered yet, or waiting for new sw to register, retry again setTimeout(() => this.loadColls(), 500); } } async onDeleteColl(event) { event.preventDefault(); event.stopPropagation(); if (!this.sortedColls) { return; } const index = Number(event.currentTarget.getAttribute("data-coll-index")); const coll = this.sortedColls[index]; if (!coll || this._deleting[coll.sourceUrl]) { return; } this._deleting[coll.sourceUrl] = true; this.requestUpdate(); const resp = await fetch(`${apiPrefix}/c/${coll.id}`, {method: "DELETE"}); if (resp.status === 200) { const json = await resp.json(); this.colls = json.colls; } return false; } static get styles() { return wrapCss(CollIndex.compStyles); } static get compStyles() { return css` :host { overflow-y: auto; min-width: 0; } .size { margin-right: 20px; } .extra-padding { padding: 2em; } .no-top-padding { padding-top: 1.0em; } .panel-heading { font-size: 0.85rem; } .is-loading { line-height: 1.5em; height: 1.5em; border: 0px; background-color: transparent !important; width: auto; } div.panel.is-light { margin-bottom: 2em; } fa-icon { vertical-align: middle; } .panel-color { background-color: rgb(210, 249, 214); } .copy { color: black; margin: 0px; margin: 0; line-height: 0.4em; padding: 6px; border-radius: 10px; display: none; position: absolute; } .copy:active { background-color: lightgray; } .sort-header { padding: 0.3rem 0.3rem 0.3rem 0; display: flex; flex-direction: row; flex-flow: row wrap; } .sort-header .control { flex: auto; padding-left: 0.3rem; width: initial; } wr-sorter { padding: 0.3rem; } a.button.is-small.collapse { border-radius: 6px; } .icon.is-left { margin-left: 0.5rem; } .coll-block { position: relative; } .delete-button { width: 32px; position: absolute; top: 10px; right: 10px; } #sort-select::after { display: none; } header { transform: translate(0px, 0px); transition: all 0.5s ease 0s; visibility: visible; display: flex; flex-direction: column; } header.closed { transform: translate(0, -100%); transition: all 0.5s ease 0s; visibility: visible; height: 269px; margin-top: -269px; } `; } renderHeader() { return html`

${this.headerName}

`; } renderSearchHeader() { return ""; } render() { const hasHeader = this.childElementCount > 0; return html`
${hasHeader ? html` ` : ""}
${this.renderHeader()} ${this.colls.length ? html`
${this.renderSearchHeader()}
${this.sortedColls && this.sortedColls.map((coll, i) => html`
${this.renderCollInfo(coll)} ${!this._deleting[coll.sourceUrl] ? html` ` : html` Deleting `)}
` : html`
${this.sortedColls === null ? html`Loading Archives...` : this.renderEmpty()}
`}
`; } renderCollInfo(coll) { return html``; } renderEmpty() { return html`No Archives so far! Archives loaded in the section above will appear here.`; } } // =========================================================================== class CollInfo extends LitElement { constructor() { super(); this.detailed = false; this.canDelete = false; } static get properties() { return { coll: { type: Object }, detailed: { type: Boolean }, canDelete: { type: Boolean } }; } static get styles() { return wrapCss(CollInfo.compStyles); } static get compStyles() { return css` .columns { width: 100%; } .column { word-break: break-word; position: relative; } :host { width: 100%; height: 100%; min-width: 0px; } :host(.is-list) .columns { display: flex !important; flex-direction: column; } :host(.is-list) .column { width: 100% !important; } .col-title:hover { } .col-title a { display: block; height: 100%; } .column:hover > .copy, .source-text:hover + .copy, .copy:hover { display: inline; } .copy { color: black; margin: 0px; margin: 0; line-height: 0.4em; padding: 6px; border-radius: 10px; display: none; position: absolute; } .copy:active { background-color: lightgray; } .minihead { font-size: 10px; font-weight: bold; } `; } renderSource(coll) { return html`

Source

${coll.sourceUrl && (coll.sourceUrl.startsWith("http://") || coll.sourceUrl.startsWith("https://")) ? html` ${coll.sourceUrl} ` : html` ${coll.sourceUrl} `}
${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://") ? html`

(${coll.filename})

` : ""}

Date Loaded

${coll.ctime ? new Date(coll.ctime).toLocaleString() : ""}

Total Size

${prettyBytes(Number(coll.size || 0))}
`; } renderSummaryView() { const coll = this.coll; return html`
${coll.title || coll.filename}
${this.renderSource(coll)}
`; } renderDetailed() { const coll = this.coll; let {numValid, numInvalid, domain, certFingerprint, datapackageHash, software} = this.coll.verify || {}; numValid = numValid || 0; numInvalid = numInvalid || 0; const certFingerprintUrl = certFingerprint ? `https://crt.sh/?q=${certFingerprint}` : ""; return html`
${coll.title || coll.filename}
${coll.desc ? html`

Description

${coll.desc}
` : html` `}

Filename

${coll.filename}
${this.renderSource(coll)} ${domain ? html`

Observed By

${domain}

${certFingerprintUrl ? html`View Certificate` : ""}
` : software ? html`

Created With

${software || "Unknown"}
` : ""}

Validation

${numValid > 0 || numInvalid > 0 ? html`

${numValid} hashes verified${numInvalid ? html`, ${numInvalid} invalid` : ""}

` : html` Not Available`}

Package Hash

${datapackageHash || "Not Available"}

Loading Mode

${coll.onDemand ? "Download On-Demand" : "Fully Local"}

Collection id

${coll.coll}
`; } render() { return this.detailed ? this.renderDetailed() : this.renderSummaryView(); } onCopy(event, sourceUrl) { event.preventDefault(); event.stopPropagation(); navigator.clipboard.writeText(sourceUrl); return false; } onPurge(reload) { const detail = {reload}; this.dispatchEvent(new CustomEvent("coll-purge", {detail})); } } customElements.define("wr-coll-info", CollInfo); customElements.define("wr-coll-index", CollIndex); export { CollIndex, CollInfo };