ui improvements:

- condense navbar, always show with breadcrumbs for collection title and current page
- add update-title event to propagate replay page title for breadcrumb, also updates document.title
- appmain: make app component more extensible, make coll-index also extensible with custom coll info component
- condense index view headers
- bump to 1.3.2
This commit is contained in:
Ilya Kreymer
2020-12-07 22:37:08 -08:00
parent ba89554833
commit 40c2e937dc
13 changed files with 559 additions and 438 deletions
+129 -65
View File
@@ -9,24 +9,35 @@ import fasArrowRight from '@fortawesome/fontawesome-free/svgs/solid/arrow-right.
// ===========================================================================
class App extends LitElement
class ReplayWebApp extends LitElement
{
constructor() {
super();
this.sourceUrl = null;
this.collInfo = null;
this.collTitle = null;
this.showAbout = false;
this.pageParams = {};
this.inited = false;
this.navMenuShown = false;
this.collPageUrl = "";
this.pageTitle = "";
this.pageReplay = false;
registerSW(__SW_NAME__);
this.safariKeyframes();
}
get appName() {
return "ReplayWeb.page";
}
get homeUrl() {
return "/";
}
static get properties() {
return {
inited: { type: Boolean },
@@ -34,19 +45,39 @@ class App extends LitElement
sourceUrl: { type: String },
navMenuShown: { type: Boolean },
showAbout: { type: Boolean },
collInfo: { type: Object },
collTitle: { type: String },
loadInfo: { type: Object },
embed: { type: String },
collPageUrl: { type: String },
pageTitle: { type: String },
pageReplay: { type: Boolean },
}
}
static get styles() {
return wrapCss(css`
return wrapCss(ReplayWebApp.appStyles);
}
static get appStyles() {
return css`
#wrlogo {
max-height: 2.5rem;
max-height: 1.0rem;
}
.navbar {
height: 1.5rem;
}
.navbar-brand {
height: 1.5rem;
display: flex;
align-items: center;
}
.wr-logo-item {
padding-right: 8px;
padding: 0 8px 0 0;
}
.no-wrap {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.has-allcaps {
font-variant-caps: small-caps;
@@ -91,7 +122,7 @@ class App extends LitElement
.logo-text {
padding-left: 0px;
margin-left: 8px;
margin-left: 6px;
}
a.navbar-item.logo-text:hover {
@@ -105,36 +136,41 @@ class App extends LitElement
}
}
`);
`;
}
get mainLogo() {
return rwpLogo;
}
renderNavBrand() {
return html`
<span id="home" class="logo-text has-text-weight-bold is-size-6 has-allcaps wide-only">
<span class="has-text-primary">replay</span>
<span class="has-text-link">web.page</span>
<span class="is-sr-only">Home</span>
</span>`;
}
renderNavBar() {
}
render() {
if (!this.inited) {
return html``;
}
const showNavBar = !this.collInfo && (!this.embed || this.embed === "full");
return html`
${showNavBar ? html`
<a href="#skip-main-target" @click=${this.skipMenu} class="skip-link">Skip main navigation</a>
<nav class="navbar has-background-info" aria-label="main">
<div class="navbar-brand">
${!this.embed ? html `
<a href="/" class="navbar-item wr-logo-item" aria-labelledby="home">
<fa-icon id="wrlogo" size="2.5rem" .svg=${rwpLogo} aria-hidden="true"></fa-icon>
<span id="home" class="logo-text has-text-weight-bold is-size-5 has-allcaps wide-only">
<span class="has-text-primary">replay</span>
<span class="has-text-link">web.page</span>
<span class="is-sr-only">Home</span>
</span>
</a>`: html `
${!this.embed ? html`
<a href="${this.homeUrl}" class="navbar-item wr-logo-item" aria-labelledby="home">
<fa-icon id="wrlogo" size="1.0rem" .svg=${this.mainLogo} aria-hidden="true"></fa-icon>
${this.renderNavBrand()}
</a>
${this.collTitle ? html`
<a href="${this.collPageUrl}" class="no-wrap is-size-6 has-text-black">/&nbsp;&nbsp;<i>${this.collTitle}</i></a>
<span class="no-wrap is-size-6">&nbsp;&nbsp;/&nbsp;
${this.pageReplay ? html`<i>${this.pageTitle}</i>` : this.pageTitle}
</span>
` : ``}
`: html`
<span class="navbar-item wr-logo-item">
<fa-icon id="wrlogo" size="2.5rem" .svg=${rwpLogo} aria-hidden="true"></fa-icon>
<fa-icon id="wrlogo" size="1.0rem" .svg=${this.mainLogo} aria-hidden="true"></fa-icon>
</span>
`}
<a href="#" role="button" id="menu-button" @click="${this.onNavMenu}" @keyup="${clickOnSpacebarPress}"
@@ -144,58 +180,74 @@ class App extends LitElement
<span aria-hidden="true"></span>
</a>
</div>
${!this.sourceUrl ? html`
<div class="navbar-menu ${this.navMenuShown ? 'is-active' : ''}">
<div class="navbar-start">
${!this.embed ? html`
<a id="home" class="navbar-item logo-text has-text-weight-bold is-size-5 has-allcaps menu-only" href="/">
<span class="has-text-primary">replay</span>
<span class="has-text-link">web.page</span>
<span class="is-sr-only">Home</span>
</a>` : ``}
${IS_APP && !this.collInfo ? html`
${IS_APP ? html`
<a role="button" href="#" class="navbar-item arrow-button" title="Go Back" @click="${(e) => window.history.back()}" @keyup="${clickOnSpacebarPress}">
<fa-icon .svg="${fasArrowLeft}" aria-hidden="true"></fa-icon><span class="menu-only">&nbsp;Go Back</span>
<fa-icon size="1.0rem" .svg="${fasArrowLeft}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7">&nbsp;Go Back</span>
</a>
<a role="button" href="#" class="navbar-item arrow-button" title="Go Forward" @click="${(e) => window.history.forward()}" @keyup="${clickOnSpacebarPress}">
<fa-icon .svg="${fasArrowRight}" aria-hidden="true"></fa-icon><span class="menu-only">&nbsp;Go Forward</span>
<fa-icon size="1.0rem" .svg="${fasArrowRight}" aria-hidden="true"></fa-icon><span class="menu-only is-size-7">&nbsp;Go Forward</span>
</a>
` : ``}
</div>
${!this.embed ? html`
<div class="navbar-end">
<a href="/docs" target="_blank" class="navbar-item">
<fa-icon .svg="${fasHelp}" aria-hidden="true"></fa-icon><span>&nbsp;User Docs</span>
</a>
<!--
-- NB: the About modal is currently inaccessible to people using keyboards or screen readers.
-- Should all the JS and infrastructure for accessible modals be added, or should About be a normal page?
-->
<a href="?terms" @click="${(e) => { e.preventDefault(); this.showAbout = true} }"class="navbar-item">About</a>
${this.renderNavEnd()}
</div>` : html``}
</div>
</nav><p id="skip-main-target" tabindex="-1" class="is-sr-only">Skipped</p>
` : ''}
</div>` : html``}
</nav>
<p id="skip-main-target" tabindex="-1" class="is-sr-only">Skipped</p>`;
}
${this.sourceUrl ? html`
renderNavEnd() {
return html`
<a href="/docs" target="_blank" class="navbar-item is-size-6">
<fa-icon .svg="${fasHelp}" aria-hidden="true"></fa-icon><span>&nbsp;User Docs</span>
</a>
<!--
-- NB: the About modal is currently inaccessible to people using keyboards or screen readers.
-- Should all the JS and infrastructure for accessible modals be added, or should About be a normal page?
-->
<a href="?terms" @click="${(e) => { e.preventDefault(); this.showAbout = true} }"class="navbar-item is-size-6">About
</a>`
}
renderColl() {
return html`
<wr-coll .loadInfo="${this.loadInfo}"
sourceUrl="${this.sourceUrl}"
embed="${this.embed}"
appName="${this.appName}"
.appLogo="${this.mainLogo}"
@replay-favicons=${this.onFavIcons}
@update-title=${this.onTitle}
@coll-loaded=${this.onCollLoaded}
@about-show=${(e) => this.showAbout = true}></wr-coll>
` : html`
@about-show=${(e) => this.showAbout = true}></wr-coll>`;
}
<wr-coll-index>
renderHomeIndex() {
return html`
<wr-coll-index>
${!IS_APP ? html`
<p slot="header" class="tagline is-size-5 has-text-centered">Explore and Replay Interactive Archived Webpages Directly in your Browser. <i><a target="_blank" href="./docs/examples">(See Examples)</a></i></p>
` : ``}
<wr-chooser slot="header" @load-start=${this.onStartLoad}></wr-chooser>
</wr-coll-index>
`}
</wr-coll-index>`;
}
${this.showAbout ? this.renderAbout() : ``}
render() {
if (!this.inited) {
return html``;
}
return html`
${!this.embed || this.embed === "full" ? this.renderNavBar() : ''}
${this.sourceUrl ? this.renderColl() : this.renderHomeIndex()}
${this.showAbout ? this.renderAbout() : ``}
`;
}
@@ -208,7 +260,7 @@ class App extends LitElement
updated(changedProperties) {
if (changedProperties.has("sourceUrl")) {
this.collInfo = null;
this.collTitle = null;
}
}
@@ -328,13 +380,16 @@ class App extends LitElement
// just redirect right away?
this.pageParams.set("source", event.detail.sourceUrl);
const url = new URL(window.location.href);
url.search = this.pageParams.toString();
this.collPageUrl = url.toString();
if (!event.detail.isFile) {
window.location.search = this.pageParams.toString();
return;
} else {
const url = new URL(window.location.href);
url.search = this.pageParams.toString();
window.history.pushState({}, "", url.toString());
window.history.pushState({}, "", this.collPageUrl);
}
this.sourceUrl = event.detail.sourceUrl;
@@ -344,7 +399,7 @@ class App extends LitElement
onCollLoaded(event) {
this.loadInfo = null;
if (event.detail.collInfo) {
this.collInfo = event.detail.collInfo;
this.collTitle = event.detail.collInfo.title;
}
if (event.detail.alreadyLoaded) {
@@ -360,6 +415,15 @@ class App extends LitElement
}
}
onTitle(event) {
if (event.detail.title) {
this.pageTitle = event.detail.title;
this.pageReplay = event.detail.replayTitle;
document.title = (event.detail.replayTitle ? "Archive of " : "") + this.pageTitle + " | ReplayWeb.page";
}
}
safariKeyframes() {
// Safari requires keyframes at the document level, add spinner keyframes here
const css = `
@@ -442,6 +506,6 @@ class App extends LitElement
}
}
customElements.define("replay-app-main", App);
customElements.define("replay-app-main", ReplayWebApp);
export { App };
export { ReplayWebApp };
+4 -3
View File
@@ -108,7 +108,7 @@ class Chooser extends LitElement
min-width: 0;
}
.extra-padding {
padding: 2em;
padding: 1.5em;
}
.less-padding {
padding-top: 1.0em;
@@ -119,6 +119,7 @@ class Chooser extends LitElement
}
.panel-heading {
background-color: #cff3ff;
font-size: 0.85rem;
}
form {
flex-grow: 1;
@@ -154,9 +155,9 @@ class Chooser extends LitElement
return html`
<section class="section less-padding">
<div class="panel">
<h2 class="panel-heading">Load Web Archive</h2>
<h4 class="panel-heading">Load Web Archive</h4>
<div class="extra-padding panel-block file has-name">
<form class="content is-flex" @submit="${this.onStartLoad}">
<form class="is-flex" @submit="${this.onStartLoad}">
<label class="file-label">
${!this.hasNativeFS ? html`
<input class="file-input"
+26 -10
View File
@@ -12,7 +12,7 @@ import fasSearch from '@fortawesome/fontawesome-free/svgs/solid/search.svg';
// ===========================================================================
class CollIndex extends LitElement
class WrCollIndex extends LitElement
{
constructor() {
super();
@@ -26,9 +26,12 @@ class CollIndex extends LitElement
this.hideHeader = localStorage.getItem("index:hideHeader") === "1";
this._deleting = {};
this.dateName = "Date Loaded";
this.headerName = "Loaded Archives";
}
static get sortKeys() {
get sortKeys() {
return [
{key: "title",
name: "Title"},
@@ -37,7 +40,7 @@ class CollIndex extends LitElement
name: "Source"},
{key: "ctime",
name: "Date Loaded"},
name: this.dateName},
{key: "size",
name: "Total Size"}
@@ -56,7 +59,10 @@ class CollIndex extends LitElement
hideHeader: { type: Boolean },
_deleting: { type: Object }
_deleting: { type: Object },
dateName: { type: String },
headerName: { type: String }
}
}
@@ -147,6 +153,9 @@ class CollIndex extends LitElement
.no-top-padding {
padding-top: 1.0em;
}
.panel-heading {
font-size: 0.85rem;
}
.is-loading {
line-height: 1.5em;
height: 1.5em;
@@ -224,19 +233,22 @@ class CollIndex extends LitElement
}
render() {
const hasHeader = this.childElementCount > 0;
return html`
<header class="${this.hideHeader ? 'closed' : ''}">
<slot name="header"></slot>
</header>
<section class="section no-top-padding">
<div class="sort-header is-small">
${hasHeader ? html`
<button @click=${(e) => this.hideHeader = !this.hideHeader} class="collapse button is-small">
<span class="icon"><fa-icon .svg=${this.hideHeader ? fasArrowDown : fasArrowUp}></span>
<span>${this.hideHeader ? 'Show ' : 'Hide'} <span class="is-sr-only">Header</span></span>
</button>
</button>` : ``}
</div>
<div class="panel is-light">
<h2 class="panel-heading"><span>Loaded Archives</span>
<h2 class="panel-heading"><span>${this.headerName}</span>
</h2>
${this.colls.length ? html`
@@ -249,7 +261,7 @@ class CollIndex extends LitElement
<wr-sorter id="index"
sortKey="ctime"
?sortDesc="${true}"
.sortKeys="${CollIndex.sortKeys}"
.sortKeys="${this.sortKeys}"
.data="${this.filteredColls}"
@sort-changed="${(e) => this.sortedColls = e.detail.sortedData}">
</wr-sorter>
@@ -258,7 +270,7 @@ class CollIndex extends LitElement
<div class="coll-list">
${this.sortedColls.map((coll, i) => html`
<div class="coll-block panel-block">
<wr-coll-info .coll=${coll}></wr-coll-info>
${this.renderCollInfo(coll)}
${!this._deleting[coll.sourceUrl] ? html`
<button class="delete" aria-label="Unload Collection" title="Unload Collection" data-coll-index="${i}" @click="${this.onDeleteColl}"></button>
` : html`
@@ -277,6 +289,10 @@ class CollIndex extends LitElement
</section>
`;
}
renderCollInfo(coll) {
return html`<wr-coll-info .coll=${coll}></wr-coll-info>`;
}
}
@@ -424,6 +440,6 @@ class WrCollInfo extends LitElement
}
customElements.define("wr-coll-info", WrCollInfo);
customElements.define("wr-coll-index", CollIndex);
customElements.define("wr-coll-index", WrCollIndex);
export { CollIndex };
export { WrCollIndex, WrCollInfo };
+46 -41
View File
@@ -10,7 +10,6 @@ import farResources from '@fortawesome/fontawesome-free/svgs/solid/puzzle-piece.
import farPages from '@fortawesome/fontawesome-free/svgs/regular/file-image.svg';
import fasInfoIcon from '@fortawesome/fontawesome-free/svgs/solid/info-circle.svg';
import fasSync from '@fortawesome/fontawesome-free/svgs/solid/sync-alt.svg';
import fasHome from '@fortawesome/fontawesome-free/svgs/solid/home.svg';
import fasRefresh from '@fortawesome/fontawesome-free/svgs/solid/redo-alt.svg';
import fasFullscreen from '@fortawesome/fontawesome-free/svgs/solid/desktop.svg';
@@ -30,7 +29,7 @@ const RWP_SCHEME = "search://";
// ===========================================================================
class Coll extends LitElement
class WrColl extends LitElement
{
constructor() {
super();
@@ -57,6 +56,12 @@ class Coll extends LitElement
this.ts = "";
this.tabNames = ["pages", "story", "resources", "info"];
this.tabLabels = {
"pages": "Pages",
"story": "Story",
"resources": "URLs",
"info": "Archive Info",
}
this.menuActive = false;
@@ -73,7 +78,6 @@ class Coll extends LitElement
this.appName = "ReplayWeb.page";
this.appLogo = rwpLogo;
this.appHomeUrl = "/";
}
static get properties() {
@@ -108,7 +112,6 @@ class Coll extends LitElement
appName: { type: String },
appLogo: { type: String },
appHomeUrl: { type: String }
}
}
@@ -294,7 +297,7 @@ class Coll extends LitElement
}
if (this.collInfo.coll && !this.tabNames.includes(this.tabData.view)) {
const view = this.hasStory ? "story" : (this.collInfo.pages.length ? "pages" : "resources");
const view = this.hasStory ? "story" : (this.editable || this.collInfo.pages.length ? "pages" : "resources");
this.tabData = {
...this.tabData, view
@@ -429,12 +432,21 @@ class Coll extends LitElement
flex: auto;
}
${Coll.replayBarStyles}
${WrColl.replayBarStyles}
`);
}
static get replayBarStyles() {
return css`
.breadbar {
display: flex;
align-items: center;
height: 35px;
width: 100%;
background-color: aliceblue;
padding: 0.5em;
}
.replay-bar {
padding: 0.5em 0em 0.5em 0.5em;
max-width: none;
@@ -502,7 +514,7 @@ class Coll extends LitElement
form {
width: 100%;
margin-left: 0.5em;
margin: 0 0 0 0.5em;
}
.gutter.gutter-horizontal {
@@ -591,8 +603,9 @@ class Coll extends LitElement
const isReplay = !!this.tabData.url;
const isSidebar = isReplay && this.showSidebar;
if (!isReplay){
document.title = `Browse Contents | ${this.appName}`;
if (!isReplay && this.tabData && this.tabData.view){
const detail = {title: this.tabLabels[this.tabData.view], replayTitle: false};
this.dispatchEvent(new CustomEvent("update-title", {bubbles: true, composed: true, detail}));
}
if (this.collInfo && !this.collInfo.coll) {
@@ -703,15 +716,6 @@ 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">
${!this.embed ? html`
<a href="${this.appHomeUrl}" role="button" class="button is-borderless is-hidden-touch" aria-labelledby="home" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small">
<fa-icon size="1.0em" .svg="${fasHome}" 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">
@@ -744,6 +748,7 @@ class Coll extends LitElement
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${farListAlt}"></fa-icon>
</span>
</a>
${this.renderExtraToolbar(false)}
<form @submit="${this.onSubmit}">
<div class="control is-expanded ${showFavIcon ? 'has-icons-left' : ''}">
<input id="url" class="input" type="text" @keydown="${this.onKeyDown}" @blur="${this.onLostFocus}" .value="${this.url}" placeholder="Enter text to search or a URL to replay"/>
@@ -766,15 +771,6 @@ class Coll extends LitElement
</div>
<div class="dropdown-menu" id="menu-dropdown">
<div class="dropdown-content">
${!this.embed ? html`
<a href="${this.appHomeUrl}" role="button" class="dropdown-item is-hidden-desktop" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasHome}"></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>
@@ -805,13 +801,15 @@ class Coll extends LitElement
</span>
<span>Browse Contents</span>
</a>
${this.renderExtraToolbar(true)}
${!this.editable ? html`
<hr class="dropdown-divider is-hidden-desktop">
<a href="#" role="button" class="dropdown-item" @click="${this.onPurgeCache}" @keyup="${clickOnSpacebarPress}">
<span class="icon is-small">
<fa-icon size="1.0em" class="has-text-grey" aria-hidden="true" .svg="${fasSync}"></fa-icon>
</span>
<span>Purge Cache + Full Reload</span>
</a>
</a>` : html``}
${dateStr ? html`
<hr class="dropdown-divider is-hidden-desktop">
<div class="dropdown-item info is-hidden-desktop">
@@ -844,6 +842,23 @@ class Coll extends LitElement
}
}
renderCollInfo() {
return 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>`;
}
renderExtraToolbar(isDropdown = false) {
return '';
}
renderCollTabs(isSidebar) {
const isStory = this.hasStory && this.tabData.view === 'story';
const isPages = this.tabData.view === 'pages';
@@ -852,17 +867,7 @@ class Coll extends LitElement
return 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``}
${isInfo ? this.renderCollInfo() : html``}
${isStory ? html`
<wr-coll-story .collInfo="${this.collInfo}"
@@ -1110,6 +1115,6 @@ class Coll extends LitElement
}
}
customElements.define("wr-coll", Coll);
customElements.define("wr-coll", WrColl);
export { Coll };
export { WrColl };
+2 -1
View File
@@ -128,4 +128,5 @@ class AnimLogo extends FaIcon
customElements.define("fa-icon", FaIcon);
customElements.define("wr-anim-logo", AnimLogo);
export { wrapCss, IS_APP, clickOnSpacebarPress, rwpLogo, FaIcon, AnimLogo };
export { wrapCss, IS_APP, clickOnSpacebarPress, rwpLogo, FaIcon, AnimLogo,
LitElement, html, css, unsafeCSS, unsafeSVG };
+1 -1
View File
@@ -806,7 +806,7 @@ class Pages extends LitElement
getNoResultsMessage() {
if (!this.collInfo || !this.collInfo.pages.length) {
return html`<span class="fix-text-wrapping">No "Pages" are defined in this archive. <a href="#view=resources">Browse by URL</a>.</span>`;
return html`<span class="fix-text-wrapping">No Pages are defined in this archive. The archive may be empty. <a href="#view=resources">Try browsing by URL</a>.</span>`;
}
if (this.updatingSearch) {
+6 -2
View File
@@ -92,6 +92,9 @@ class Replay extends LitElement
if (this.iframeUrl && changedProperties.has("iframeUrl")) {
this.waitForLoad();
const detail = {title: "Archived Page", replayTitle: false};
this.dispatchEvent(new CustomEvent("update-title", {bubbles: true, composed: true, detail}));
}
if (this.replayUrl && changedProperties.has("replayUrl")) {
@@ -131,8 +134,9 @@ class Replay extends LitElement
}
}
if (this.title){
document.title = `Replay of ${this.title} | ReplayWeb.page`;
if (this.title) {
const detail = {title: this.title, replayTitle: true};
this.dispatchEvent(new CustomEvent("update-title", {bubbles: true, composed: true, detail}));
}
}