Created
November 18, 2020 15:57
-
-
Save th3hunt/2601a6dbfaf5ca38f243184a584f0731 to your computer and use it in GitHub Desktop.
Nested History
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* NestedHistory | |
* ------------- | |
* | |
* TODO: doc | |
* | |
*/ | |
import _ from 'underscore'; | |
const NESTED_HISTORY_HEAD = 'head'; | |
const NESTED_HISTORY_TAIL = 'tail'; | |
const headState = {nested_history: NESTED_HISTORY_HEAD}; | |
const tailState = {nested_history: NESTED_HISTORY_TAIL}; | |
const sortByPosition = (entry1, entry2) => _.compareNumbers(entry1.position, entry2.position); | |
const isBrowserHistoryAtNestHead = () => (global.history.state || {}).nested_history === NESTED_HISTORY_HEAD; | |
const isBrowserHistoryAtNestTail = () => (global.history.state || {}).nested_history === NESTED_HISTORY_TAIL; | |
class Entry { | |
constructor({id, state, title, url, position, onPop, onRemove}) { | |
this.id = id; | |
this.state = state; | |
this.title = title; | |
this.url = url; | |
this.popCallback = onPop; | |
this.removeCallback = onRemove; | |
this.position = position; | |
} | |
onPop() { | |
if (this.popCallback) { | |
this.popCallback(); | |
} | |
} | |
onRemove() { | |
if (this.removeCallback) { | |
this.removeCallback(); | |
} | |
} | |
} | |
class NestedHistory { | |
constructor() { | |
this.entries = new Map(); | |
} | |
bindToBrowserHistory() { | |
global.addEventListener('popstate', e => this.onPopState(e.state)); | |
this.isBoundToBrowserHistory = true; | |
} | |
onPopState() { | |
if (!isBrowserHistoryAtNestTail()) { | |
return; | |
} | |
if (this.isEmpty) { | |
return; | |
} | |
const previousHead = this.head(); | |
if (!previousHead) { | |
return; | |
} | |
this.remove(previousHead); | |
if (this.isEmpty) { | |
return; | |
} | |
this.pushToBrowserHistory(); | |
const poppedUpHead = this.head(); | |
if (poppedUpHead) { | |
poppedUpHead.onPop(); | |
} | |
} | |
jumpBefore(entry, {silent} = {}) { | |
if (!this.hasEntry(entry)) { | |
return; | |
} | |
entry = this.getEntry(entry); | |
for (const entryToRemove of this.getEntries().reverse()) { | |
this.remove(entryToRemove, {silent}); | |
if (entryToRemove === entry) { | |
break; | |
} | |
} | |
if (this.isEmpty && isBrowserHistoryAtNestHead()) { | |
setTimeout(() => { | |
if (this.isEmpty && isBrowserHistoryAtNestHead()) { | |
global.history.go(-1); | |
} | |
}, 20); | |
} else { | |
this.pushToBrowserHistory(); | |
} | |
} | |
push(entryProps = {}) { | |
if (!this.isBoundToBrowserHistory) { | |
this.bindToBrowserHistory(); | |
} | |
const entry = new Entry({ | |
...entryProps, | |
id: entryProps.id || _.uniqueId('nh:'), | |
position: this.entries.size | |
}); | |
this.entries.set(entry.id, entry); | |
this.pushToBrowserHistory(); | |
return entry; | |
} | |
remove(entry, {silent = false} = {}) { | |
if (!entry) { | |
return; | |
} | |
this.entries.delete(entry.id); | |
if (!silent) { | |
entry.onRemove(); | |
} | |
} | |
getEntry(entry) { | |
return this.entries.get(entry.id || entry); | |
} | |
hasEntry(entry) { | |
return this.entries.has(entry.id || entry); | |
} | |
getEntries() { | |
return Array.from(this.entries.values()).sort(sortByPosition); | |
} | |
getHistoryFromEntries() { | |
return this.getEntries().map(entry => entry.id); | |
} | |
clear() { | |
this.entries.clear(); | |
} | |
head() { | |
return _.last(this.getEntries()); | |
} | |
pushToBrowserHistory() { | |
if (isBrowserHistoryAtNestHead() || this.isEmpty) { | |
return; | |
} | |
const title = document.title; | |
if (isBrowserHistoryAtNestTail()) { | |
global.history.pushState(headState, title); | |
} else { | |
global.history.replaceState({...global.history.state, ...tailState}, title); | |
global.history.pushState(headState, title); | |
} | |
} | |
get isEmpty() { | |
return this.entries.size === 0; | |
} | |
} | |
export default new NestedHistory(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment