IPFS Support + Loading Improvements (Better local file handling) + Streamlined Collection View (#25)

* support ipfs:// urls!
misc: better error message for loading from http://
tabs: don't render pages/story/urls if hidden

* ipfs work: remove ww only use sw

* electron + ipfs: add ipfs support to electron app, using ipfs-core
- route ipfs requests to files proxy with 'ipfs=' query param

* file loading:
- add support for new Chrome native file system api with file handles, which allows persistent access to files vial file handles
- if window.showOpenFilePicker() is available use that, include file handle in load message
- support asking permissions for file handles, using similar workflow as google drive auth
- attempt to ask for file permission directly, show auth button if fails (user gestured required)
- replace auth modal with iframe replacement insert to ask for auth for google drive and local file

* ui overhaul for 1.3.0 / condense collection view:
- don't show navbar in collection view
- add logo to back to home page to location bar
- condense location bar buttons
- convert terms to general about dialog, available from collection context menu
- add info tab to collection view in place of drop-down info
- make <wr-coll-info> component, used in both collection list and info tab with additional details
- update to wabac.js 2.2.0

* update CHANGES.md
This commit is contained in:
Ilya Kreymer
2020-11-16 18:31:15 -08:00
committed by GitHub
parent e839400017
commit 228e9bdff6
15 changed files with 4533 additions and 614 deletions
+108 -31
View File
@@ -8,6 +8,7 @@ import fasBook from '@fortawesome/fontawesome-free/svgs/solid/book.svg';
import farListAlt from '@fortawesome/fontawesome-free/svgs/regular/list-alt.svg';
import farResources from '@fortawesome/fontawesome-free/svgs/solid/puzzle-piece.svg';
import farPages from '@fortawesome/fontawesome-free/svgs/regular/file-image.svg';
import fasInfoIcon from '@fortawesome/fontawesome-free/svgs/solid/info-circle.svg';
import fasRefresh from '@fortawesome/fontawesome-free/svgs/solid/redo-alt.svg';
import fasFullscreen from '@fortawesome/fontawesome-free/svgs/solid/desktop.svg';
@@ -20,6 +21,8 @@ import fasMenuV from '@fortawesome/fontawesome-free/svgs/solid/ellipsis-v.svg';
import fasAngleLeft from '@fortawesome/fontawesome-free/svgs/solid/angle-left.svg';
import fasAngleRight from '@fortawesome/fontawesome-free/svgs/solid/angle-right.svg';
import prettyBytes from 'pretty-bytes';
import Split from 'split.js';
@@ -53,7 +56,7 @@ class Coll extends LitElement
this.url = "";
this.ts = "";
this.tabNames = ["pages", "story", "resources"];
this.tabNames = ["pages", "story", "resources", "info"];
this.menuActive = false;
@@ -97,7 +100,7 @@ class Coll extends LitElement
isVisible: { type: Boolean },
favIconUrl: {type: String }
favIconUrl: {type: String },
}
}
@@ -425,7 +428,7 @@ class Coll extends LitElement
static get replayBarStyles() {
return css`
.replay-bar {
padding: 1em 0em 1em 1em;
padding: 0.5em 0em 0.5em 0.5em;
max-width: none;
border-bottom: solid .1rem #97989A;
width: 100%;
@@ -439,7 +442,8 @@ class Coll extends LitElement
.favicon img {
width: 20px;
height: 20px;
filter: drop-shadow(1px 1px 2px grey);
margin: 8px;
/*filter: drop-shadow(1px 1px 2px grey);*/
}
#datetime {
@@ -484,12 +488,13 @@ class Coll extends LitElement
border: 0px;
}
.modal {
top: 174px;
.narrow {
padding: calc(0.5em - 1px) 0.8em;
}
form {
width: 100%;
margin-left: 0.5em;
}
.gutter.gutter-horizontal {
@@ -506,6 +511,18 @@ class Coll extends LitElement
width: 100%;
}
.info-bg {
background-color: whitesmoke;
width: 100%;
height: 100%;
display: flex;
}
.is-list {
margin: 1.0em;
background-color: whitesmoke;
}
#contents.full-pages {
width: 100% !important;
}
@@ -581,7 +598,7 @@ class Coll extends LitElement
<div id="contents" class="is-light ${isSidebar ? 'sidebar' : (isReplay ? 'is-hidden' : 'full-pages')}"
role="${isSidebar ? "complementary" : ""}" aria-label="${isSidebar ? "Browse Contents" : ""}">
${this.renderTabHeader(isSidebar)}
${this.renderCollTabs(isSidebar)}
${isSidebar || !isReplay ? this.renderCollTabs(isSidebar) : html``}
</div>
${isReplay && this.isVisible ? html`
@@ -644,6 +661,13 @@ class Coll extends LitElement
</a>
</li>
<li class="${this.tabData.view === 'info' ? 'is-active' : ''}">
<a @click="${this.onTabClick}" href="#info" class="is-size-6" aria-label="Archive Info" aria-current="${this.tabData.view === 'info' ? 'location' : ''}">
<span class="icon"><fa-icon .svg="${fasInfoIcon}" aria-hidden="true" title="Archive Info"></fa-icon></span>
<span class="tab-label ${isSidebar ? 'is-hidden' : ''}" title="Archive Info">Info</span>
</a>
</li>
${isSidebar ? html`
<li class="sidebar-nav right">
<a role="button" href="#" @click="${this.onFullPageView}" @keyup="${clickOnSpacebarPress}" class="is-marginless is-size-6 is-paddingless">
@@ -671,25 +695,34 @@ class Coll extends LitElement
<a class="skip-link" href="#skip-replay-target" @click="${this.skipMenu}">Skip replay navigation</a>
<nav class="replay-bar" aria-label="replay">
<div class="field has-addons">
<a href="#" role="button" id="fullscreen" class="button is-borderless is-hidden-touch" @click="${this.onFullscreenToggle}" @keyup="${clickOnSpacebarPress}"
${!this.embed ? html`
<a href="/" role="button" class="button is-borderless is-hidden-mobile" aria-labelledby="home" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small">
<fa-icon id="wrlogo" size="1.5rem" .svg=${rwpLogo} aria-hidden="true"></fa-icon>
<span class="is-sr-only">Home</span>
</span>
</a>` : ``}
<a href="#" role="button" class="button narrow is-borderless is-hidden-touch" id="fullscreen" @click="${this.onFullscreenToggle}" @keyup="${clickOnSpacebarPress}"
title="${this.isFullscreen ? "Exit Full Screen" : "Full Screen"}" aria-label="${this.isFullscreen ? "Exit Fullscreen" : "Fullscreen"}">
<span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${this.isFullscreen ? fasUnfullscreen : fasFullscreen}"></fa-icon>
</span>
</a>
<a href="#" role="button" class="button is-borderless is-hidden-mobile" @click="${this.onGoBack}" @keyup="${clickOnSpacebarPress}"
<a href="#" role="button" class="button narrow is-borderless is-hidden-mobile" @click="${this.onGoBack}" @keyup="${clickOnSpacebarPress}"
title="Back" aria-label="Back">
<span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasLeft}"></fa-icon>
</span>
</a>
<a href="#" role="button" class="button is-borderless is-hidden-mobile" @click="${this.onGoForward}" @keyup="${clickOnSpacebarPress}"
<a href="#" role="button" class="button narrow is-borderless is-hidden-mobile" @click="${this.onGoForward}" @keyup="${clickOnSpacebarPress}"
title="Forward" aria-label="Forward">
<span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasRight}"></fa-icon>
</span>
</a>
<a href="#" role="button" id="refresh" class="button is-borderless ${this.isLoading ? 'is-loading' : 'is-hidden-mobile'}" @click="${this.onRefresh}" @keyup="${clickOnSpacebarPress}"
<a href="#" role="button" class="button narrow is-borderless ${this.isLoading ? 'is-loading' : 'is-hidden-mobile'}" id="refresh" @click="${this.onRefresh}" @keyup="${clickOnSpacebarPress}"
title="Reload" aria-label="Reload">
<span class="icon is-small">
${!this.isLoading ? html`
@@ -697,7 +730,7 @@ class Coll extends LitElement
` : ``}
</span>
</a>
<a href="#" role="button" class="button is-borderless is-hidden-touch ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}"
<a href="#" role="button" class="button narrow is-borderless is-hidden-touch ${!isReplay ? 'grey-disabled' : ''}" @click="${this.onShowPages}" @keyup="${clickOnSpacebarPress}"
?disabled="${!isReplay}" title="Browse Contents" aria-label="Browse Contents" aria-controls="contents">
<span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${farListAlt}"></fa-icon>
@@ -725,6 +758,15 @@ class Coll extends LitElement
</div>
<div class="dropdown-menu" id="menu-dropdown">
<div class="dropdown-content">
${!this.embed ? html`
<a href="/" role="button" class="dropdown-item is-hidden-tablet" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${rwpLogo}"></fa-icon>
</span>
<span>Home</span>
</a>
<hr class="dropdown-divider is-hidden-tablet">
` : ``}
<a href="#" role="button" class="dropdown-item is-hidden-desktop" @click="${this.onFullscreenToggle}" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${this.isFullscreen ? fasUnfullscreen : fasFullscreen}"></fa-icon>
@@ -764,14 +806,11 @@ class Coll extends LitElement
<div class="dropdown-item info is-hidden-desktop">
<span class="menu-head">Capture Date</span>${dateStr}
</div>` : ``}
${this.embed ? html`
<hr class="dropdown-divider">
<a href="https://replayweb.page/" target="_blank" class="dropdown-item info">
<fa-icon class="menu-logo" size="1.0rem" aria-hidden="true" .svg=${rwpLogo}></fa-icon>
<a href="#" role="button" class="dropdown-item" @click="${this.onAbout}">
<span>&nbsp;About ReplayWeb.page</span>
<span class="menu-version">(${__VERSION__})</span>
</a>
` : ``}
</div>
</div>
</div>
@@ -796,12 +835,26 @@ class Coll extends LitElement
}
renderCollTabs(isSidebar) {
const isStory = this.tabData.view === 'story';
const isStory = this.hasStory && this.tabData.view === 'story';
const isPages = this.tabData.view === 'pages';
const isResources = this.tabData.view === 'resources';
const isInfo = this.tabData.view === 'info';
return html`
${this.hasStory ? html`
${isInfo ? html`
<div class="info-bg">
<wr-coll-info
class="is-list"
.coll="${this.collInfo}"
?detailed="${true}"
?canDelete="${!this.embed}"
@coll-purge="${this.onPurgeCache}"
></wr-coll-info>
</div>
`: html``}
${isStory ? html`
<wr-coll-story .collInfo="${this.collInfo}"
.active="${isStory}"
currList="${this.tabData.currList || 0}"
@@ -812,6 +865,7 @@ class Coll extends LitElement
>
</wr-coll-story>` : ''}
${isResources ? html`
<wr-coll-resources .collInfo="${this.collInfo}"
.active="${isResources}"
query="${this.tabData.query || ""}"
@@ -822,8 +876,9 @@ class Coll extends LitElement
class="is-paddingless ${isResources ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"
role="${isSidebar ? '' : 'main'}"
>
</wr-coll-resources>
</wr-coll-resources>` : ``}
${isPages ? html`
<wr-page-view
.collInfo="${this.collInfo}"
.active="${isPages}"
@@ -838,7 +893,7 @@ class Coll extends LitElement
class="${isPages ? '' : 'is-hidden'} ${isSidebar ? 'sidebar' : ''}"
role="${isSidebar ? '' : 'main'}"
>
</wr-page-view>
</wr-page-view>` : ``}
`;
}
@@ -911,28 +966,46 @@ class Coll extends LitElement
this.showSidebar = false;
}
onFavIcons(event) {
this.favIconUrl = "";
event.detail.icons.map(async (icon) => {
const res = await fetch(icon.href);
if (res.status === 200) {
this.favIconUrl = icon.href;
async onFavIcons(event) {
for (const icon of event.detail.icons) {
const resp = await fetch(icon.href);
if (resp.status === 200) {
const ct = resp.headers.get("Content-Type");
if (ct && !ct.startsWith("text/")) {
this.favIconUrl = icon.href;
return;
}
}
});
}
// not found
this.favIconUrl = "";
}
async onPurgeCache(event) {
onPurgeCache(event) {
event.preventDefault();
const reload = event.detail && event.detail.reload !== undefined ? event.detail.reload : true;
this.deleteFully(reload);
}
async deleteFully(reload = false) {
const resp = await fetch(`${this.collInfo.apiPrefix}`, {
method: 'DELETE',
});
if (resp.status === 200 && window.parent) {
window.parent.location.reload();
} else {
if (resp.status !== 200) {
console.warn("purge failed: " + resp.status);
}
if (!reload && !this.embed) {
window.location.search = "";
return;
}
window.location.hash = "";
window.location.reload();
}
onSubmit(event) {
@@ -1019,6 +1092,10 @@ class Coll extends LitElement
window.location.reload();
}
}
onAbout() {
this.dispatchEvent(new CustomEvent("about-show"));
}
}
customElements.define("wr-coll", Coll);