mirror of
https://github.com/webrecorder/pywb.git
synced 2025-03-15 00:03:28 +01:00
vueui: second batch of fixes from @vanecat:
- support for 'cdx-simulator' - add loading spinner
This commit is contained in:
parent
6260b226ce
commit
dc81e78393
@ -13,6 +13,7 @@ collections:
|
|||||||
archive_paths: ./sample_archive/warcs/
|
archive_paths: ./sample_archive/warcs/
|
||||||
|
|
||||||
ukwa: cdx+https://www.webarchive.org.uk/wayback/archive/cdx
|
ukwa: cdx+https://www.webarchive.org.uk/wayback/archive/cdx
|
||||||
|
is: cdx+http://beta.vefsafn.is/is/cdx
|
||||||
|
|
||||||
# Settings for each collection
|
# Settings for each collection
|
||||||
use_js_obj_proxy: true
|
use_js_obj_proxy: true
|
||||||
|
156
pywb/static/loading-spinner/loading-spinner.js
Normal file
156
pywb/static/loading-spinner/loading-spinner.js
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
class LoadingSpinner {
|
||||||
|
static #instanceCount = 0;
|
||||||
|
constructor(config={}) {
|
||||||
|
this.config = {initialState:true, animationDuration:500, text:'Loading...', ...config};
|
||||||
|
|
||||||
|
if (LoadingSpinner.#instanceCount > 0) {
|
||||||
|
throw new Error('Cannot make a second loading spinner (aka progress indicator)');
|
||||||
|
}
|
||||||
|
LoadingSpinner.#instanceCount++;
|
||||||
|
|
||||||
|
|
||||||
|
const uuid = Math.floor(Math.random()*1000);
|
||||||
|
this.classes = {
|
||||||
|
el: `loading-spinner-${uuid}`,
|
||||||
|
mask: `loading-spinner-mask-${uuid}`,
|
||||||
|
hidden: `hidden-${uuid}`,
|
||||||
|
spinning: `spinning-${uuid}`
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state = config.initialState;
|
||||||
|
this.addStyles();
|
||||||
|
this.addDom();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
if (this.state) {
|
||||||
|
this.setOn();
|
||||||
|
} else {
|
||||||
|
this.setOff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setOn() {
|
||||||
|
this.state = true;
|
||||||
|
this.el.classList.remove(this.classes.hidden);
|
||||||
|
setTimeout(function setSpinning() {
|
||||||
|
this.el.classList.add(this.classes.spinning);
|
||||||
|
}.bind(this), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOff() {
|
||||||
|
this.state = false;
|
||||||
|
this.el.classList.remove(this.classes.spinning);
|
||||||
|
setTimeout(function setHidden() {
|
||||||
|
this.el.classList.add(this.classes.hidden);
|
||||||
|
}.bind(this), this.config.animationDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
addDom() {
|
||||||
|
const text = this.config.text;
|
||||||
|
const dom = `
|
||||||
|
<div class="${this.classes.mask} ${this.classes[this.config.initialState ? 'spinning':'hidden']}">
|
||||||
|
<div class="${this.classes.el}">
|
||||||
|
<div data-loading-spinner="circle1"></div>
|
||||||
|
<div data-loading-spinner="circle2"></div>
|
||||||
|
<div data-loading-spinner="circle3"></div>
|
||||||
|
<div data-loading-spinner="circle4"></div>
|
||||||
|
<span data-loading-spinner="text">${text}</span>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
const wrapEl = document.createElement('div');
|
||||||
|
wrapEl.innerHTML = dom;
|
||||||
|
this.el = wrapEl.firstElementChild;
|
||||||
|
document.getElementsByTagName('body')[0].appendChild(this.el);
|
||||||
|
}
|
||||||
|
|
||||||
|
addStyles() {
|
||||||
|
const duration = this.config.animationDuration;
|
||||||
|
const stylesheetEl = document.createElement('style');
|
||||||
|
document.head.appendChild(stylesheetEl);
|
||||||
|
|
||||||
|
const rules = [`
|
||||||
|
.${this.classes.mask} {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 900;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background-color: rgba(255,255,255, .85);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity ${duration}ms ease-in;
|
||||||
|
}`,`
|
||||||
|
.${this.classes.mask}.${this.classes.spinning} {
|
||||||
|
opacity: 1;
|
||||||
|
}`,`
|
||||||
|
.${this.classes.mask}.${this.classes.hidden} {
|
||||||
|
display: none;
|
||||||
|
}`,`
|
||||||
|
.${this.classes.el} {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}`,`
|
||||||
|
[data-loading-spinner^=circle] {
|
||||||
|
position: absolute;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
}`,`
|
||||||
|
[data-loading-spinner=circle1] {
|
||||||
|
border: 3px solid #444444;/* #0D4B9F; */
|
||||||
|
width: 70%;
|
||||||
|
height: 70%;
|
||||||
|
animation: rotate 2s cubic-bezier(0.26, 1.36, 0.74, -0.29) infinite;
|
||||||
|
}`,`
|
||||||
|
[data-loading-spinner=circle2] {
|
||||||
|
border: 3px solid #ddd;/* #E0EDFF; */
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
animation: rotateReverse 2s cubic-bezier(0.26, 1.36, 0.74, -0.29) infinite;
|
||||||
|
}`,`
|
||||||
|
[data-loading-spinner=circle3] {
|
||||||
|
border: 3px solid #656565;/* #005CDC; */
|
||||||
|
width: 90%;
|
||||||
|
height: 90%;
|
||||||
|
animation: rotate 2s cubic-bezier(0.26, 1.36, 0.74, -0.29) infinite;
|
||||||
|
}`,`
|
||||||
|
[data-loading-spinner=circle4] {
|
||||||
|
border: 3px solid #aaa; /* #94B6E5; */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
animation: rotateReverse 2s cubic-bezier(0.26, 1.36, 0.74, -0.29) infinite;
|
||||||
|
}`,`
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotateZ(-360deg)
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotateZ(0deg)
|
||||||
|
}
|
||||||
|
}`,`
|
||||||
|
@keyframes rotateReverse {
|
||||||
|
from {
|
||||||
|
transform: rotateZ(360deg)
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotateZ(0deg)
|
||||||
|
}
|
||||||
|
}`,`
|
||||||
|
[data-loading-spinner=text] {
|
||||||
|
font-size: 15px;
|
||||||
|
}`];
|
||||||
|
rules.forEach(rule => stylesheetEl.sheet.insertRule(rule));
|
||||||
|
}
|
||||||
|
}
|
12
pywb/static/loading-spinner/test.html
Normal file
12
pywb/static/loading-spinner/test.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="loading-spinner.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<button onclick="loadingSpinner.setOn()">load...</button>
|
||||||
|
<script>
|
||||||
|
const loadingSpinner = new LoadingSpinner();
|
||||||
|
loadingSpinner.el.addEventListener('click', e => loadingSpinner.setOff());
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because one or more lines are too long
@ -31,17 +31,12 @@ window.banner_info = {
|
|||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
|
{% if ui.vue_timeline_banner %}
|
||||||
|
<script src="{{ static_prefix }}/loading-spinner/loading-spinner.js"></script>
|
||||||
<link rel='stylesheet' href='{{ static_prefix }}/vue_banner.css'/>
|
<link rel='stylesheet' href='{{ static_prefix }}/vue_banner.css'/>
|
||||||
<script src="{{ static_prefix }}/vue/vueui.js"></script>
|
<script src="{{ static_prefix }}/vue/vueui.js"></script>
|
||||||
<script>
|
|
||||||
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ wb_prefix }}", "{{ timestamp }}", "{{ ui.logo }}");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% if ui.vue_timeline_banner %}
|
|
||||||
<div id="app" style="width: 100%; height: 200px"></div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +21,13 @@ html, body
|
|||||||
</head>
|
</head>
|
||||||
<body style="margin: 0px; padding: 0px;">
|
<body style="margin: 0px; padding: 0px;">
|
||||||
|
|
||||||
|
{% if ui.vue_timeline_banner %}
|
||||||
|
<div id="app" style="width: 100%; height: 200px"></div>
|
||||||
|
<script>
|
||||||
|
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ wb_prefix }}", "{{ timestamp }}", "{{ ui.logo }}");
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div id="wb_iframe_div">
|
<div id="wb_iframe_div">
|
||||||
<iframe id="replay_iframe" frameborder="0" seamless="seamless" scrolling="yes" class="wb_iframe" allow="autoplay; fullscreen"></iframe>
|
<iframe id="replay_iframe" frameborder="0" seamless="seamless" scrolling="yes" class="wb_iframe" allow="autoplay; fullscreen"></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<script src="{{ static_prefix }}/query.js"></script>
|
<script src="{{ static_prefix }}/query.js"></script>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
|
<script src="{{ static_prefix }}/loading-spinner/loading-spinner.js"></script>
|
||||||
<script src="{{ static_prefix }}/vue/vueui.js"></script>
|
<script src="{{ static_prefix }}/vue/vueui.js"></script>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -36,6 +37,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if ui.vue_calendar_ui %}
|
||||||
|
<div id="app" style="width: 100%; height: 100%"></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var text = {
|
var text = {
|
||||||
months: {
|
months: {
|
||||||
@ -81,8 +86,4 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% if ui.vue_calendar_ui %}
|
|
||||||
<div id="app" style="width: 100%; height: 100%"></div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -67,7 +67,7 @@ export default {
|
|||||||
currentPeriod: null,
|
currentPeriod: null,
|
||||||
currentSnapshot: null,
|
currentSnapshot: null,
|
||||||
msgs: [],
|
msgs: [],
|
||||||
showFullView: false,
|
showFullView: true,
|
||||||
showTimelineView: true,
|
showTimelineView: true,
|
||||||
maxTimelineZoomLevel: PywbPeriod.Type.day,
|
maxTimelineZoomLevel: PywbPeriod.Type.day,
|
||||||
config: {
|
config: {
|
||||||
@ -79,7 +79,6 @@ export default {
|
|||||||
},
|
},
|
||||||
components: {Timeline, TimelineBreadcrumbs, CalendarYear},
|
components: {Timeline, TimelineBreadcrumbs, CalendarYear},
|
||||||
mounted: function() {
|
mounted: function() {
|
||||||
this.init();
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
sessionStorageUrlKey() {
|
sessionStorageUrlKey() {
|
||||||
@ -133,21 +132,13 @@ export default {
|
|||||||
window.location.href = this.config.prefix + "*/" + newUrl;
|
window.location.href = this.config.prefix + "*/" + newUrl;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
init() {
|
setData(/** @type {PywbData} data */ data) {
|
||||||
this.config.url = this.config.initialView.url;
|
|
||||||
if (this.config.initialView.title) {
|
// data-set will usually happen at App INIT (from parent caller)
|
||||||
this.config.title = this.config.initialView.title;
|
this.$set(this, "snapshots", data.snapshots);
|
||||||
}
|
this.$set(this, "currentPeriod", data.timeline);
|
||||||
if (this.config.initialView.timestamp === undefined) {
|
|
||||||
this.showFullView = true;
|
// get last-saved current period from previous page/app refresh (if there was such)
|
||||||
this.showTimelineView = true;
|
|
||||||
} else {
|
|
||||||
this.showFullView = false;
|
|
||||||
this.showTimelineView = true;
|
|
||||||
if (this.currentPeriod.children.length) {
|
|
||||||
this.setSnapshot(this.config.initialView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (window.sessionStorage) {
|
if (window.sessionStorage) {
|
||||||
const currentPeriodId = window.sessionStorage.getItem(this.sessionStorageUrlKey);
|
const currentPeriodId = window.sessionStorage.getItem(this.sessionStorageUrlKey);
|
||||||
if (currentPeriodId) {
|
if (currentPeriodId) {
|
||||||
@ -157,12 +148,23 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// signal app is DONE setting and rendering data; ON NEXT TICK
|
||||||
|
this.$nextTick(function isDone() {
|
||||||
|
this.$emit('data-set-and-render-completed');
|
||||||
|
}.bind(this));
|
||||||
},
|
},
|
||||||
setSnapshot(view) {
|
setSnapshot(view) {
|
||||||
// convert to snapshot objec to support proper rendering of time/date
|
// turn off calendar (aka full) view
|
||||||
|
this.showFullView = false;
|
||||||
|
|
||||||
|
// convert to snapshot object to support proper rendering of time/date
|
||||||
const snapshot = new PywbSnapshot(view, 0);
|
const snapshot = new PywbSnapshot(view, 0);
|
||||||
|
|
||||||
|
// set config current URL and title
|
||||||
this.config.url = view.url;
|
this.config.url = view.url;
|
||||||
this.config.title = view.title;
|
this.config.title = view.title;
|
||||||
|
|
||||||
this.gotoSnapshot(snapshot);
|
this.gotoSnapshot(snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
pywb/vueui/src/cdx-simulator/README.md
Normal file
41
pywb/vueui/src/cdx-simulator/README.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# How to incorporate CDX Simulator
|
||||||
|
|
||||||
|
Place following code snippets in **index.js**
|
||||||
|
|
||||||
|
## Import `CDXQueryWorkerSimulator` Mock Class
|
||||||
|
|
||||||
|
It is the mock class to the main javascript built-in `Worker` class:
|
||||||
|
|
||||||
|
``import { CDXQueryWorkerSimulator } from "./cdx-simulator/cdx-simulator";``
|
||||||
|
|
||||||
|
## Initialize `queryWorker` with Mock Class
|
||||||
|
|
||||||
|
Update `const queryWorker = ...` initialization in `CDXLoader` class, `loadCDX()` method
|
||||||
|
|
||||||
|
### by replacing it
|
||||||
|
|
||||||
|
```
|
||||||
|
const queryWorker = new CDXQueryWorkerSimulator(this.staticPrefix + "/queryWorker.js");
|
||||||
|
```
|
||||||
|
|
||||||
|
### or by adding a conditional, so you can go back and forth between simulator and real CDX-data-loader:
|
||||||
|
|
||||||
|
for example with a URL-hash flag conditional:
|
||||||
|
|
||||||
|
```
|
||||||
|
const queryWorker = new (window.location.hash.indexOf('cdx_simulate') >= 0 ? CDXQueryWorkerSimulator : Worker)(this.staticPrefix + "/queryWorker.js");
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: where if the url contains '#cdx_simulate' the mock simulator will be used; using a URL hash does not interfere with the main URL parsing of the PYWB app
|
||||||
|
|
||||||
|
## Configure Simulation
|
||||||
|
|
||||||
|
Add a **local** storage (from Chrome Dev Tools > Application > Local Storage)
|
||||||
|
|
||||||
|
```
|
||||||
|
{"count":5000, "yearStart":2020, "yearEnd":2022, "fetchTime":3000}
|
||||||
|
```
|
||||||
|
|
||||||
|
where `count` is the total records, yearStart and yearEnd are self-explanatory, and `fetchTime` is how long it should take
|
||||||
|
|
||||||
|

|
197
pywb/vueui/src/cdx-simulator/cdx-record-sample.json
Normal file
197
pywb/vueui/src/cdx-simulator/cdx-record-sample.json
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/",
|
||||||
|
"timestamp": "20130729195151",
|
||||||
|
"url": "http://test@example.com/",
|
||||||
|
"mime": "warc/revisit",
|
||||||
|
"status": "-",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "591",
|
||||||
|
"offset": "355",
|
||||||
|
"filename": "example-url-agnostic-revisit.warc.gz",
|
||||||
|
"source": "pywb:url-agnost-example.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/",
|
||||||
|
"timestamp": "20140127171200",
|
||||||
|
"url": "http://example.com",
|
||||||
|
"mime": "text/html",
|
||||||
|
"status": "200",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "1046",
|
||||||
|
"offset": "334",
|
||||||
|
"filename": "dupes.warc.gz",
|
||||||
|
"source": "pywb:dupes.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/",
|
||||||
|
"timestamp": "20140127171251",
|
||||||
|
"url": "http://example.com",
|
||||||
|
"mime": "warc/revisit",
|
||||||
|
"status": "-",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "553",
|
||||||
|
"offset": "11875",
|
||||||
|
"filename": "dupes.warc.gz",
|
||||||
|
"source": "pywb:dupes.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/?example=1",
|
||||||
|
"timestamp": "20140103030321",
|
||||||
|
"url": "http://example.com?example=1",
|
||||||
|
"mime": "text/html",
|
||||||
|
"status": "200",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "1043",
|
||||||
|
"offset": "333",
|
||||||
|
"filename": "example.warc.gz",
|
||||||
|
"source": "pywb:example.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/?example=1",
|
||||||
|
"timestamp": "20140103030341",
|
||||||
|
"url": "http://example.com?example=1",
|
||||||
|
"mime": "warc/revisit",
|
||||||
|
"status": "-",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "553",
|
||||||
|
"offset": "1864",
|
||||||
|
"filename": "example.warc.gz",
|
||||||
|
"source": "pywb:example.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/?example=2",
|
||||||
|
"timestamp": "20140103030321",
|
||||||
|
"url": "http://example.com?example=2",
|
||||||
|
"mime": "text/html",
|
||||||
|
"status": "200",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "1987",
|
||||||
|
"offset": "0",
|
||||||
|
"filename": "example-extra.warc",
|
||||||
|
"source": "pywb:example-extra.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/?example=2",
|
||||||
|
"timestamp": "20140603030341",
|
||||||
|
"url": "http://example.com?example=2",
|
||||||
|
"mime": "warc/revisit",
|
||||||
|
"status": "-",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "504",
|
||||||
|
"offset": "2701",
|
||||||
|
"filename": "example-extra.warc",
|
||||||
|
"source": "pywb:example-extra.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/?example=2",
|
||||||
|
"timestamp": "20140603030351",
|
||||||
|
"url": "http://example.com?example=2",
|
||||||
|
"mime": "warc/revisit",
|
||||||
|
"status": "-",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36B",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "504",
|
||||||
|
"offset": "2701",
|
||||||
|
"filename": "example-extra.warc",
|
||||||
|
"source": "pywb:bad.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/?example=2",
|
||||||
|
"timestamp": "20140703030321",
|
||||||
|
"url": "http://example.com?example=2",
|
||||||
|
"mime": "text/html",
|
||||||
|
"status": "200",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "1987",
|
||||||
|
"offset": "0",
|
||||||
|
"filename": "non-existent.warc",
|
||||||
|
"source": "pywb:bad.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/?example=3",
|
||||||
|
"timestamp": "20140603030351",
|
||||||
|
"url": "http://example.com?example=3",
|
||||||
|
"mime": "warc/revisit",
|
||||||
|
"status": "-",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36B",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "504",
|
||||||
|
"offset": "2701",
|
||||||
|
"filename": "example-extra.warc",
|
||||||
|
"source": "pywb:bad.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example)/?example=3",
|
||||||
|
"timestamp": "20140703030321",
|
||||||
|
"url": "http://example.com?example=3",
|
||||||
|
"mime": "text/html",
|
||||||
|
"status": "200",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "1987",
|
||||||
|
"offset": "0",
|
||||||
|
"filename": "non-existent.warc",
|
||||||
|
"source": "pywb:bad.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example,test,arc)/",
|
||||||
|
"timestamp": "20140216050221",
|
||||||
|
"url": "http://example.com/",
|
||||||
|
"mime": "text/html",
|
||||||
|
"status": "200",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "1656",
|
||||||
|
"offset": "151",
|
||||||
|
"filename": "example.arc",
|
||||||
|
"source": "pywb:example-arc-test.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"urlkey": "com,example,test,gz,arc)/",
|
||||||
|
"timestamp": "20140216050221",
|
||||||
|
"url": "http://example.com/",
|
||||||
|
"mime": "text/html",
|
||||||
|
"status": "200",
|
||||||
|
"digest": "B2LTWWPUOYAH7UIPQ7ZUPQ4VMBSVC36A",
|
||||||
|
"redirect": "-",
|
||||||
|
"robotflags": "-",
|
||||||
|
"length": "856",
|
||||||
|
"offset": "171",
|
||||||
|
"filename": "example.arc.gz",
|
||||||
|
"source": "pywb:example-arc-test.cdx",
|
||||||
|
"source-coll": "pywb"
|
||||||
|
}
|
||||||
|
]
|
83
pywb/vueui/src/cdx-simulator/cdx-simulator.js
Normal file
83
pywb/vueui/src/cdx-simulator/cdx-simulator.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
const getMonthDays = (y, mZeroIndex) => {
|
||||||
|
const firstOfNextMonth = new Date(y, mZeroIndex+1, 1);
|
||||||
|
const lastOfMonth = new Date(firstOfNextMonth - 1000 * 3600 * 24);
|
||||||
|
return lastOfMonth.getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// read dynamically from local storage options for make
|
||||||
|
let simulateCdxOptions = window.localStorage.getItem('cdx_simulate');
|
||||||
|
simulateCdxOptions = !!simulateCdxOptions ? JSON.parse(simulateCdxOptions) : {};
|
||||||
|
|
||||||
|
class CDXRecordFactory {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
async make(url, opts={}) {
|
||||||
|
// defaults
|
||||||
|
opts = {count:1000, yearStart:2015, yearEnd:2022, fetchTime:5*1000, ...opts};
|
||||||
|
|
||||||
|
const records = [];
|
||||||
|
|
||||||
|
const total = opts.count;
|
||||||
|
const years = [opts.yearStart, opts.yearEnd];
|
||||||
|
const avgPerMonth = total / (years[1]-years[0]) / 12;
|
||||||
|
// exaggerate max count per day, any day can hold up to 10th of the month's captures
|
||||||
|
const maxPerDay = avgPerMonth/10;
|
||||||
|
|
||||||
|
let startTime = Math.floor(new Date().getTime());
|
||||||
|
let recordI = 0;
|
||||||
|
|
||||||
|
for(let y=years[0]; y<=years[1]; y++) {
|
||||||
|
for(let m=1; m<=12; m++) {
|
||||||
|
for(let d=1; d<=getMonthDays(y, m-1); d++) {
|
||||||
|
const dayTimestampPrefix = y + ('0'+m).substr(-2) + ('0'+d).substr(-2);
|
||||||
|
// minumum to maximum count (random value)
|
||||||
|
const timesCount = Math.floor(Math.random() * maxPerDay);
|
||||||
|
|
||||||
|
const times = {}; // make sure we save to hash to de-dupe
|
||||||
|
for(let i=0; i<timesCount; i++) {
|
||||||
|
const newTime = [Math.floor(Math.random()*24), Math.floor(Math.random()*60), Math.floor(Math.random()*60)].join('');
|
||||||
|
times[newTime] = 1;
|
||||||
|
}
|
||||||
|
Object.keys(times).sort().forEach(time => {
|
||||||
|
records.push({url, timestamp: dayTimestampPrefix+time});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let endTime = Math.floor(new Date().getTime());
|
||||||
|
|
||||||
|
if (opts.fetchTime && opts.fetchTime > endTime - startTime) { // wait till we reac fetchTime
|
||||||
|
const p = new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(true);
|
||||||
|
}, (opts.fetchTime - (endTime - startTime)));
|
||||||
|
});
|
||||||
|
await p;
|
||||||
|
}
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CDXQueryWorkerSimulator {
|
||||||
|
constructor(workerPath) {
|
||||||
|
this.messageCb = [];
|
||||||
|
this.recordFactory = new CDXRecordFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
addEventListener(type, cb) {
|
||||||
|
if (type === 'message') {
|
||||||
|
this.messageCb = cb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async postMessage({type, queryUrl}) {
|
||||||
|
const records = await this.recordFactory.make(queryUrl, simulateCdxOptions);
|
||||||
|
records.forEach(record => this.messageCb({data: {type: 'cdxRecord', record}}));
|
||||||
|
this.messageCb({data: {type: 'finished'}});
|
||||||
|
}
|
||||||
|
|
||||||
|
terminate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
BIN
pywb/vueui/src/cdx-simulator/pywb-vueui-cdx-simulator-config.jpg
Normal file
BIN
pywb/vueui/src/cdx-simulator/pywb-vueui-cdx-simulator-config.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
14
pywb/vueui/src/cdx-simulator/test.html
Normal file
14
pywb/vueui/src/cdx-simulator/test.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>CDX Simulator</title>
|
||||||
|
<script src="cdx-simulator.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
const factory = new CDXRecordFactory();
|
||||||
|
console.log(factory.make('test.com'));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
139
pywb/vueui/src/components/LoadingSpinner.vue
Normal file
139
pywb/vueui/src/components/LoadingSpinner.vue
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<div class="pywb-loading-spinner-mask" :class="{hidden: isHidden, spinning: isSpinning}" @click="setOff">
|
||||||
|
<div class="pywb-loading-spinner">
|
||||||
|
<div data-loading-spinner="circle1"></div>
|
||||||
|
<div data-loading-spinner="circle2"></div>
|
||||||
|
<div data-loading-spinner="circle3"></div>
|
||||||
|
<div data-loading-spinner="circle4"></div>
|
||||||
|
<span data-loading-spinner="text">{{text}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let elStyle = null;
|
||||||
|
export default {
|
||||||
|
name: "LoadingSpinner",
|
||||||
|
props: ['text', 'isLoading'],
|
||||||
|
mounted() {
|
||||||
|
this.setOnOff();
|
||||||
|
this.$watch('isLoading', this.setOnOff);
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isSpinning: '',
|
||||||
|
isHidden: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setOnOff() {
|
||||||
|
if (this.isLoading) {
|
||||||
|
this.setOn();
|
||||||
|
} else {
|
||||||
|
this.setOff();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setOn() {
|
||||||
|
this.isHidden = false;
|
||||||
|
setTimeout(function setSpinning() {
|
||||||
|
this.isSpinning = true;
|
||||||
|
}.bind(this), 100);
|
||||||
|
},
|
||||||
|
setOff() {
|
||||||
|
this.isSpinning = false;
|
||||||
|
setTimeout(function setHidden() {
|
||||||
|
this.isHidden = true;
|
||||||
|
}.bind(this), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pywb-loading-spinner-mask.spinning {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.pywb-loading-spinner-mask.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.pywb-loading-spinner-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 900;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background-color: rgba(255,255,255, .85);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 500ms ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pywb-loading-spinner {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
[data-loading-spinner^=circle] {
|
||||||
|
position: absolute;
|
||||||
|
border: 3px solid #444444;/* #0D4B9F; */
|
||||||
|
width: 70%;
|
||||||
|
height: 70%;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
animation: rotate 2s cubic-bezier(0.26, 1.36, 0.74, -0.29) infinite;
|
||||||
|
}
|
||||||
|
[data-loading-spinner=circle2] {
|
||||||
|
border: 3px solid #ddd;/* #E0EDFF; */
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
animation: rotate2 2s cubic-bezier(0.26, 1.36, 0.74, -0.29) infinite;
|
||||||
|
}
|
||||||
|
[data-loading-spinner=circle3] {
|
||||||
|
border: 3px solid #656565;/* #005CDC; */
|
||||||
|
width: 90%;
|
||||||
|
height: 90%;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
animation: rotate 2s cubic-bezier(0.26, 1.36, 0.74, -0.29) infinite;
|
||||||
|
}
|
||||||
|
[data-loading-spinner=circle4] {
|
||||||
|
border: 3px solid #aaa; /* #94B6E5; */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
animation: rotate2 2s cubic-bezier(0.26, 1.36, 0.74, -0.29) infinite;
|
||||||
|
}
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotateZ(-360deg)
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotateZ(0deg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes rotate2 {
|
||||||
|
0% {
|
||||||
|
transform: rotateZ(360deg)
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotateZ(0deg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[data-loading-spinner=text] {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -19,14 +19,14 @@
|
|||||||
<div class="scroll" ref="periodScroll" :class="{highlight: highlight}">
|
<div class="scroll" ref="periodScroll" :class="{highlight: highlight}">
|
||||||
<div class="periods" ref="periods">
|
<div class="periods" ref="periods">
|
||||||
<div v-for="subPeriod in period.children"
|
<div v-for="subPeriod in period.children"
|
||||||
:key="subPeriod.id"
|
:key="subPeriod.fullId"
|
||||||
class="period"
|
class="period"
|
||||||
:class="{empty: !subPeriod.snapshotCount, highlight: highlightPeriod === subPeriod, 'last-level': !canZoom, 'contains-current-snapshot': containsCurrentSnapshot(subPeriod) }"
|
:class="{empty: !subPeriod.snapshotCount, highlight: highlightPeriod === subPeriod, 'last-level': !canZoom, 'contains-current-snapshot': containsCurrentSnapshot(subPeriod) }"
|
||||||
>
|
>
|
||||||
<div class="histo">
|
<div class="histo">
|
||||||
<div class="line"
|
<div class="line"
|
||||||
v-for="histoPeriod in subPeriod.children"
|
v-for="histoPeriod in subPeriod.children"
|
||||||
:key="histoPeriod.id"
|
:key="histoPeriod.fullId"
|
||||||
:style="{height: getHistoLineHeight(histoPeriod.snapshotCount)}"
|
:style="{height: getHistoLineHeight(histoPeriod.snapshotCount)}"
|
||||||
:class="{'has-single-snapshot': histoPeriod.snapshotCount === 1, 'contains-current-snapshot': containsCurrentSnapshot(histoPeriod)}"
|
:class="{'has-single-snapshot': histoPeriod.snapshotCount === 1, 'contains-current-snapshot': containsCurrentSnapshot(histoPeriod)}"
|
||||||
@click="changePeriod(histoPeriod, $event)"
|
@click="changePeriod(histoPeriod, $event)"
|
||||||
|
@ -7,12 +7,14 @@ import Vue from "vue/dist/vue.esm.browser";
|
|||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
export function main(staticPrefix, url, prefix, timestamp, logoUrl) {
|
export function main(staticPrefix, url, prefix, timestamp, logoUrl) {
|
||||||
new CDXLoader(staticPrefix, url, prefix, timestamp, logoUrl);
|
const loadingSpinner = new LoadingSpinner(); // bootstrap loading-spinner EARLY ON
|
||||||
|
new CDXLoader(staticPrefix, url, prefix, timestamp, logoUrl, loadingSpinner);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
class CDXLoader {
|
class CDXLoader {
|
||||||
constructor(staticPrefix, url, prefix, timestamp, logoUrl) {
|
constructor(staticPrefix, url, prefix, timestamp, logoUrl, loadingSpinner) {
|
||||||
|
this.loadingSpinner = loadingSpinner;
|
||||||
this.opts = {};
|
this.opts = {};
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
this.staticPrefix = staticPrefix;
|
this.staticPrefix = staticPrefix;
|
||||||
@ -43,23 +45,17 @@ class CDXLoader {
|
|||||||
throw new Error("No query URL specified");
|
throw new Error("No query URL specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.opts.initialView = {url, timestamp};
|
const logoImg = this.staticPrefix + "/" + (this.logoUrl ? this.logoUrl : "pywb-logo-sm.png");
|
||||||
|
|
||||||
this.opts.logoImg = this.staticPrefix + "/" + (this.logoUrl ? this.logoUrl : "pywb-logo-sm.png");
|
|
||||||
|
|
||||||
|
this.app = this.initApp({logoImg, url});
|
||||||
this.loadCDX(queryURL).then((cdxList) => {
|
this.loadCDX(queryURL).then((cdxList) => {
|
||||||
this.app = this.initApp(cdxList, this.opts, (snapshot) => this.loadSnapshot(snapshot));
|
this.setAppData(cdxList, timestamp ? {url, timestamp}:null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initApp(data, config = {}, loadCallback = null) {
|
initApp(config = {}) {
|
||||||
const app = new Vue(appData);
|
const app = new Vue(appData);
|
||||||
|
|
||||||
const pywbData = new PywbData(data);
|
|
||||||
|
|
||||||
app.$set(app, "snapshots", pywbData.snapshots);
|
|
||||||
app.$set(app, "currentPeriod", pywbData.timeline);
|
|
||||||
|
|
||||||
app.$set(app, "config", {...app.config, ...config, prefix: this.prefix});
|
app.$set(app, "config", {...app.config, ...config, prefix: this.prefix});
|
||||||
|
|
||||||
app.$mount("#app");
|
app.$mount("#app");
|
||||||
@ -75,9 +71,9 @@ class CDXLoader {
|
|||||||
// };
|
// };
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
if (loadCallback) {
|
|
||||||
app.$on("show-snapshot", loadCallback);
|
app.$on("show-snapshot", this.loadSnapshot.bind(this));
|
||||||
}
|
app.$on("data-set-and-render-completed", () => this.loadingSpinner.setOff()); // only turn off loading-spinner AFTER app has told us it is DONE DONE
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
@ -90,16 +86,19 @@ class CDXLoader {
|
|||||||
|
|
||||||
const cdxList = await this.loadCDX(queryURL);
|
const cdxList = await this.loadCDX(queryURL);
|
||||||
|
|
||||||
const pywbData = new PywbData(cdxList);
|
this.setAppData(cdxList, {url, timestamp});
|
||||||
|
}
|
||||||
|
|
||||||
const app = this.app;
|
setAppData(cdxList, snapshot=null) {
|
||||||
app.$set(app, "snapshots", pywbData.snapshots);
|
this.app.setData(new PywbData(cdxList));
|
||||||
app.$set(app, "currentPeriod", pywbData.timeline);
|
|
||||||
|
|
||||||
app.setSnapshot({url, timestamp});
|
if (snapshot) {
|
||||||
|
this.app.setSnapshot(snapshot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCDX(queryURL) {
|
async loadCDX(queryURL) {
|
||||||
|
this.loadingSpinner.setOn(); // start loading-spinner when CDX loading begins
|
||||||
const queryWorker = new Worker(this.staticPrefix + "/queryWorker.js");
|
const queryWorker = new Worker(this.staticPrefix + "/queryWorker.js");
|
||||||
|
|
||||||
const p = new Promise((resolve) => {
|
const p = new Promise((resolve) => {
|
||||||
|
@ -8,24 +8,37 @@ export function PywbData(rawSnaps) {
|
|||||||
rawSnaps.forEach((rawSnap, i) => {
|
rawSnaps.forEach((rawSnap, i) => {
|
||||||
const snap = new PywbSnapshot(rawSnap, i);
|
const snap = new PywbSnapshot(rawSnap, i);
|
||||||
let year, month, day, hour, single;
|
let year, month, day, hour, single;
|
||||||
|
|
||||||
|
// Year Period
|
||||||
|
// if year did not exist in "all time", create it
|
||||||
if (!(year = allTimePeriod.getChildById(snap.year))) {
|
if (!(year = allTimePeriod.getChildById(snap.year))) {
|
||||||
if (lastYear) lastYear.checkIfSingleSnapshotOnly();
|
if (lastYear) lastYear.checkIfSingleSnapshotOnly(); // check last year for containing single snapshot
|
||||||
lastYear = year = new PywbPeriod({type: PywbPeriod.Type.year, id: snap.year});
|
lastYear = year = new PywbPeriod({type: PywbPeriod.Type.year, id: snap.year});
|
||||||
allTimePeriod.addChild(year);
|
allTimePeriod.addChild(year);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Month Period
|
||||||
|
// if month did not exist in "year" period, create it
|
||||||
if (!(month = year.getChildById(snap.month))) {
|
if (!(month = year.getChildById(snap.month))) {
|
||||||
if (lastMonth) lastMonth.checkIfSingleSnapshotOnly();
|
if (lastMonth) lastMonth.checkIfSingleSnapshotOnly();// check last month for containing single snapshot
|
||||||
lastMonth = month = new PywbPeriod({type: PywbPeriod.Type.month, id: snap.month});
|
lastMonth = month = new PywbPeriod({type: PywbPeriod.Type.month, id: snap.month});
|
||||||
year.addChild(month);
|
year.addChild(month);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Day Period
|
||||||
|
// if day did not exist in "month" period, create it
|
||||||
if (!(day = month.getChildById(snap.day))) {
|
if (!(day = month.getChildById(snap.day))) {
|
||||||
if (lastDay) lastDay.checkIfSingleSnapshotOnly();
|
if (lastDay) lastDay.checkIfSingleSnapshotOnly(); // check last day for containing single snapshot
|
||||||
lastDay = day = new PywbPeriod({type: PywbPeriod.Type.day, id: snap.day});
|
lastDay = day = new PywbPeriod({type: PywbPeriod.Type.day, id: snap.day});
|
||||||
month.addChild(day);
|
month.addChild(day);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hour Period
|
||||||
const hourValue = Math.ceil((snap.hour + .0001) / (24/8)); // divide day in 4 six-hour periods (aka quarters)
|
const hourValue = Math.ceil((snap.hour + .0001) / (24/8)); // divide day in 4 six-hour periods (aka quarters)
|
||||||
|
|
||||||
|
// if hour did not exist in "day" period, create it
|
||||||
if (!(hour = day.getChildById(hourValue))) {
|
if (!(hour = day.getChildById(hourValue))) {
|
||||||
if (lastHour) lastHour.checkIfSingleSnapshotOnly();
|
if (lastHour) lastHour.checkIfSingleSnapshotOnly(); // check last hour for containing single snapshot
|
||||||
lastHour = hour = new PywbPeriod({type: PywbPeriod.Type.hour, id: hourValue});
|
lastHour = hour = new PywbPeriod({type: PywbPeriod.Type.hour, id: hourValue});
|
||||||
day.addChild(hour);
|
day.addChild(hour);
|
||||||
}
|
}
|
||||||
@ -33,14 +46,29 @@ export function PywbData(rawSnaps) {
|
|||||||
single = new PywbPeriod({type: PywbPeriod.Type.snapshot, id: snap.id});
|
single = new PywbPeriod({type: PywbPeriod.Type.snapshot, id: snap.id});
|
||||||
hour.addChild(single);
|
hour.addChild(single);
|
||||||
}
|
}
|
||||||
single.setSnapshot(snap);
|
|
||||||
if (lastSingle) {
|
|
||||||
lastSingle.setNextSnapshotPeriod(single);
|
|
||||||
single.setPreviousSnapshotPeriod(lastSingle);
|
|
||||||
}
|
|
||||||
lastSingle = single;
|
|
||||||
|
|
||||||
snapshots.push(snap);
|
// De-duplicate single snapshots (sometimes there are multiple snapshots
|
||||||
|
// of the same timestamp with different HTTP status; ignore all
|
||||||
|
// duplicates and take the first entry regardless of status)
|
||||||
|
if (!lastSingle || lastSingle.id !== single.id) {
|
||||||
|
single.setSnapshot(snap);
|
||||||
|
if (lastSingle) {
|
||||||
|
lastSingle.setNextSnapshotPeriod(single);
|
||||||
|
single.setPreviousSnapshotPeriod(lastSingle);
|
||||||
|
}
|
||||||
|
lastSingle = single;
|
||||||
|
|
||||||
|
snapshots.push(snap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// At end of snapshot loop, check period of each type: year/month/day/hour
|
||||||
|
// as all snapshots are now added to the period hierarchy
|
||||||
|
if (i === rawSnaps.length - 1) { // is last snapshot
|
||||||
|
year.checkIfSingleSnapshotOnly();
|
||||||
|
month.checkIfSingleSnapshotOnly();
|
||||||
|
day.checkIfSingleSnapshotOnly();
|
||||||
|
hour.checkIfSingleSnapshotOnly();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.timeline = allTimePeriod;
|
this.timeline = allTimePeriod;
|
||||||
@ -112,7 +140,7 @@ export class PywbSnapshot {
|
|||||||
export function PywbPeriod(init) {
|
export function PywbPeriod(init) {
|
||||||
this.type = init.type;
|
this.type = init.type;
|
||||||
this.id = init.id;
|
this.id = init.id;
|
||||||
this.fullId = ''; // full-id property that include string id of parents and self with a delimitor
|
this.fullId = Math.floor(1000*1000+Math.random()*9*1000*1000).toString(16); // full-id property that include string id of parents and self with a delimitor
|
||||||
|
|
||||||
this.childrenIds = {}; // allow for query by ID
|
this.childrenIds = {}; // allow for query by ID
|
||||||
this.children = []; // allow for sequentiality / order
|
this.children = []; // allow for sequentiality / order
|
||||||
|
Loading…
x
Reference in New Issue
Block a user