diff options
| -rw-r--r-- | gui/src/renderer/lib/history.ts | 147 | ||||
| -rw-r--r-- | gui/test/history.spec.ts | 117 |
2 files changed, 122 insertions, 142 deletions
diff --git a/gui/src/renderer/lib/history.ts b/gui/src/renderer/lib/history.ts index 3f98508ce1..c3a06b98e3 100644 --- a/gui/src/renderer/lib/history.ts +++ b/gui/src/renderer/lib/history.ts @@ -1,9 +1,44 @@ import { Location, Action, LocationDescriptor } from 'history'; +export interface ITransitionSpecification { + name: string; + duration: number; +} + +interface ITransitionMap { + [name: string]: ITransitionSpecification; +} + +/** + * Transition descriptors + */ +export const transitions: ITransitionMap = { + show: { + name: 'slide-up', + duration: 450, + }, + dismiss: { + name: 'slide-down', + duration: 450, + }, + push: { + name: 'push', + duration: 450, + }, + pop: { + name: 'pop', + duration: 450, + }, + none: { + name: '', + duration: 0, + }, +}; + type LocationListener<S = unknown> = ( location: Location<S>, action: Action, - entries: Location<S>[], + transition: ITransitionSpecification, ) => void; // It currently isn't possible to implement this correctly with support for a generic state. State @@ -33,80 +68,90 @@ export default class History { } public push = (nextLocation: LocationDescriptor<S>, nextState?: S) => { - const affectedEntries = [this.entries[this.index]]; - const location = this.createLocation(nextLocation, nextState); - this.lastAction = 'PUSH'; - this.index += 1; - this.entries.splice(this.index, this.entries.length - this.index, location); - this.notify(affectedEntries); - }; - - public replace = (nextLocation: LocationDescriptor<S>, nextState?: S) => { - const affectedEntries = [this.entries[this.index]]; - this.entries[this.index] = this.createLocation(nextLocation, nextState); - this.lastAction = 'REPLACE'; - this.notify(affectedEntries); + this.pushImpl(nextLocation, nextState); + this.notify(transitions.push); }; - public go = (n: number) => { - if (this.canGo(n)) { - const nextIndex = this.index + n; - const affectedEntries = - this.index < nextIndex - ? this.entries.slice(this.index, nextIndex) - : this.entries.slice(nextIndex + 1, this.index + 1); - - this.index = nextIndex; - this.lastAction = 'POP'; - this.notify(affectedEntries); + public pop = () => { + if (this.popImpl()) { + this.notify(transitions.pop); } }; - public goBack = () => this.go(-1); - public goForward = () => this.go(1); + public show = (nextLocation: LocationDescriptor<S>, nextState?: S) => { + this.pushImpl(nextLocation, nextState); + this.notify(transitions.show); + }; - public reset = () => { - const affectedEntries = this.entries.slice(1); - this.lastAction = 'POP'; - this.index = 0; - this.notify(affectedEntries); + public dismiss = (all?: boolean) => { + if (this.popImpl(all ? this.index : 1)) { + this.notify(transitions.dismiss); + } }; - public resetWith = (nextLocation: LocationDescriptor<S>, nextState?: S) => { - const affectedEntries = [...this.entries]; - this.entries = [this.createLocation(nextLocation, nextState)]; + public reset = ( + nextLocation: LocationDescriptor<S>, + transition?: ITransitionSpecification, + nextState?: S, + ) => { + const location = this.createLocation(nextLocation, nextState); this.lastAction = 'REPLACE'; this.index = 0; - this.notify(affectedEntries); - }; + this.entries = [location]; - public resetWithIfDifferent = (nextLocation: LocationDescriptor<S>, nextState?: S) => { - const location = this.createLocation(nextLocation, nextState); - if (this.entries[0].pathname !== location.pathname) { - this.resetWith(nextLocation, nextState); - } + this.notify(transition ?? transitions.none); }; + public listen(callback: LocationListener<S>) { + this.listeners.push(callback); + return () => (this.listeners = this.listeners.filter((listener) => listener !== callback)); + } + public canGo(n: number) { const nextIndex = this.index + n; return nextIndex >= 0 && nextIndex < this.entries.length; } - public listen(callback: LocationListener<S>) { - this.listeners.push(callback); - return () => (this.listeners = this.listeners.filter((listener) => listener !== callback)); + public block(): never { + throw Error('Not implemented'); } - - public block(): () => void { + public replace(): never { throw Error('Not implemented'); } - - public createHref(): string { + public go(): never { + throw Error('Not implemented'); + } + public goBack(): never { + throw Error('Not implemented'); + } + public goForward(): never { + throw Error('Not implemented'); + } + public createHref(): never { throw Error('Not implemented'); } - private notify(affectedEntries: Location<S>[]) { - this.listeners.forEach((listener) => listener(this.location, this.action, affectedEntries)); + private pushImpl(nextLocation: LocationDescriptor<S>, nextState?: S) { + const location = this.createLocation(nextLocation, nextState); + this.lastAction = 'PUSH'; + this.index += 1; + this.entries.splice(this.index, this.entries.length - this.index, location); + } + + private popImpl(n = 1): boolean { + if (this.canGo(-n)) { + this.lastAction = 'POP'; + this.index -= n; + this.entries = this.entries.slice(0, this.index + 1); + + return true; + } else { + return false; + } + } + + private notify(transition: ITransitionSpecification) { + this.listeners.forEach((listener) => listener(this.location, this.action, transition)); } private createLocation(location: LocationDescriptor<S>, state?: S): Location<S> { diff --git a/gui/test/history.spec.ts b/gui/test/history.spec.ts index 1bf00dd4ba..3087905c7b 100644 --- a/gui/test/history.spec.ts +++ b/gui/test/history.spec.ts @@ -8,7 +8,6 @@ const SECOND_PATH = '/second-path'; const THIRD_PATH = '/third-path'; const FOURTH_PATH = '/fourth-path'; const FIFTH_PATH = '/fifth-path'; -const SIXTH_PATH = '/sixth-path'; describe('History', () => { let history: History; @@ -30,139 +29,75 @@ describe('History', () => { expect(history.length).to.equal(5); }); - it('should go back', () => { - history.goBack(); + it('should pop', () => { + history.pop(); expect(history.location.pathname).to.equal(THIRD_PATH); - expect(history.length).to.equal(5); + expect(history.length).to.equal(4); }); - it('should go back three entries', () => { - history.go(-3); - expect(history.location.pathname).to.equal(FIRST_PATH); - expect(history.length).to.equal(5); - }); + it('should fail to pop', () => { + history.pop(); + history.pop(); + history.pop(); + history.pop(); - it('should go forward', () => { - history.go(-3); - history.goForward(); - expect(history.location.pathname).to.equal(SECOND_PATH); - expect(history.length).to.equal(5); - }); + expect(history.location.pathname).to.equal(BASE_PATH); + expect(history.length).to.equal(1); - it('should go forward two entries', () => { - history.go(-3); - history.go(2); - expect(history.location.pathname).to.equal(THIRD_PATH); - expect(history.length).to.equal(5); - }); + history.pop(); - it('should fail to go forward', () => { - history.goForward(); - expect(history.location.pathname).to.equal(FOURTH_PATH); - expect(history.length).to.equal(5); + expect(history.location.pathname).to.equal(BASE_PATH); + expect(history.length).to.equal(1); }); it('should push', () => { history.push(FIFTH_PATH); - history.goBack(); - expect(history.location.pathname).to.equal(FOURTH_PATH); + expect(history.location.pathname).to.equal(FIFTH_PATH); expect(history.length).to.equal(6); }); - it('should replace', () => { - history.replace(FIFTH_PATH); - history.goBack(); - expect(history.location.pathname).to.equal(THIRD_PATH); - expect(history.length).to.equal(5); - }); - - it('should fail to go backwards further than base path', () => { - history.go(-5); - expect(history.location.pathname).to.equal(FOURTH_PATH); - expect(history.length).to.equal(5); - }); - it('should go backward to base path', () => { - history.reset(); + history.dismiss(true); expect(history.location.pathname).to.equal(BASE_PATH); - expect(history.length).to.equal(5); + expect(history.length).to.equal(1); }); it('should reset entries with path', () => { - history.resetWith(THIRD_PATH); + history.reset(THIRD_PATH); expect(history.location.pathname).to.equal(THIRD_PATH); expect(history.length).to.equal(1); - - history.goBack(); - expect(history.location.pathname).to.equal(THIRD_PATH); - expect(history.length).to.equal(1); - - history.goForward(); - expect(history.location.pathname).to.equal(THIRD_PATH); - expect(history.length).to.equal(1); - }); - - it('should fail to go forward after navigating', () => { - history.goBack(); - history.push(FIFTH_PATH); - history.goForward(); - expect(history.location.pathname).to.equal(FIFTH_PATH); - - history.goBack(); - history.replace(SIXTH_PATH); - history.goForward(); - expect(history.location.pathname).to.equal(FIFTH_PATH); }); it('should add a listener', () => { const listenerA = spy(); history.listen(listenerA); - history.goBack(); - history.goForward(); + history.pop(); + history.push(FIFTH_PATH); const listenerB = spy(); history.listen(listenerB); - history.reset(); + history.dismiss(true); history.push(FIRST_PATH); - history.replace(SECOND_PATH); - expect(listenerA).to.have.been.called.exactly(5); - expect(listenerB).to.have.been.called.exactly(3); + expect(listenerA).to.have.been.called.exactly(4); + expect(listenerB).to.have.been.called.exactly(2); }); it('should remove a listener', () => { const listenerA = spy(); const removeListenerA = history.listen(listenerA); - history.goBack(); - history.goForward(); + history.pop(); + history.push(FIFTH_PATH); const listenerB = spy(); history.listen(listenerB); - history.reset(); + history.dismiss(true); removeListenerA(); history.push(FIRST_PATH); - history.replace(SECOND_PATH); + history.reset(SECOND_PATH); expect(listenerA).to.have.been.called.exactly(3); expect(listenerB).to.have.been.called.exactly(3); }); - - it('should only remove listener once', () => { - const listenerA = spy(); - const removeListenerA = history.listen(listenerA); - history.goBack(); - - const listenerB = spy(); - history.listen(listenerB); - history.goForward(); - - removeListenerA(); - removeListenerA(); - - history.reset(); - - expect(listenerA).to.have.been.called.exactly(2); - expect(listenerB).to.have.been.called.exactly(2); - }); }); |
