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
+12
View File
@@ -1,5 +1,17 @@
## CHANGES
v1.3.0
- UI: Condense navbar and location bar: add logo to location bar, condense button spacing, add about link to context menu
- UI: Add collection info dropdown to be a separate tab instead
- UI: Convert Terms into general About dialog
- Loading: IPFS support! Load from `ipfs://` URLs, eg. `ipfs://hash/filename.wacz. Possible to specify extension for root with: `ipfs://hash#filename=.wacz`
- App: Electron App uses IPFS in node, proxies through file proxy.
- Loading: Native File System (Chrome-only) support: Loading local files on Chrome uses FileHandle, which allows persistent reuse of files with permission
- Loading: Optimize local file loading on all browsers: No more separate webworker, serviceworker caches entire blob into memory. Should fix Firefox loading issues streaming from blob.
- Loading/UI: Revamp permissions UI to replace iframe instead of popup
- UI: Favicon smoother update, better check for valid icon
- UI: Purge Cache / Full Reload reloads current frame, not parent
v1.2.1
- Loading/UI: Fix Google Drive Loading/Reauthorization: Only trigger when reauth is needed. For rate-limit errors, attempt reloading more slowly (exponential backoff)
- Loading: ReplayWeb.page App supports opening WACZ, WARC and HAR files via double-clicking from OS and via command-line.
+8 -3
View File
@@ -1,7 +1,7 @@
{
"name": "replaywebpage",
"productName": "ReplayWeb.page",
"version": "1.2.3",
"version": "1.3.0",
"description": "Serverless Web Archive Replay",
"repository": "https://github.com/webrecorder/replayweb.page",
"homepage": "https://replayweb.page/",
@@ -9,10 +9,11 @@
"license": "AGPL-3.0-or-later",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.13.0",
"@webrecorder/wabac": "^2.1.5",
"@webrecorder/wabac": "^2.2.0",
"bulma": "^0.9.0",
"fetch-ndjson": "^1.1.0",
"flexsearch": "^0.6.32",
"ipfs-core": "^0.2.0",
"keyword-mark-element": "^0.1.2",
"lit-element": "^2.3.1",
"marked": "^1.0.0",
@@ -23,6 +24,7 @@
"split.js": "^1.6.2"
},
"devDependencies": {
"copy-webpack-plugin": "^6.3.0",
"css-loader": "^3.5.3",
"electron": "^10.1.3",
"electron-builder": "^22.7.0",
@@ -35,7 +37,7 @@
"style-loader": "^1.2.1",
"svg-inline-loader": "^0.8.2",
"to-string-loader": "^1.1.6",
"webpack": "^4.43.0",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
},
@@ -56,6 +58,7 @@
"start-electron": "NODE_ENV=development electron ./dist/electron.js $1",
"start-jekyll": "yarn run build-docs && bundle exec jekyll serve",
"dist": "yarn run build && yarn run pack",
"dist-dev": "yarn run build-dev && yarn run pack",
"release": "yarn run build && electron-builder"
},
"build": {
@@ -70,6 +73,8 @@
"files": [
"!**/node_modules/**/*",
"dist/*.js",
"dist/prebuilds/${platform}-${arch}/*",
"dist/build/*",
"*.js",
"*.html",
"webmanifest.json",
+82 -80
View File
@@ -1,14 +1,11 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss, rwpLogo, IS_APP, clickOnSpacebarPress } from './misc';
import { registerSW, initDBWorker } from './pageutils';
import prettyBytes from 'pretty-bytes';
import { registerSW } from './pageutils';
import fasHelp from '@fortawesome/fontawesome-free/svgs/solid/question-circle.svg';
import fasArrowLeft from '@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg';
import fasArrowRight from '@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg';
import fasInfoIcon from '@fortawesome/fontawesome-free/svgs/solid/info-circle.svg';
// ===========================================================================
@@ -19,14 +16,13 @@ class App extends LitElement
super();
this.sourceUrl = null;
this.collInfo = null;
this.showTerms = false;
this.showAbout = false;
this.pageParams = {};
this.inited = false;
this.navMenuShown = false;
registerSW(__SW_NAME__);
initDBWorker();
this.safariKeyframes();
}
@@ -37,7 +33,7 @@ class App extends LitElement
pageParams: { type: Object },
sourceUrl: { type: String },
navMenuShown: { type: Boolean },
showTerms: { type: Boolean },
showAbout: { type: Boolean },
collInfo: { type: Object },
loadInfo: { type: Object },
embed: { type: String },
@@ -112,12 +108,19 @@ class App extends LitElement
`);
}
renderNavBar() {
}
render() {
if (!this.inited) {
return html``;
}
const showNavBar = !this.collInfo && (!this.embed || this.embed === "full");
return html`
${!this.embed || this.embed === "full" ? 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">
@@ -158,37 +161,6 @@ class App extends LitElement
<fa-icon .svg="${fasArrowRight}" aria-hidden="true"></fa-icon><span class="menu-only">&nbsp;Go Forward</span>
</a>
` : ``}
${this.sourceUrl && this.collInfo ? html`
<div class="navbar-item has-dropdown is-hoverable">
<span tabindex="0" class="info-menu navbar-link is-arrowless" aria-labelledby="about-this-archive">
<fa-icon size="1.2em" .svg="${fasInfoIcon}" aria-hidden="true"></fa-icon>
<span id="about-this-archive" class="menu-only">About this Archive</span>
</span>
<div class="navbar-dropdown">
<div class="navbar-item wide-only"><i>About this Archive</i></div>
<hr class="navbar-divider">
<div class="navbar-item">
Source:&nbsp;<b>${this.sourceUrl}</b>
</div>
<div class="navbar-item">
Filename:&nbsp;<b>${this.collInfo.filename}</b>
</div>
<div class="navbar-item">
Total Size:&nbsp;<b>${prettyBytes(Number(this.collInfo.size || 0))}</b>
</div>
<div class="navbar-item">
Loading Mode:&nbsp;<b>${this.collInfo.onDemand ? "Download On-Demand" : "Fully Local"}</b>
</div>
<div class="navbar-item">
Collection Id:&nbsp;<b>${this.collInfo.coll}</b>
</div>
<hr class="navbar-divider">
<div class="navbar-item">
ReplayWeb.page Version: ${__VERSION__}
</div>
</div>
</div>
` : ``}
</div>
${!this.embed ? html`
<div class="navbar-end">
@@ -196,10 +168,10 @@ class App extends LitElement
<fa-icon .svg="${fasHelp}" aria-hidden="true"></fa-icon><span>&nbsp;User Docs</span>
</a>
<!--
-- NB: the Terms modal is currently inaccessible to people using keyboards or screen readers.
-- Should all the JS and infrastructure for accessible modals be added, or should Terms be a normal page?
-- 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.showTerms = true} }"class="navbar-item">Terms</a>
<a href="?terms" @click="${(e) => { e.preventDefault(); this.showAbout = true} }"class="navbar-item">About</a>
</div>` : html``}
</div>
</nav><p id="skip-main-target" tabindex="-1" class="is-sr-only">Skipped</p>
@@ -211,7 +183,8 @@ class App extends LitElement
sourceUrl="${this.sourceUrl}"
embed="${this.embed}"
@replay-favicons=${this.onFavIcons}
@coll-loaded=${this.onCollLoaded}></wr-coll>
@coll-loaded=${this.onCollLoaded}
@about-show=${(e) => this.showAbout = true}></wr-coll>
` : html`
<wr-coll-index>
@@ -222,42 +195,8 @@ class App extends LitElement
</wr-coll-index>
`}
${this.showTerms ? html`
<div class="modal is-active">
<div class="modal-background" @click="${(e) => this.showTerms = false}"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Terms and Privacy</p>
<button class="delete" aria-label="close" @click="${(e) => this.showTerms = false}"></button>
</header>
<section class="modal-card-body">
<div class="container">
<div class="content">
<p>This site is a static browser-based application that loads web archive files provided by the user
and renders them for replay in the browser.</p>
<p>The site is operated by the <a href="https://webrecorder.net/">Webrecorder Project</a></p>
<p>See the <a target="_blank" href="./docs">Docs</a> for more info on how it works.</p>
<h3>Privacy</h3>
<p><b>No data is uploaded anywhere and no information is collected.</b></p>
<p>All content rendered stays directly in your browser. When loading an archive from Google Drive,
the site may ask for account authorization to download the specified file only.</p>
<h4>Disclaimer of Warranties</h4>
<p>The application may not always be available. No guarantees!</p>
<p>Some legalese:</p>
<p style="font-size: 0.8rem">DISCLAIMER OF SOFTWARE WARRANTY. WEBRECORDER SOFTWARE PROVIDES THIS SOFTWARE TO YOU "AS AVAILABLE"
AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
INCLUDING WITHOUT LIMITATION ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
</p>
<a class="button is-warning" href="#" @click="${(e) => this.showTerms = false}">Close</a>
</div>
</div>
</section>
</div>
</div>
</div>
` : ``} `
${this.showAbout ? this.renderAbout() : ``}
`;
}
firstUpdated() {
@@ -344,7 +283,7 @@ class App extends LitElement
this.embed = this.pageParams.get("embed") || "";
if (this.pageParams.has("terms")) {
this.showTerms = true;
this.showAbout = true;
}
if (this.pageParams.get("config")) {
@@ -438,6 +377,69 @@ class App extends LitElement
document.head.appendChild(style);
style.appendChild(document.createTextNode(css));
}
renderAbout() {
return html`
<div class="modal is-active">
<div class="modal-background" @click="${this.onAboutClose}"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">About ReplayWeb.page ${IS_APP ? 'App' : ''}</p>
<button class="delete" aria-label="close" @click="${this.onAboutClose}"></button>
</header>
<section class="modal-card-body">
<div class="container">
<div class="content">
<div style="display: flex">
<div class="has-text-centered" style="width: 220px">
<wr-anim-logo class="logo" size="48px"></wr-anim-logo>
<div style="font-size: smaller; margin-bottom: 1em">${IS_APP ? 'App' : ''} v${__VERSION__}</div>
</div>
${IS_APP ? html`
<p>ReplayWeb.page App is a standalone app for Mac, Windows and Linux that loads web archive files provided by the user
and renders them for replay.</p>
` : html`
<p><a href="https://replayweb.page" target="_blank">ReplayWeb.page</a> is a browser-based viewer that loads web archive files provided by the user
and renders them for replay in the browser.</p>`}
</div>
<p>Full source code is available at:
<a href="https://github.com/webrecorder/replayweb.page" target="_blank">https://github.com/webrecorder/replayweb.page</a>
</p>
<p>See the <a target="_blank" href="./docs">User Docs</a> or the GitHub README for more info on how it works.</p>
<p>ReplayWeb.page is part of the <a href="https://webrecorder.net/" target="_blank">Webrecorder Project</a>.</p>
<h3>Privacy</h3>
<p><b>No data is uploaded anywhere and no information is collected.</b></p>
<p>All content rendered stays directly in your browser.<br/>When loading an archive from Google Drive,
the site may ask for account authorization to download the specified file only.</p>
<h4>Disclaimer of Warranties</h4>
<p>The application is provided "as is" without any guarantees.</p>
<details>
<summary>Legalese:</summary>
<p style="font-size: 0.8rem">DISCLAIMER OF SOFTWARE WARRANTY. WEBRECORDER SOFTWARE PROVIDES THIS SOFTWARE TO YOU "AS AVAILABLE"
AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
INCLUDING WITHOUT LIMITATION ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
</details>
<div class="has-text-centered">
<a class="button is-warning" href="#" @click="${this.onAboutClose}">Close</a>
</div>
</div>
</div>
</section>
</div>
</div>
</div>`;
}
onAboutClose() {
this.showAbout = false;
}
}
customElements.define("replay-app-main", App);
+45 -6
View File
@@ -1,5 +1,5 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss } from './misc';
import { IS_APP, wrapCss } from './misc';
import fasUpload from '@fortawesome/fontawesome-free/svgs/solid/upload.svg';
@@ -12,6 +12,14 @@ class Chooser extends LitElement
this.fileDisplayName = "";
this.file = null;
this.hasNativeFS = !!window.showOpenFilePicker && !IS_APP;
}
static get properties() {
return {
fileDisplayName: { type: String }
}
}
onChooseFile(event) {
@@ -22,7 +30,32 @@ class Chooser extends LitElement
this.file = event.currentTarget.files[0];
// file.path only available in electron app
this.fileDisplayName = "file://" + (this.file.path || this.file.name);
this.requestUpdate();
}
async onChooseNativeFile() {
const options = {
types: [
{
description: 'WARC, WACZ, HAR and WBN Files',
accept: {
'application/warc': ['.warc', '.gz'],
'application/har': ['.har'],
'application/wacz': ['.wacz'],
'application/wbn': ['.wbn']
}
}
]
}
const [fileHandle] = await window.showOpenFilePicker(options);
this.fileHandle = fileHandle;
this.file = await fileHandle.getFile();
this.fileDisplayName = "file://" + fileHandle.name;
}
randomId() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
onStartLoad(event) {
@@ -37,12 +70,17 @@ class Chooser extends LitElement
const url = new URL(__APP_FILE_SERVE_PREFIX__);
url.searchParams.set("filename", this.file.path);
loadInfo.loadUrl = url.href;
loadInfo.noCache = true;
} else if (this.fileHandle) {
loadInfo.loadUrl = this.fileDisplayName + "-" + this.randomId();
loadInfo.extra = {fileHandle: this.fileHandle};
loadInfo.noCache = false;
} else {
loadInfo.loadUrl = URL.createObjectURL(this.file);
loadInfo.blob = this.file;
loadInfo.noCache = false;
}
loadInfo.size = this.file.size;
loadInfo.noCache = this.file.path !== undefined;
loadInfo.name = this.fileDisplayName;
}
@@ -116,10 +154,11 @@ class Chooser extends LitElement
<div class="extra-padding panel-block file has-name">
<form class="content is-flex" @submit="${this.onStartLoad}">
<label class="file-label">
${!this.hasNativeFS ? html`
<input class="file-input"
@click="${(e) => e.currentTarget.value = null}"
@change=${this.onChooseFile} type="file" id="fileupload" name="fileupload">
<span class="file-cta">
@change=${this.onChooseFile} type="file" id="fileupload" name="fileupload">` : ``}
<span class="file-cta" @click="${this.onChooseNativeFile}">
<span class="file-icon">
<fa-icon size="0.9em" .svg=${fasUpload} aria-hidden="true"></fa-icon>
</span>
@@ -133,7 +172,7 @@ class Chooser extends LitElement
<p class="control is-expanded">
<input class="file-name input" type="text"
name="filename" id="filename"
pattern="((file|http|https|s3):\/\/.*\.(warc|warc.gz|zip|wacz|har|wbn))|(googledrive:\/\/.+)"
pattern="((file|http|https|ipfs|s3):\/\/.*\.(warc|warc.gz|zip|wacz|har|wbn))|(googledrive:\/\/.+)"
.value="${this.fileDisplayName}"
@input="${this.onInput}"
autocomplete="off"
+139 -39
View File
@@ -195,23 +195,6 @@ class CollIndex extends LitElement
.icon.is-left {
margin-left: 0.5rem;
}
.columns {
width: 100vw;
}
.column {
word-break: break-word;
position: relative;
}
.col-title:hover {
}
.col-title a {
display: block;
height: 100%;
}
.column:hover > .copy, .source-text:hover + .copy, .copy:hover {
display: inline;
}
.coll-block {
position: relative;
}
@@ -220,10 +203,6 @@ class CollIndex extends LitElement
top: 10px;
right: 10px;
}
.minihead {
font-size: 10px;
font-weight: bold;
}
#sort-select::after {
display: none;
}
@@ -279,23 +258,7 @@ class CollIndex extends LitElement
<div class="coll-list">
${this.sortedColls.map((coll, i) => html`
<div class="coll-block panel-block">
<div class="columns">
<div class="column col-title is-4">
<span class="subtitle has-text-weight-bold">
<a href="?source=${coll.sourceUrl}">${coll.title || coll.filename}</a>
</span>
</div>
<div class="column is-4">
<span class="source-text"><p class="minihead">Source</p>${coll.sourceUrl}&nbsp;</span>
<a @click="${(e) => this.onCopy(e, coll.sourceUrl)}" class="copy"><fa-icon .svg="${fasCopy}"/></a>
${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://") ? html`
<p><i>(${coll.filename})</i></p>` : ''}
</div>
<div class="column is-2"><p class="minihead">Date Loaded</p>${coll.ctime ? new Date(coll.ctime).toLocaleString() : ""}</div>
<div class="column is-2"><p class="minihead">Total Size</p>${prettyBytes(Number(coll.size || 0))}
</div>
</div>
<wr-coll-info .coll=${coll}></wr-coll-info>
${!this._deleting[coll.sourceUrl] ? html`
<button class="delete" aria-label="Unload Collection" title="Unload Collection" data-coll-index="${i}" @click="${this.onDeleteColl}"></button>
` : html`
@@ -314,6 +277,138 @@ class CollIndex extends LitElement
</section>
`;
}
}
// ===========================================================================
class WrCollInfo 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(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;
}
`);
}
render() {
const coll = this.coll;
const detailed = this.detailed;
return html`
<div class="columns">
<div class="column col-title is-4">
<span class="subtitle has-text-weight-bold">
${detailed ? html`
${coll.title || coll.filename}
` : html`
<a href="?source=${encodeURIComponent(coll.sourceUrl)}">${coll.title || coll.filename}</a>`}
</span>
</div>
${detailed && coll.desc ? html`
<div class="column">
<p class="minihead">Description</p>
${coll.desc}
</div>` : html`
`}
<div class="column is-4">
<span class="source-text"><p class="minihead">Source</p>${coll.sourceUrl}&nbsp;</span>
<a @click="${(e) => this.onCopy(e, coll.sourceUrl)}" class="copy"><fa-icon .svg="${fasCopy}"/></a>
${coll.sourceUrl && coll.sourceUrl.startsWith("googledrive://") ? html`
<p><i>(${coll.filename})</i></p>` : ''}
</div>
${detailed ? html`
<div class="column"><p class="minihead">Filename</p>${coll.filename}</div>` : html``}
<div class="column is-2"><p class="minihead">Date Loaded</p>${coll.ctime ? new Date(coll.ctime).toLocaleString() : ""}</div>
<div class="column is-2"><p class="minihead">Total Size</p>${prettyBytes(Number(coll.size || 0))}</div>
${detailed ? html`
<div class="column">
<p class="minihead">Loading Mode</p>
${coll.onDemand ? "Download On-Demand" : "Fully Local"}
</div>
<div class="column">
<p class="minihead">Collection id</p>
${coll.coll}
</div>
` : html``}
</div>`;
}
// purge + delete buttons
//
// <div class="column">
// <button @click="${(e) => this.onPurge(true)}" class="button">Purge Cache + Full Reload</button>
// </div>
// ${this.canDelete ? html`
// <div class="column">
// <button @click="${(e) => this.onPurge(false)}" class="button is-outlined is-danger">Delete Archive</button>
// </div>` : html``}
//
onCopy(event, sourceUrl) {
event.preventDefault();
@@ -321,9 +416,14 @@ class CollIndex extends LitElement
navigator.clipboard.writeText(sourceUrl);
return false;
}
onPurge(reload) {
const detail = {reload};
this.dispatchEvent(new CustomEvent("coll-purge", {detail}));
}
}
customElements.define("wr-coll-info", WrCollInfo);
customElements.define("wr-coll-index", CollIndex);
export { CollIndex };
+107 -30
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) {
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;
}
}
});
}
async onPurgeCache(event) {
// not found
this.favIconUrl = "";
}
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);
+80 -6
View File
@@ -8,7 +8,7 @@ import fs from 'fs';
import { ArchiveResponse, Rewriter } from '@webrecorder/wabac/src/rewrite';
import { PassThrough } from 'stream';
import { PassThrough, Readable } from 'stream';
import mime from 'mime-types';
@@ -21,6 +21,10 @@ const REPLAY_PREFIX = STATIC_PREFIX + "wabac/";
const URL_RX = /([^\/]+)\/([\d]+)(?:\w\w_)?\/(.*)$/;
let IPFS = null;
let ipfs = null;
let initingIPFS = null;
// ============================================================================
class ElectronReplayApp
@@ -95,7 +99,7 @@ class ElectronReplayApp
app.commandLine.appendSwitch('ppapi-flash-path', this.pluginPath);
console.log('app path', this.appPath);
console.log('dir name', this.__dirname);
console.log('dir name', __dirname);
console.log('proj path', this.projPath);
console.log('plugin path', this.pluginPath);
@@ -134,7 +138,38 @@ class ElectronReplayApp
});
}
static async doInitIPFS() {
if (!IPFS) {
IPFS = require("ipfs-core");
}
ipfs = await IPFS.create({
//repo: "/tmp/test-ipfs"
init: {emptyRepo: true},
//preload: {enabled: false},
});
}
async initIPFS() {
if (!ipfs) {
try {
if (!initingIPFS) {
initingIPFS = ElectronReplayApp.doInitIPFS();
}
await initingIPFS;
} catch (e) {
console.warn(e);
}
}
return ipfs;
}
onAppReady() {
//ElectronReplayApp.doInitIPFS();
this.screenSize = screen.getPrimaryDisplay().workAreaSize;
app.on('web-contents-created', (event, contents) => {
@@ -213,7 +248,14 @@ class ElectronReplayApp
if (request.url.startsWith(__APP_FILE_SERVE_PREFIX__)) {
const parsedUrl = new URL(request.url);
const filename = parsedUrl.searchParams.get("filename");
const ipfsCID = parsedUrl.searchParams.get("ipfs");
const headers = {"Content-Type": "application/octet-stream"};
const reqHeaders = new Headers(request.headers);
if (filename) {
console.log("file serve:", filename);
const stat = await fs.promises.lstat(filename);
@@ -224,16 +266,44 @@ class ElectronReplayApp
const size = stat.size;
const headers = {"Content-Length": "" + size, "Content-Type": "application/octet-stream"};
const reqHeaders = new Headers(request.headers);
const {statusCode, start, end} = this.parseRange(reqHeaders, headers, size);
const data = request.method === "HEAD" ? null : fs.createReadStream(filename, {start, end});
callback({statusCode, headers, data});
return;
} else if (ipfsCID) {
console.log("ipfs serve:", ipfsCID);
const ipfs = await this.initIPFS();
let size = 0;
for await (const file of ipfs.get(ipfsCID, {timeout: 20000, preload: false})) {
if (file.type !== "file") {
return this.notFound(ipfsCID, callback);
}
size = file.size;
break;
}
const {statusCode, start, end} = this.parseRange(reqHeaders, headers, size);
let data = null;
if (request.method === "GET") {
const offset = start || 0;
const length = end ? end - start + 1 : size;
data = Readable.from(ipfs.cat(ipfsCID, {offset, length}));
}
callback({statusCode, headers, data});
return;
} else {
return this.notFound("No Resource Specified", callback);
}
}
// possible 'live leak' attempt, just return not found
@@ -324,6 +394,9 @@ class ElectronReplayApp
const range = reqHeaders.get("range");
if (!range) {
if (headers) {
headers["Content-Length"] = "" + size;
}
return {statusCode};
}
@@ -337,6 +410,7 @@ class ElectronReplayApp
statusCode = 206;
if (headers) {
headers["Content-Range"] = `bytes ${start}-${end}/${size}`;
headers["Content-Length"] = `${end - start + 1}`;
}
return {statusCode, start, end};
}
+34 -8
View File
@@ -1,9 +1,9 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss } from './misc';
import { wrapCss, IS_APP } from './misc';
import prettyBytes from 'pretty-bytes';
import { parseURLSchemeHostPath, initDBWorker } from './pageutils';
import { parseURLSchemeHostPath } from './pageutils';
// ===========================================================================
@@ -20,8 +20,6 @@ class Loader extends LitElement
this.currentSize = 0;
this.totalSize = 0;
this.dbworker = initDBWorker();
}
static get properties() {
@@ -47,7 +45,11 @@ class Loader extends LitElement
}
initMessages() {
this.dbworker.addEventListener("message", (event) => {
if (!navigator.serviceWorker) {
return;
}
navigator.serviceWorker.addEventListener("message", (event) => {
switch (event.data.msg_type) {
case "collProgress":
if (event.data.name === this.coll) {
@@ -85,7 +87,13 @@ class Loader extends LitElement
if (!navigator.serviceWorker) {
this.state = "errored";
if (window.location.protocol === "http:") {
this.error = `\
Sorry, the ReplayWeb.page system must be loaded from an HTTPS URL, but was loaded from: ${window.location.host}.
Please try loading this page from an HTTPS URL`;
} else {
this.error = "Sorry, this browser is not supported. Please try a different browser\n(If you're using Firefox, try without Private Mode)";
}
return;
}
@@ -116,8 +124,18 @@ You can select a file to upload from the main page by clicking the \'Choose File
source = this.loadInfo;
break;
case "ipfs":
if (IS_APP) {
const url = new URL(__APP_FILE_SERVE_PREFIX__);
url.searchParams.set("ipfs", sourceUrl.slice("ipfs://".length));
source = {sourceUrl, loadUrl: url.href};
}
break;
}
} catch (e) {
console.log(e);
}
} catch (e) {}
if (!source) {
source = {sourceUrl};
@@ -131,7 +149,13 @@ You can select a file to upload from the main page by clicking the \'Choose File
msg.extraConfig = this.loadInfo.extraConfig;
}
this.dbworker.postMessage(msg);
if (!navigator.serviceWorker.controller) {
navigator.serviceWorker.addEventListener("controllerchange", (event) => {
navigator.serviceWorker.controller.postMessage(msg);
});
} else {
navigator.serviceWorker.controller.postMessage(msg);
}
}
googledriveInit() {
@@ -149,7 +173,9 @@ You can select a file to upload from the main page by clicking the \'Choose File
}
onCancel() {
this.dbworker.postMessage({"msg_type": "cancelLoad", "name": this.coll});
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({"msg_type": "cancelLoad", "name": this.coll});
}
}
updated(changedProperties) {
+2 -2
View File
@@ -2,7 +2,7 @@ import { register } from 'register-service-worker';
// ===========================================================================
async function registerSW(name = "sw.js", scope = "./") {
function registerSW(name = "sw.js", scope = "./") {
let resolve, reject;
const p = new Promise((res, rej) => {
@@ -28,7 +28,7 @@ async function registerSW(name = "sw.js", scope = "./") {
}
});
await p;
return p;
}
// ===========================================================================
+67 -31
View File
@@ -1,5 +1,5 @@
import { LitElement, html, css } from 'lit-element';
import { wrapCss } from './misc';
import { wrapCss, rwpLogo } from './misc';
@@ -17,6 +17,7 @@ class Replay extends LitElement
this.showAuth = false;
this.reauthWait = null;
this.authFileHandle = null;
}
static get properties() {
@@ -36,20 +37,37 @@ class Replay extends LitElement
iframeUrl: { type: String },
showAuth: { type: Boolean },
authFileHandle: { type: Object }
}
}
firstUpdated() {
window.addEventListener("message", (event) => this.onReplayMessage(event));
navigator.serviceWorker.addEventListener("message", async (event) => {
navigator.serviceWorker.addEventListener("message", (event) => this.handleAuthMessage(event));
}
async handleAuthMessage(event) {
if (event.data.type === "authneeded" && this.collInfo && event.data.coll === this.collInfo.coll) {
if (event.data.fileHandle) {
this.authFileHandle = event.data.fileHandle;
try {
if (await this.authFileHandle.requestPermission({mode: "read"}) === "granted") {
this.showAuth = false;
this.reauthWait = null;
this.refresh();
return;
}
} catch (e) {}
} else {
this.authFileHandle = null;
}
if (this.reauthWait) {
await this.reauthWait;
} else {
this.showAuth = true;
}
}
});
}
doSetIframeUrl() {
@@ -58,9 +76,6 @@ class Replay extends LitElement
updated(changedProperties) {
if (changedProperties.has("sourceUrl") || changedProperties.has("collInfo")) {
this.isAuthable = (this.sourceUrl.startsWith("googledrive://") &&
this.collInfo && this.collInfo.onDemand);
this.reauthWait = null;
}
@@ -70,6 +85,8 @@ class Replay extends LitElement
this.replayUrl = this.url;
this.replayTS = this.ts;
this.showAuth = false;
this.reauthWait = null;
this.doSetIframeUrl();
}
@@ -121,17 +138,28 @@ class Replay extends LitElement
onReAuthed(event) {
this.reauthWait = (async () => {
if (!this.authFileHandle) {
// google drive reauth
const headers = event.detail.headers;
const resp = await fetch(`${this.collInfo.apiPrefix}/updateAuth`, {
method: 'POST',
body: JSON.stringify({headers})
});
} else {
if (await this.authFileHandle.requestPermission({mode: "read"}) !== "granted") {
this.reauthWait = null;
return;
}
this.authFileHandle = null;
}
if (this.showAuth) {
this.refresh();
this.showAuth = false;
this.reauthWait = null;
}
this.refresh();
})();
}
@@ -191,18 +219,24 @@ class Replay extends LitElement
.intro-panel .panel-heading {
font-size: 1.0em;
display: inline-block;
}
#wrlogo {
vertical-align: middle;
}
.intro-panel .panel-block {
padding: 1.0em;
flex-direction: column;
line-height: 2.5em;
}
div.intro-panel.panel {
min-width: 40%;
display: flex;
flex-direction: column;
margin: auto;
margin: 3em auto;
}
`);
}
@@ -214,37 +248,39 @@ class Replay extends LitElement
<h1 id="replay-heading" class="is-sr-only">${title}</h1>
${this.iframeUrl ? html`
<iframe @message="${this.onReplayMessage}" allow="autoplay 'self'; fullscreen" allowfullscreen
src="${this.iframeUrl}" title="${title}"></iframe>
` : html`
${!this.iframeUrl ? html`
<div class="panel intro-panel">
<p class="panel-heading">Replay Web Page</p>
<div class="panel-block">
<p>Enter a URL above to replay it from the web archive!</p>
<p>(Or, check out <a href="#view=pages">Pages</a> or <a href="#view=resources">URLs</a> to explore the contents of this archive.)</p>
</div>
</div>
`}
</div>` : html`
${this.isAuthable ? html`
<div class="modal ${this.showAuth ? 'is-active' : ''}">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Auth Needed</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<div class="container has-text-centered">
<wr-gdrive .sourceUrl=${this.sourceUrl} .state="${this.showAuth ? 'trymanual' : 'implicitonly'}" .reauth="${true}" @load-ready=${this.onReAuthed}/>
${this.showAuth ? html`
<div class="panel intro-panel">
<p class="panel-heading">
<fa-icon id="wrlogo" size="1.5rem" .svg=${rwpLogo} aria-hidden="true"></fa-icon>
Authorization Needed
</p>
<div class="panel-block">
${this.authFileHandle ? html`
<p>This archive is loaded from a local file: <b>${this.authFileHandle.name}</b></p>
<p>The browser needs to confirm your permission to continue loading from this file.</p>
<button class="button is-warning is-rounded" @click="${this.onReAuthed}">Show Confirmation</button>
` : html`
<wr-gdrive
.sourceUrl=${this.sourceUrl}
.state="${this.showAuth ? 'trymanual' : 'implicitonly'}"
.reauth="${true}
@load-ready=${this.onReAuthed}/>`}
</div>
</section>
</div>
</div>
</div>
` : ``}
`;
</div>` : html`
<iframe @message="${this.onReplayMessage}" allow="autoplay 'self'; fullscreen" allowfullscreen
src="${this.iframeUrl}" title="${title}"></iframe>
`}
`}`;
}
}
+9 -15
View File
@@ -1,21 +1,15 @@
"use strict";
import INDEX_HTML from '../index.html';
if (self.registration) {
const staticData = new Map();
import { SWReplay } from '@webrecorder/wabac/src/swmain';
const prefix = self.registration.scope;
const staticData = new Map();
staticData.set(prefix, {type: "text/html", content: INDEX_HTML});
staticData.set(prefix + "index.html", {type: "text/html", content: INDEX_HTML});
//staticData.set(prefix + "ui.js", {type: "application/javascript", content: UI_JS});
const prefix = self.registration.scope;
const { SWReplay } = require('@webrecorder/wabac/src/swmain');
self.sw = new SWReplay(staticData);
staticData.set(prefix, {type: "text/html", content: INDEX_HTML});
staticData.set(prefix + "index.html", {type: "text/html", content: INDEX_HTML});
//staticData.set(prefix + "ui.js", {type: "application/javascript", content: UI_JS});
//const { SWReplay } = require('@webrecorder/wabac/src/swmain');
self.sw = new SWReplay(staticData);
} else if (self.postMessage) {
// Inited as Web Worker
const { WorkerLoader } = require('@webrecorder/wabac/src/loaders');
const loader = new WorkerLoader(self);
}
+7 -5
View File
File diff suppressed because one or more lines are too long
+307 -203
View File
File diff suppressed because one or more lines are too long
+23 -9
View File
@@ -1,6 +1,7 @@
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyPlugin = require('copy-webpack-plugin');
// fake url used in app to serve files
// can not use custom scheme due to service worker issues
@@ -25,9 +26,16 @@ const electronMainConfig = (env, argv) => {
entry: {
'electron': './src/electron-main.js',
},
resolve: {
alias: {
"abort-controller": "abort-controller/dist/abort-controller.js",
"dlv": "dlv/dist/dlv.js",
"bignumber.js": "bignumber.js/bignumber.js"
}
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
filename: '[name].js',
},
node: {
__dirname: false,
@@ -38,8 +46,18 @@ const electronMainConfig = (env, argv) => {
__APP_FILE_SERVE_PREFIX__ : JSON.stringify(APP_FILE_SERVE_PREFIX),
__HELPER_PROXY__ : JSON.stringify(HELPER_PROXY)
}),
new webpack.BannerPlugin(BANNER_TEXT)
new webpack.BannerPlugin(BANNER_TEXT),
new CopyPlugin({
patterns: [
{ from: 'node_modules/bcrypto/build/Release/bcrypto.node', to: 'build' },
{ from: 'node_modules/leveldown/prebuilds/', to: 'prebuilds' },
],
}),
],
externals: {
"bufferutil": "bufferutil",
"utf-8-validate": "utf-8-validate",
}
}
};
@@ -51,10 +69,6 @@ const electronPreloadConfig = (env, argv) => {
entry: {
'preload': './src/electron-preload.js',
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
plugins: [
new webpack.BannerPlugin(BANNER_TEXT)
],
@@ -111,15 +125,15 @@ const browserConfig = (env, argv) => {
rules: [
{
test: /\.svg$/,
loader: 'svg-inline-loader'
use: ['svg-inline-loader'],
},
{
test: /main.scss$/,
loaders: ['css-loader', 'sass-loader']
use: ['css-loader', 'sass-loader']
},
{
test: /wombat.js|wombatWorkers.js|index.html$/i,
loaders: ['raw-loader'],
use: ['raw-loader'],
}
]
},
+3566 -132
View File
File diff suppressed because it is too large Load Diff