summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-06-29 14:49:13 +0200
committerOskar Nyberg <oskar@mullvad.net>2021-07-01 11:37:48 +0200
commite6d83cc9fe4b8e951403bc3ee6d0744934b041ee (patch)
tree29a4738c20ce56e2f29855111a235a251807e96c /gui
parentd962e59af7ebe3980686324d430ff1854a832445 (diff)
downloadmullvadvpn-e6d83cc9fe4b8e951403bc3ee6d0744934b041ee.tar.xz
mullvadvpn-e6d83cc9fe4b8e951403bc3ee6d0744934b041ee.zip
Replace history methods with new ones more aligned with our needs
Diffstat (limited to 'gui')
-rw-r--r--gui/src/renderer/lib/history.ts147
-rw-r--r--gui/test/history.spec.ts117
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);
- });
});