From 66f6c4aba15c555cf0e77b1671e5c3dc92f92df7 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 16 Feb 2022 23:37:10 +0530 Subject: [PATCH 01/29] WIP --- .../{Navigation.js => Navigation.ts} | 91 +++++++++++++------ src/domain/navigation/index.js | 2 +- 2 files changed, 63 insertions(+), 30 deletions(-) rename src/domain/navigation/{Navigation.js => Navigation.ts} (76%) diff --git a/src/domain/navigation/Navigation.js b/src/domain/navigation/Navigation.ts similarity index 76% rename from src/domain/navigation/Navigation.js rename to src/domain/navigation/Navigation.ts index 340ae0d5..da6841f8 100644 --- a/src/domain/navigation/Navigation.js +++ b/src/domain/navigation/Navigation.ts @@ -15,28 +15,53 @@ limitations under the License. */ import {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue"; +import type {allowsChild as AllowsChild} from "./index.js"; + +type SegmentType = { + "login": true; + "session": string; + "sso": string; + "logout": true; + "room": string; + "rooms": string[]; + "settings": true; + "create-room": true; + "empty-grid-tile": number; + "lightbox": string; + "right-panel": boolean; + "details": true; + "members": true; + "member": string; +}; export class Navigation { - constructor(allowsChild) { + private readonly _allowsChild: AllowsChild; + private _path: Path; + private readonly _observables: Map = new Map(); + private readonly _pathObservable: ObservableValue; + + constructor(allowsChild: AllowsChild) { this._allowsChild = allowsChild; this._path = new Path([], allowsChild); - this._observables = new Map(); this._pathObservable = new ObservableValue(this._path); } - get pathObservable() { + get pathObservable(): ObservableValue { return this._pathObservable; } - get path() { + get path(): Path { return this._path; } - push(type, value = undefined) { - return this.applyPath(this.path.with(new Segment(type, value))); + push(type, value = undefined): void { + const newPath = this.path.with(new Segment(type, value)); + if (newPath) { + this.applyPath(newPath); + } } - applyPath(path) { + applyPath(path: Path): void { // Path is not exported, so you can only create a Path through Navigation, // so we assume it respects the allowsChild rules const oldPath = this._path; @@ -60,7 +85,7 @@ export class Navigation { this._pathObservable.set(this._path); } - observe(type) { + observe(type: keyof SegmentType): SegmentObservable { let observable = this._observables.get(type); if (!observable) { observable = new SegmentObservable(this, type); @@ -69,9 +94,9 @@ export class Navigation { return observable; } - pathFrom(segments) { - let parent; - let i; + pathFrom(segments: Segment[]): Path { + let parent: Segment | undefined; + let i: number; for (i = 0; i < segments.length; i += 1) { if (!this._allowsChild(parent, segments[i])) { return new Path(segments.slice(0, i), this._allowsChild); @@ -81,12 +106,12 @@ export class Navigation { return new Path(segments, this._allowsChild); } - segment(type, value) { + segment(type: T, value: SegmentType[T]): Segment { return new Segment(type, value); } } -function segmentValueEqual(a, b) { +function segmentValueEqual(a?: SegmentType[keyof SegmentType], b?: SegmentType[keyof SegmentType]): boolean { if (a === b) { return true; } @@ -103,24 +128,28 @@ function segmentValueEqual(a, b) { return false; } -export class Segment { - constructor(type, value) { - this.type = type; - this.value = value === undefined ? true : value; - } + +export class Segment { + constructor( + public type: T, + public value: SegmentType[T] | true = value === undefined ? true : value + ) {} } class Path { - constructor(segments = [], allowsChild) { + private readonly _segments: Segment[]; + private readonly _allowsChild: AllowsChild; + + constructor(segments: Segment[] = [], allowsChild: AllowsChild) { this._segments = segments; this._allowsChild = allowsChild; } - clone() { + clone(): Path { return new Path(this._segments.slice(), this._allowsChild); } - with(segment) { + with(segment: Segment): Path | null { let index = this._segments.length - 1; do { if (this._allowsChild(this._segments[index], segment)) { @@ -135,7 +164,7 @@ class Path { return null; } - until(type) { + until(type: keyof SegmentType): Path { const index = this._segments.findIndex(s => s.type === type); if (index !== -1) { return new Path(this._segments.slice(0, index + 1), this._allowsChild) @@ -143,11 +172,11 @@ class Path { return new Path([], this._allowsChild); } - get(type) { + get(type: keyof SegmentType): Segment | undefined { return this._segments.find(s => s.type === type); } - replace(segment) { + replace(segment: Segment): Path | null { const index = this._segments.findIndex(s => s.type === segment.type); if (index !== -1) { const parent = this._segments[index - 1]; @@ -163,7 +192,7 @@ class Path { return null; } - get segments() { + get segments(): Segment[] { return this._segments; } } @@ -172,22 +201,26 @@ class Path { * custom observable so it always returns what is in navigation.path, even if we haven't emitted the change yet. * This ensures that observers of a segment can also read the most recent value of other segments. */ -class SegmentObservable extends BaseObservableValue { - constructor(navigation, type) { +class SegmentObservable extends BaseObservableValue { + private readonly _navigation: Navigation; + private _type: keyof SegmentType; + private _lastSetValue?: SegmentType[keyof SegmentType]; + + constructor(navigation: Navigation, type: keyof SegmentType) { super(); this._navigation = navigation; this._type = type; this._lastSetValue = navigation.path.get(type)?.value; } - get() { + get(): SegmentType[keyof SegmentType] | undefined { const path = this._navigation.path; const segment = path.get(this._type); const value = segment?.value; return value; } - emitIfChanged() { + emitIfChanged(): void { const newValue = this.get(); if (!segmentValueEqual(newValue, this._lastSetValue)) { this._lastSetValue = newValue; diff --git a/src/domain/navigation/index.js b/src/domain/navigation/index.js index 086367ce..68fbe993 100644 --- a/src/domain/navigation/index.js +++ b/src/domain/navigation/index.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {Navigation, Segment} from "./Navigation.js"; +import {Navigation, Segment} from "./Navigation"; import {URLRouter} from "./URLRouter.js"; export function createNavigation() { From 04d5b9bfda15b085a7aea8377ec9fb0a62681b06 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 18 Feb 2022 16:07:18 +0530 Subject: [PATCH 02/29] WIP - 2 --- src/domain/navigation/Navigation.ts | 82 ++++++++++++++++------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index da6841f8..cb6637a9 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -34,11 +34,11 @@ type SegmentType = { "member": string; }; -export class Navigation { +export class Navigation { private readonly _allowsChild: AllowsChild; - private _path: Path; - private readonly _observables: Map = new Map(); - private readonly _pathObservable: ObservableValue; + private _path: Path; + private readonly _observables: Map> = new Map(); + private readonly _pathObservable: ObservableValue>; constructor(allowsChild: AllowsChild) { this._allowsChild = allowsChild; @@ -46,11 +46,11 @@ export class Navigation { this._pathObservable = new ObservableValue(this._path); } - get pathObservable(): ObservableValue { + get pathObservable(): ObservableValue> { return this._pathObservable; } - get path(): Path { + get path(): Path { return this._path; } @@ -61,7 +61,7 @@ export class Navigation { } } - applyPath(path: Path): void { + applyPath(path: Path): void { // Path is not exported, so you can only create a Path through Navigation, // so we assume it respects the allowsChild rules const oldPath = this._path; @@ -85,7 +85,7 @@ export class Navigation { this._pathObservable.set(this._path); } - observe(type: keyof SegmentType): SegmentObservable { + observe(type: keyof T): SegmentObservable { let observable = this._observables.get(type); if (!observable) { observable = new SegmentObservable(this, type); @@ -94,7 +94,7 @@ export class Navigation { return observable; } - pathFrom(segments: Segment[]): Path { + pathFrom(segments: Segment[]): Path { let parent: Segment | undefined; let i: number; for (i = 0; i < segments.length; i += 1) { @@ -106,12 +106,12 @@ export class Navigation { return new Path(segments, this._allowsChild); } - segment(type: T, value: SegmentType[T]): Segment { + segment(type: K, value: T[K]): Segment { return new Segment(type, value); } } -function segmentValueEqual(a?: SegmentType[keyof SegmentType], b?: SegmentType[keyof SegmentType]): boolean { +function segmentValueEqual(a?: T[keyof T], b?: T[keyof T]): boolean { if (a === b) { return true; } @@ -129,27 +129,27 @@ function segmentValueEqual(a?: SegmentType[keyof SegmentType], b?: SegmentType[k } -export class Segment { +export class Segment { constructor( - public type: T, - public value: SegmentType[T] | true = value === undefined ? true : value + public type: K, + public value: T[K] = (value === undefined ? true : value) as T[K] ) {} } -class Path { - private readonly _segments: Segment[]; +class Path { + private readonly _segments: Segment[]; private readonly _allowsChild: AllowsChild; - constructor(segments: Segment[] = [], allowsChild: AllowsChild) { + constructor(segments: Segment[] = [], allowsChild: AllowsChild) { this._segments = segments; this._allowsChild = allowsChild; } - clone(): Path { + clone(): Path { return new Path(this._segments.slice(), this._allowsChild); } - with(segment: Segment): Path | null { + with(segment: Segment): Path | null { let index = this._segments.length - 1; do { if (this._allowsChild(this._segments[index], segment)) { @@ -164,7 +164,7 @@ class Path { return null; } - until(type: keyof SegmentType): Path { + until(type: keyof T): Path { const index = this._segments.findIndex(s => s.type === type); if (index !== -1) { return new Path(this._segments.slice(0, index + 1), this._allowsChild) @@ -172,11 +172,11 @@ class Path { return new Path([], this._allowsChild); } - get(type: keyof SegmentType): Segment | undefined { + get(type: keyof T): Segment | undefined { return this._segments.find(s => s.type === type); } - replace(segment: Segment): Path | null { + replace(segment: Segment): Path | null { const index = this._segments.findIndex(s => s.type === segment.type); if (index !== -1) { const parent = this._segments[index - 1]; @@ -192,7 +192,7 @@ class Path { return null; } - get segments(): Segment[] { + get segments(): Segment[] { return this._segments; } } @@ -201,19 +201,19 @@ class Path { * custom observable so it always returns what is in navigation.path, even if we haven't emitted the change yet. * This ensures that observers of a segment can also read the most recent value of other segments. */ -class SegmentObservable extends BaseObservableValue { - private readonly _navigation: Navigation; - private _type: keyof SegmentType; - private _lastSetValue?: SegmentType[keyof SegmentType]; +class SegmentObservable extends BaseObservableValue { + private readonly _navigation: Navigation; + private _type: keyof T; + private _lastSetValue?: T[keyof T]; - constructor(navigation: Navigation, type: keyof SegmentType) { + constructor(navigation: Navigation, type: keyof T) { super(); this._navigation = navigation; this._type = type; this._lastSetValue = navigation.path.get(type)?.value; } - get(): SegmentType[keyof SegmentType] | undefined { + get(): T[keyof T] | undefined { const path = this._navigation.path; const segment = path.get(this._type); const value = segment?.value; @@ -222,7 +222,7 @@ class SegmentObservable extends BaseObservableValue(newValue, this._lastSetValue)) { this._lastSetValue = newValue; this.emit(newValue); } @@ -249,7 +249,7 @@ export function tests() { } function observeTypes(nav, types) { - const changes = []; + const changes: {type:string, value:any}[] = []; for (const type of types) { nav.observe(type).subscribe(value => { changes.push({type, value}); @@ -258,6 +258,12 @@ export function tests() { return changes; } + type SegmentType = { + "foo": number; + "bar": number; + "baz": number; + } + return { "applying a path emits an event on the observable": assert => { const nav = createMockNavigation(); @@ -275,18 +281,18 @@ export function tests() { assert.equal(changes[1].value, 8); }, "path.get": assert => { - const path = new Path([new Segment("foo", 5), new Segment("bar", 6)], () => true); - assert.equal(path.get("foo").value, 5); - assert.equal(path.get("bar").value, 6); + const path = new Path([new Segment("foo", 5), new Segment("bar", 6)], () => true); + assert.equal(path.get("foo")!.value, 5); + assert.equal(path.get("bar")!.value, 6); }, "path.replace success": assert => { - const path = new Path([new Segment("foo", 5), new Segment("bar", 6)], () => true); + const path = new Path([new Segment("foo", 5), new Segment("bar", 6)], () => true); const newPath = path.replace(new Segment("foo", 1)); - assert.equal(newPath.get("foo").value, 1); - assert.equal(newPath.get("bar").value, 6); + assert.equal(newPath!.get("foo")!.value, 1); + assert.equal(newPath!.get("bar")!.value, 6); }, "path.replace not found": assert => { - const path = new Path([new Segment("foo", 5), new Segment("bar", 6)], () => true); + const path = new Path([new Segment("foo", 5), new Segment("bar", 6)], () => true); const newPath = path.replace(new Segment("baz", 1)); assert.equal(newPath, null); } From 3efc426fedf596697f12c357b5bcca7483871ae0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 21 Feb 2022 17:30:46 +0530 Subject: [PATCH 03/29] Complete converting Navigation.js to ts --- src/domain/navigation/Navigation.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index cb6637a9..dfd69bd1 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -34,6 +34,7 @@ type SegmentType = { "member": string; }; + export class Navigation { private readonly _allowsChild: AllowsChild; private _path: Path; @@ -54,8 +55,8 @@ export class Navigation { return this._path; } - push(type, value = undefined): void { - const newPath = this.path.with(new Segment(type, value)); + push(type: K, ...value: T[K] extends true? [undefined?]: [T[K]]): void { + const newPath = this.path.with(new Segment(type, ...value)); if (newPath) { this.applyPath(newPath); } @@ -106,8 +107,8 @@ export class Navigation { return new Path(segments, this._allowsChild); } - segment(type: K, value: T[K]): Segment { - return new Segment(type, value); + segment(type: K, ...value: T[K] extends true? [undefined?]: [T[K]]): Segment { + return new Segment(type, ...value); } } @@ -130,10 +131,11 @@ function segmentValueEqual(a?: T[keyof T], b?: T[keyof T]): boolean { export class Segment { - constructor( - public type: K, - public value: T[K] = (value === undefined ? true : value) as T[K] - ) {} + public value: T[K]; + + constructor(public type: K, ...value: T[K] extends true? [undefined?]: [T[K]]) { + this.value = (value[0] === undefined ? true : value[0]) as unknown as T[K]; + } } class Path { From 55229252d750a6a00a26bade28b802f1f02e786b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 21 Feb 2022 17:37:30 +0530 Subject: [PATCH 04/29] Type allowsChild --- src/domain/navigation/Navigation.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index dfd69bd1..b3b79a89 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -15,7 +15,6 @@ limitations under the License. */ import {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue"; -import type {allowsChild as AllowsChild} from "./index.js"; type SegmentType = { "login": true; @@ -34,14 +33,15 @@ type SegmentType = { "member": string; }; +type AllowsChild = (parent: Segment | undefined, child: Segment) => boolean; export class Navigation { - private readonly _allowsChild: AllowsChild; + private readonly _allowsChild: AllowsChild; private _path: Path; private readonly _observables: Map> = new Map(); private readonly _pathObservable: ObservableValue>; - constructor(allowsChild: AllowsChild) { + constructor(allowsChild: AllowsChild) { this._allowsChild = allowsChild; this._path = new Path([], allowsChild); this._pathObservable = new ObservableValue(this._path); @@ -140,9 +140,9 @@ export class Segment { class Path { private readonly _segments: Segment[]; - private readonly _allowsChild: AllowsChild; + private readonly _allowsChild: AllowsChild; - constructor(segments: Segment[] = [], allowsChild: AllowsChild) { + constructor(segments: Segment[] = [], allowsChild: AllowsChild) { this._segments = segments; this._allowsChild = allowsChild; } @@ -237,13 +237,13 @@ export function tests() { return new Navigation((parent, {type}) => { switch (parent?.type) { case undefined: - return type === "1" || "2"; + return type === "1" || type === "2"; case "1": return type === "1.1"; case "1.1": return type === "1.1.1"; case "2": - return type === "2.1" || "2.2"; + return type === "2.1" || type === "2.2"; default: return false; } From 92c79c853d2442da004f91744617deb2120e59ef Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 11:41:36 +0530 Subject: [PATCH 05/29] Convert index.js to typescript --- src/domain/navigation/Navigation.ts | 24 +-- src/domain/navigation/{index.js => index.ts} | 175 +++++++++++------- src/domain/session/RoomGridViewModel.js | 4 +- .../session/leftpanel/LeftPanelViewModel.js | 2 +- src/lib.ts | 2 +- src/platform/web/main.js | 2 +- 6 files changed, 114 insertions(+), 95 deletions(-) rename src/domain/navigation/{index.js => index.ts} (75%) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index b3b79a89..f0b29816 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -16,22 +16,6 @@ limitations under the License. import {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue"; -type SegmentType = { - "login": true; - "session": string; - "sso": string; - "logout": true; - "room": string; - "rooms": string[]; - "settings": true; - "create-room": true; - "empty-grid-tile": number; - "lightbox": string; - "right-panel": boolean; - "details": true; - "members": true; - "member": string; -}; type AllowsChild = (parent: Segment | undefined, child: Segment) => boolean; @@ -55,7 +39,7 @@ export class Navigation { return this._path; } - push(type: K, ...value: T[K] extends true? [undefined?]: [T[K]]): void { + push(type: K, ...value: T[K] extends true? [(undefined | true)?]: [T[K]]): void { const newPath = this.path.with(new Segment(type, ...value)); if (newPath) { this.applyPath(newPath); @@ -107,7 +91,7 @@ export class Navigation { return new Path(segments, this._allowsChild); } - segment(type: K, ...value: T[K] extends true? [undefined?]: [T[K]]): Segment { + segment(type: K, ...value: T[K] extends true? [(undefined | true)?]: [T[K]]): Segment { return new Segment(type, ...value); } } @@ -133,7 +117,7 @@ function segmentValueEqual(a?: T[keyof T], b?: T[keyof T]): boolean { export class Segment { public value: T[K]; - constructor(public type: K, ...value: T[K] extends true? [undefined?]: [T[K]]) { + constructor(public type: K, ...value: T[K] extends true? [(undefined | true)?]: [T[K]]) { this.value = (value[0] === undefined ? true : value[0]) as unknown as T[K]; } } @@ -231,6 +215,8 @@ class SegmentObservable extends BaseObservableValue { } } +export type { Path }; + export function tests() { function createMockNavigation() { diff --git a/src/domain/navigation/index.js b/src/domain/navigation/index.ts similarity index 75% rename from src/domain/navigation/index.js rename to src/domain/navigation/index.ts index 68fbe993..92cf41a1 100644 --- a/src/domain/navigation/index.js +++ b/src/domain/navigation/index.ts @@ -16,6 +16,24 @@ limitations under the License. import {Navigation, Segment} from "./Navigation"; import {URLRouter} from "./URLRouter.js"; +import type { Path } from "./Navigation"; + +type SegmentType = { + login: true; + session: string; + sso: string; + logout: true; + room: string; + rooms: string[]; + settings: true; + "create-room": true; + "empty-grid-tile": number; + lightbox: string; + "right-panel": true; + details: true; + members: true; + member: string; +}; export function createNavigation() { return new Navigation(allowsChild); @@ -25,7 +43,7 @@ export function createRouter({history, navigation}) { return new URLRouter({history, navigation, stringifyPath, parseUrlPath}); } -function allowsChild(parent, child) { +function allowsChild(parent: {type: string, value: any} | undefined, child: {type: string, value: any}): boolean { const {type} = child; switch (parent?.type) { case undefined: @@ -45,8 +63,9 @@ function allowsChild(parent, child) { } } -export function removeRoomFromPath(path, roomId) { - const rooms = path.get("rooms"); +export function removeRoomFromPath(path: Path, roomId: string): Path | null { + let newPath: Path | null = path; + const rooms = newPath.get("rooms"); let roomIdGridIndex = -1; // first delete from rooms segment if (rooms) { @@ -54,22 +73,22 @@ export function removeRoomFromPath(path, roomId) { if (roomIdGridIndex !== -1) { const idsWithoutRoom = rooms.value.slice(); idsWithoutRoom[roomIdGridIndex] = ""; - path = path.replace(new Segment("rooms", idsWithoutRoom)); + newPath = newPath.replace(new Segment("rooms", idsWithoutRoom)); } } - const room = path.get("room"); + const room = newPath!.get("room"); // then from room (which occurs with or without rooms) if (room && room.value === roomId) { if (roomIdGridIndex !== -1) { - path = path.with(new Segment("empty-grid-tile", roomIdGridIndex)); + newPath = newPath!.with(new Segment("empty-grid-tile", roomIdGridIndex)); } else { - path = path.until("session"); + newPath = newPath!.until("session"); } } - return path; + return newPath; } -function roomsSegmentWithRoom(rooms, roomId, path) { +function roomsSegmentWithRoom(rooms: Segment, roomId: string, path: Path): Segment { if(!rooms.value.includes(roomId)) { const emptyGridTile = path.get("empty-grid-tile"); const oldRoom = path.get("room"); @@ -87,28 +106,29 @@ function roomsSegmentWithRoom(rooms, roomId, path) { } } -function pushRightPanelSegment(array, segment, value = true) { +// todo-self: verify code change here is okay +function pushRightPanelSegment(array: Segment[], segment: T, ...value: SegmentType[T] extends true? [(undefined | true)?]: [SegmentType[T]]) { array.push(new Segment("right-panel")); - array.push(new Segment(segment, value)); + array.push(new Segment(segment, ...value)); } -export function addPanelIfNeeded(navigation, path) { +export function addPanelIfNeeded(navigation: Navigation, path: Path): Path { const segments = navigation.path.segments; const i = segments.findIndex(segment => segment.type === "right-panel"); let _path = path; if (i !== -1) { _path = path.until("room"); - _path = _path.with(segments[i]); - _path = _path.with(segments[i + 1]); + _path = _path.with(segments[i])!; + _path = _path.with(segments[i + 1])!; } return _path; } -export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) { +export function parseUrlPath(urlPath: string, currentNavPath: Path, defaultSessionId: string): Segment[] { // substr(1) to take of initial / const parts = urlPath.substr(1).split("/"); const iterator = parts[Symbol.iterator](); - const segments = []; + const segments: Segment[] = []; let next; while (!(next = iterator.next()).done) { const type = next.value; @@ -170,9 +190,9 @@ export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) { return segments; } -export function stringifyPath(path) { +export function stringifyPath(path: Path): string { let urlPath = ""; - let prevSegment; + let prevSegment: Segment | undefined; for (const segment of path.segments) { switch (segment.type) { case "rooms": @@ -205,9 +225,16 @@ export function stringifyPath(path) { } export function tests() { + + function createEmptyPath() { + const nav: Navigation = new Navigation(allowsChild); + const path = nav.pathFrom([]); + return path; + } + return { "stringify grid url with focused empty tile": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), @@ -217,7 +244,7 @@ export function tests() { assert.equal(urlPath, "/session/1/rooms/a,b,c/3"); }, "stringify grid url with focused room": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), @@ -227,7 +254,7 @@ export function tests() { assert.equal(urlPath, "/session/1/rooms/a,b,c/1"); }, "stringify url with right-panel and details segment": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), @@ -239,13 +266,15 @@ export function tests() { assert.equal(urlPath, "/session/1/rooms/a,b,c/1/details"); }, "Parse loginToken query parameter into SSO segment": assert => { - const segments = parseUrlPath("?loginToken=a1232aSD123"); + const path = createEmptyPath(); + const segments = parseUrlPath("?loginToken=a1232aSD123", path, ""); assert.equal(segments.length, 1); assert.equal(segments[0].type, "sso"); assert.equal(segments[0].value, "a1232aSD123"); }, "parse grid url path with focused empty tile": assert => { - const segments = parseUrlPath("/session/1/rooms/a,b,c/3"); + const path = createEmptyPath(); + const segments = parseUrlPath("/session/1/rooms/a,b,c/3", path, ""); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -255,7 +284,8 @@ export function tests() { assert.equal(segments[2].value, 3); }, "parse grid url path with focused room": assert => { - const segments = parseUrlPath("/session/1/rooms/a,b,c/1"); + const path = createEmptyPath(); + const segments = parseUrlPath("/session/1/rooms/a,b,c/1", path, ""); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -265,7 +295,8 @@ export function tests() { assert.equal(segments[2].value, "b"); }, "parse empty grid url": assert => { - const segments = parseUrlPath("/session/1/rooms/"); + const path = createEmptyPath(); + const segments = parseUrlPath("/session/1/rooms/", path, ""); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -275,7 +306,8 @@ export function tests() { assert.equal(segments[2].value, 0); }, "parse empty grid url with focus": assert => { - const segments = parseUrlPath("/session/1/rooms//1"); + const path = createEmptyPath(); + const segments = parseUrlPath("/session/1/rooms//1", path, ""); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -285,13 +317,13 @@ export function tests() { assert.equal(segments[2].value, 1); }, "parse open-room action replacing the current focused room": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), new Segment("room", "b") ]); - const segments = parseUrlPath("/session/1/open-room/d", path); + const segments = parseUrlPath("/session/1/open-room/d", path, ""); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -301,13 +333,13 @@ export function tests() { assert.equal(segments[2].value, "d"); }, "parse open-room action changing focus to an existing room": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), new Segment("room", "b") ]); - const segments = parseUrlPath("/session/1/open-room/a", path); + const segments = parseUrlPath("/session/1/open-room/a", path, ""); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -317,7 +349,7 @@ export function tests() { assert.equal(segments[2].value, "a"); }, "parse open-room action changing focus to an existing room with details open": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), @@ -325,7 +357,7 @@ export function tests() { new Segment("right-panel", true), new Segment("details", true) ]); - const segments = parseUrlPath("/session/1/open-room/a", path); + const segments = parseUrlPath("/session/1/open-room/a", path, ""); assert.equal(segments.length, 5); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -339,7 +371,7 @@ export function tests() { assert.equal(segments[4].value, true); }, "open-room action should only copy over previous segments if there are no parts after open-room": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), @@ -347,7 +379,7 @@ export function tests() { new Segment("right-panel", true), new Segment("members", true) ]); - const segments = parseUrlPath("/session/1/open-room/a/member/foo", path); + const segments = parseUrlPath("/session/1/open-room/a/member/foo", path, ""); assert.equal(segments.length, 5); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -361,13 +393,13 @@ export function tests() { assert.equal(segments[4].value, "foo"); }, "parse open-room action setting a room in an empty tile": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), new Segment("empty-grid-tile", 4) ]); - const segments = parseUrlPath("/session/1/open-room/d", path); + const segments = parseUrlPath("/session/1/open-room/d", path, ""); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -377,82 +409,83 @@ export function tests() { assert.equal(segments[2].value, "d"); }, "parse session url path without id": assert => { - const segments = parseUrlPath("/session"); + const path = createEmptyPath(); + const segments = parseUrlPath("/session", path, ""); assert.equal(segments.length, 1); assert.equal(segments[0].type, "session"); assert.strictEqual(segments[0].value, true); }, "remove active room from grid path turns it into empty tile": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), new Segment("room", "b") ]); const newPath = removeRoomFromPath(path, "b"); - assert.equal(newPath.segments.length, 3); - assert.equal(newPath.segments[0].type, "session"); - assert.equal(newPath.segments[0].value, 1); - assert.equal(newPath.segments[1].type, "rooms"); - assert.deepEqual(newPath.segments[1].value, ["a", "", "c"]); - assert.equal(newPath.segments[2].type, "empty-grid-tile"); - assert.equal(newPath.segments[2].value, 1); + assert.equal(newPath?.segments.length, 3); + assert.equal(newPath?.segments[0].type, "session"); + assert.equal(newPath?.segments[0].value, 1); + assert.equal(newPath?.segments[1].type, "rooms"); + assert.deepEqual(newPath?.segments[1].value, ["a", "", "c"]); + assert.equal(newPath?.segments[2].type, "empty-grid-tile"); + assert.equal(newPath?.segments[2].value, 1); }, "remove inactive room from grid path": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", "c"]), new Segment("room", "b") ]); const newPath = removeRoomFromPath(path, "a"); - assert.equal(newPath.segments.length, 3); - assert.equal(newPath.segments[0].type, "session"); - assert.equal(newPath.segments[0].value, 1); - assert.equal(newPath.segments[1].type, "rooms"); - assert.deepEqual(newPath.segments[1].value, ["", "b", "c"]); - assert.equal(newPath.segments[2].type, "room"); - assert.equal(newPath.segments[2].value, "b"); + assert.equal(newPath?.segments.length, 3); + assert.equal(newPath?.segments[0].type, "session"); + assert.equal(newPath?.segments[0].value, 1); + assert.equal(newPath?.segments[1].type, "rooms"); + assert.deepEqual(newPath?.segments[1].value, ["", "b", "c"]); + assert.equal(newPath?.segments[2].type, "room"); + assert.equal(newPath?.segments[2].value, "b"); }, "remove inactive room from grid path with empty tile": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("rooms", ["a", "b", ""]), new Segment("empty-grid-tile", 3) ]); const newPath = removeRoomFromPath(path, "b"); - assert.equal(newPath.segments.length, 3); - assert.equal(newPath.segments[0].type, "session"); - assert.equal(newPath.segments[0].value, 1); - assert.equal(newPath.segments[1].type, "rooms"); - assert.deepEqual(newPath.segments[1].value, ["a", "", ""]); - assert.equal(newPath.segments[2].type, "empty-grid-tile"); - assert.equal(newPath.segments[2].value, 3); + assert.equal(newPath?.segments.length, 3); + assert.equal(newPath?.segments[0].type, "session"); + assert.equal(newPath?.segments[0].value, 1); + assert.equal(newPath?.segments[1].type, "rooms"); + assert.deepEqual(newPath?.segments[1].value, ["a", "", ""]); + assert.equal(newPath?.segments[2].type, "empty-grid-tile"); + assert.equal(newPath?.segments[2].value, 3); }, "remove active room": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("room", "b") ]); const newPath = removeRoomFromPath(path, "b"); - assert.equal(newPath.segments.length, 1); - assert.equal(newPath.segments[0].type, "session"); - assert.equal(newPath.segments[0].value, 1); + assert.equal(newPath?.segments.length, 1); + assert.equal(newPath?.segments[0].type, "session"); + assert.equal(newPath?.segments[0].value, 1); }, "remove inactive room doesn't do anything": assert => { - const nav = new Navigation(allowsChild); + const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([ new Segment("session", 1), new Segment("room", "b") ]); const newPath = removeRoomFromPath(path, "a"); - assert.equal(newPath.segments.length, 2); - assert.equal(newPath.segments[0].type, "session"); - assert.equal(newPath.segments[0].value, 1); - assert.equal(newPath.segments[1].type, "room"); - assert.equal(newPath.segments[1].value, "b"); + assert.equal(newPath?.segments.length, 2); + assert.equal(newPath?.segments[0].type, "session"); + assert.equal(newPath?.segments[0].value, 1); + assert.equal(newPath?.segments[1].type, "room"); + assert.equal(newPath?.segments[1].value, "b"); }, } diff --git a/src/domain/session/RoomGridViewModel.js b/src/domain/session/RoomGridViewModel.js index a7d19054..8e443e2d 100644 --- a/src/domain/session/RoomGridViewModel.js +++ b/src/domain/session/RoomGridViewModel.js @@ -15,7 +15,7 @@ limitations under the License. */ import {ViewModel} from "../ViewModel"; -import {addPanelIfNeeded} from "../navigation/index.js"; +import {addPanelIfNeeded} from "../navigation/index"; function dedupeSparse(roomIds) { return roomIds.map((id, idx) => { @@ -185,7 +185,7 @@ export class RoomGridViewModel extends ViewModel { } } -import {createNavigation} from "../navigation/index.js"; +import {createNavigation} from "../navigation/index"; import {ObservableValue} from "../../observable/ObservableValue"; export function tests() { diff --git a/src/domain/session/leftpanel/LeftPanelViewModel.js b/src/domain/session/leftpanel/LeftPanelViewModel.js index 2fd3ca7e..8c8d71a2 100644 --- a/src/domain/session/leftpanel/LeftPanelViewModel.js +++ b/src/domain/session/leftpanel/LeftPanelViewModel.js @@ -21,7 +21,7 @@ import {InviteTileViewModel} from "./InviteTileViewModel.js"; import {RoomBeingCreatedTileViewModel} from "./RoomBeingCreatedTileViewModel.js"; import {RoomFilter} from "./RoomFilter.js"; import {ApplyMap} from "../../../observable/map/ApplyMap.js"; -import {addPanelIfNeeded} from "../../navigation/index.js"; +import {addPanelIfNeeded} from "../../navigation/index"; export class LeftPanelViewModel extends ViewModel { constructor(options) { diff --git a/src/lib.ts b/src/lib.ts index 90bf597c..4d1f906f 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -18,7 +18,7 @@ export {Platform} from "./platform/web/Platform.js"; export {Client, LoadStatus} from "./matrix/Client.js"; export {RoomStatus} from "./matrix/room/common"; // export main view & view models -export {createNavigation, createRouter} from "./domain/navigation/index.js"; +export {createNavigation, createRouter} from "./domain/navigation/index"; export {RootViewModel} from "./domain/RootViewModel.js"; export {RootView} from "./platform/web/ui/RootView.js"; export {SessionViewModel} from "./domain/session/SessionViewModel.js"; diff --git a/src/platform/web/main.js b/src/platform/web/main.js index edc2cf14..83644456 100644 --- a/src/platform/web/main.js +++ b/src/platform/web/main.js @@ -17,7 +17,7 @@ limitations under the License. // import {RecordRequester, ReplayRequester} from "./matrix/net/request/replay"; import {RootViewModel} from "../../domain/RootViewModel.js"; -import {createNavigation, createRouter} from "../../domain/navigation/index.js"; +import {createNavigation, createRouter} from "../../domain/navigation/index"; // Don't use a default export here, as we use multiple entries during legacy build, // which does not support default exports, // see https://github.com/rollup/plugins/tree/master/packages/multi-entry From 92e8fc8ad3641ed1d3a955de604059259ae065b0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 11:43:35 +0530 Subject: [PATCH 06/29] Remove deprecated method --- src/domain/navigation/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 92cf41a1..143ae734 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -126,7 +126,7 @@ export function addPanelIfNeeded(navigation: Navigation, defaultSessionId: string): Segment[] { // substr(1) to take of initial / - const parts = urlPath.substr(1).split("/"); + const parts = urlPath.substring(1).split("/"); const iterator = parts[Symbol.iterator](); const segments: Segment[] = []; let next; From 646cbe0fff7bbfc9568fef5945e61b818c8193fe Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 11:48:02 +0530 Subject: [PATCH 07/29] Make all keys string --- src/domain/navigation/index.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 143ae734..4e840bf4 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -19,20 +19,20 @@ import {URLRouter} from "./URLRouter.js"; import type { Path } from "./Navigation"; type SegmentType = { - login: true; - session: string; - sso: string; - logout: true; - room: string; - rooms: string[]; - settings: true; + "login": true; + "session": string; + "sso": string; + "logout": true; + "room": string; + "rooms": string[]; + "settings": true; "create-room": true; "empty-grid-tile": number; - lightbox: string; + "lightbox": string; "right-panel": true; - details: true; - members: true; - member: string; + "details": true; + "members": true; + "member": string; }; export function createNavigation() { From bf2fb52691a47dad0c4a2ffd414cd5b5979a3646 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 11:48:20 +0530 Subject: [PATCH 08/29] Fix formatting --- src/domain/navigation/Navigation.ts | 2 +- src/domain/navigation/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index f0b29816..4a0e4969 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -215,7 +215,7 @@ class SegmentObservable extends BaseObservableValue { } } -export type { Path }; +export type {Path}; export function tests() { diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 4e840bf4..ac856f86 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -16,7 +16,7 @@ limitations under the License. import {Navigation, Segment} from "./Navigation"; import {URLRouter} from "./URLRouter.js"; -import type { Path } from "./Navigation"; +import type {Path} from "./Navigation"; type SegmentType = { "login": true; From d9bfca10e1085b7f8a8bab0ba6527273b201fc01 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 11:51:30 +0530 Subject: [PATCH 09/29] Type function --- src/domain/navigation/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index ac856f86..fb75b04d 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -35,7 +35,7 @@ type SegmentType = { "member": string; }; -export function createNavigation() { +export function createNavigation(): Navigation { return new Navigation(allowsChild); } From 4c3e0a6ff042b8298751f59a287a7efe8fdbf352 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 13:14:27 +0530 Subject: [PATCH 10/29] Convert URLRouter.js to typescript --- src/domain/navigation/Navigation.ts | 2 + .../navigation/{URLRouter.js => URLRouter.ts} | 64 +++++++++++-------- src/domain/navigation/index.ts | 6 +- 3 files changed, 44 insertions(+), 28 deletions(-) rename src/domain/navigation/{URLRouter.js => URLRouter.ts} (65%) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index 4a0e4969..5157f86c 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -19,6 +19,8 @@ import {BaseObservableValue, ObservableValue} from "../../observable/ObservableV type AllowsChild = (parent: Segment | undefined, child: Segment) => boolean; +export type OptionalValue = T extends true? [(undefined | true)?]: [T]; + export class Navigation { private readonly _allowsChild: AllowsChild; private _path: Path; diff --git a/src/domain/navigation/URLRouter.js b/src/domain/navigation/URLRouter.ts similarity index 65% rename from src/domain/navigation/URLRouter.js rename to src/domain/navigation/URLRouter.ts index 586eec8a..f865a1de 100644 --- a/src/domain/navigation/URLRouter.js +++ b/src/domain/navigation/URLRouter.ts @@ -14,19 +14,33 @@ See the License for the specific language governing permissions and limitations under the License. */ -export class URLRouter { - constructor({history, navigation, parseUrlPath, stringifyPath}) { +import type {History} from "../../platform/web/dom/History.js"; +import type {Navigation, Segment, Path, OptionalValue} from "./Navigation"; +import type {SubscriptionHandle} from "../../observable/BaseObservable"; +import type {SegmentType} from "./index"; + +type ParseURLPath = (urlPath: string, currentNavPath: Path, defaultSessionId: string | null) => Segment[]; +type StringifyPath = (path: Path) => string; + +export class URLRouter { + private readonly _history: History; + private readonly _navigation: Navigation; + private readonly _parseUrlPath: ParseURLPath; + private readonly _stringifyPath: StringifyPath; + private _subscription?: SubscriptionHandle; + private _pathSubscription?: SubscriptionHandle; + private _isApplyingUrl: boolean = false; + private _defaultSessionId: string | null; + + constructor(history: History, navigation: Navigation, parseUrlPath: ParseURLPath, stringifyPath: StringifyPath) { this._history = history; this._navigation = navigation; this._parseUrlPath = parseUrlPath; this._stringifyPath = stringifyPath; - this._subscription = null; - this._pathSubscription = null; - this._isApplyingUrl = false; this._defaultSessionId = this._getLastSessionId(); } - _getLastSessionId() { + _getLastSessionId(): string | null { const navPath = this._urlAsNavPath(this._history.getLastUrl() || ""); const sessionId = navPath.get("session")?.value; if (typeof sessionId === "string") { @@ -35,7 +49,7 @@ export class URLRouter { return null; } - attach() { + attach(): void { this._subscription = this._history.subscribe(url => this._applyUrl(url)); // subscribe to path before applying initial url // so redirects in _applyNavPathToHistory are reflected in url bar @@ -43,12 +57,12 @@ export class URLRouter { this._applyUrl(this._history.get()); } - dispose() { - this._subscription = this._subscription(); - this._pathSubscription = this._pathSubscription(); + dispose(): void { + if (this._subscription) { this._subscription = this._subscription(); } + if (this._pathSubscription) { this._pathSubscription = this._pathSubscription(); } } - _applyNavPathToHistory(path) { + _applyNavPathToHistory(path: Path): void { const url = this.urlForPath(path); if (url !== this._history.get()) { if (this._isApplyingUrl) { @@ -60,7 +74,7 @@ export class URLRouter { } } - _applyNavPathToNavigation(navPath) { + _applyNavPathToNavigation(navPath: Path): void { // this will cause _applyNavPathToHistory to be called, // so set a flag whether this request came from ourselves // (in which case it is a redirect if the url does not match the current one) @@ -69,21 +83,21 @@ export class URLRouter { this._isApplyingUrl = false; } - _urlAsNavPath(url) { + _urlAsNavPath(url: string): Path { const urlPath = this._history.urlAsPath(url); return this._navigation.pathFrom(this._parseUrlPath(urlPath, this._navigation.path, this._defaultSessionId)); } - _applyUrl(url) { + _applyUrl(url: string): void { const navPath = this._urlAsNavPath(url); this._applyNavPathToNavigation(navPath); } - pushUrl(url) { + pushUrl(url: string): void { this._history.pushUrl(url); } - tryRestoreLastUrl() { + tryRestoreLastUrl(): boolean { const lastNavPath = this._urlAsNavPath(this._history.getLastUrl() || ""); if (lastNavPath.segments.length !== 0) { this._applyNavPathToNavigation(lastNavPath); @@ -92,8 +106,8 @@ export class URLRouter { return false; } - urlForSegments(segments) { - let path = this._navigation.path; + urlForSegments(segments: Segment[]): string | undefined { + let path: Path | null = this._navigation.path; for (const segment of segments) { path = path.with(segment); if (!path) { @@ -103,29 +117,29 @@ export class URLRouter { return this.urlForPath(path); } - urlForSegment(type, value) { - return this.urlForSegments([this._navigation.segment(type, value)]); + urlForSegment(type: K, ...value: OptionalValue): string | undefined { + return this.urlForSegments([this._navigation.segment(type, ...value)]); } - urlUntilSegment(type) { + urlUntilSegment(type: keyof T): string { return this.urlForPath(this._navigation.path.until(type)); } - urlForPath(path) { + urlForPath(path: Path): string { return this._history.pathAsUrl(this._stringifyPath(path)); } - openRoomActionUrl(roomId) { + openRoomActionUrl(roomId: string) { // not a segment to navigation knowns about, so append it manually const urlPath = `${this._stringifyPath(this._navigation.path.until("session"))}/open-room/${roomId}`; return this._history.pathAsUrl(urlPath); } - createSSOCallbackURL() { + createSSOCallbackURL(): string { return window.location.origin; } - normalizeUrl() { + normalizeUrl(): void { // Remove any queryParameters from the URL // Gets rid of the loginToken after SSO this._history.replaceUrlSilently(`${window.location.origin}/${window.location.hash}`); diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index fb75b04d..386eac53 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -15,10 +15,10 @@ limitations under the License. */ import {Navigation, Segment} from "./Navigation"; -import {URLRouter} from "./URLRouter.js"; +import {URLRouter} from "./URLRouter"; import type {Path} from "./Navigation"; -type SegmentType = { +export type SegmentType = { "login": true; "session": string; "sso": string; @@ -124,7 +124,7 @@ export function addPanelIfNeeded(navigation: Navigation, defaultSessionId: string): Segment[] { +export function parseUrlPath(urlPath: string, currentNavPath: Path, defaultSessionId: string | null): Segment[] { // substr(1) to take of initial / const parts = urlPath.substring(1).split("/"); const iterator = parts[Symbol.iterator](); From 5d42f372f6b2d902332f5df54ec75d3dffafeccf Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 13:15:30 +0530 Subject: [PATCH 11/29] Pass as separate arguments to constructor --- src/domain/navigation/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 386eac53..13d2df84 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -40,7 +40,7 @@ export function createNavigation(): Navigation { } export function createRouter({history, navigation}) { - return new URLRouter({history, navigation, stringifyPath, parseUrlPath}); + return new URLRouter(history, navigation, parseUrlPath, stringifyPath); } function allowsChild(parent: {type: string, value: any} | undefined, child: {type: string, value: any}): boolean { From c14e4f3eed5ff4738f1376ed12a161b37d13f426 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 13:16:54 +0530 Subject: [PATCH 12/29] Use segment type --- src/domain/navigation/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 13d2df84..c90926cf 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -43,7 +43,7 @@ export function createRouter({history, navigation}) { return new URLRouter(history, navigation, parseUrlPath, stringifyPath); } -function allowsChild(parent: {type: string, value: any} | undefined, child: {type: string, value: any}): boolean { +function allowsChild(parent: Segment | undefined, child: Segment): boolean { const {type} = child; switch (parent?.type) { case undefined: From f28dfc6964e29c8e5b6db5cd7f47dc0135ac67cf Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 13:22:37 +0530 Subject: [PATCH 13/29] Type createRouter function --- src/domain/navigation/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index c90926cf..740ebf9d 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -39,7 +39,7 @@ export function createNavigation(): Navigation { return new Navigation(allowsChild); } -export function createRouter({history, navigation}) { +export function createRouter({history, navigation}: {history: History, navigation: Navigation}): URLRouter { return new URLRouter(history, navigation, parseUrlPath, stringifyPath); } From 76d04ee27783ee3316cdd6727d865fbcd5e8e340 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 13:26:59 +0530 Subject: [PATCH 14/29] Make defaultSessionId optional --- src/domain/navigation/URLRouter.ts | 8 ++++---- src/domain/navigation/index.ts | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/domain/navigation/URLRouter.ts b/src/domain/navigation/URLRouter.ts index f865a1de..06a67c25 100644 --- a/src/domain/navigation/URLRouter.ts +++ b/src/domain/navigation/URLRouter.ts @@ -19,7 +19,7 @@ import type {Navigation, Segment, Path, OptionalValue} from "./Navigation"; import type {SubscriptionHandle} from "../../observable/BaseObservable"; import type {SegmentType} from "./index"; -type ParseURLPath = (urlPath: string, currentNavPath: Path, defaultSessionId: string | null) => Segment[]; +type ParseURLPath = (urlPath: string, currentNavPath: Path, defaultSessionId?: string) => Segment[]; type StringifyPath = (path: Path) => string; export class URLRouter { @@ -30,7 +30,7 @@ export class URLRouter { private _subscription?: SubscriptionHandle; private _pathSubscription?: SubscriptionHandle; private _isApplyingUrl: boolean = false; - private _defaultSessionId: string | null; + private _defaultSessionId?: string; constructor(history: History, navigation: Navigation, parseUrlPath: ParseURLPath, stringifyPath: StringifyPath) { this._history = history; @@ -40,13 +40,13 @@ export class URLRouter { this._defaultSessionId = this._getLastSessionId(); } - _getLastSessionId(): string | null { + _getLastSessionId(): string | undefined { const navPath = this._urlAsNavPath(this._history.getLastUrl() || ""); const sessionId = navPath.get("session")?.value; if (typeof sessionId === "string") { return sessionId; } - return null; + return undefined; } attach(): void { diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 740ebf9d..bcce8936 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -124,7 +124,7 @@ export function addPanelIfNeeded(navigation: Navigation, defaultSessionId: string | null): Segment[] { +export function parseUrlPath(urlPath: string, currentNavPath: Path, defaultSessionId?: string): Segment[] { // substr(1) to take of initial / const parts = urlPath.substring(1).split("/"); const iterator = parts[Symbol.iterator](); @@ -267,14 +267,14 @@ export function tests() { }, "Parse loginToken query parameter into SSO segment": assert => { const path = createEmptyPath(); - const segments = parseUrlPath("?loginToken=a1232aSD123", path, ""); + const segments = parseUrlPath("?loginToken=a1232aSD123", path); assert.equal(segments.length, 1); assert.equal(segments[0].type, "sso"); assert.equal(segments[0].value, "a1232aSD123"); }, "parse grid url path with focused empty tile": assert => { const path = createEmptyPath(); - const segments = parseUrlPath("/session/1/rooms/a,b,c/3", path, ""); + const segments = parseUrlPath("/session/1/rooms/a,b,c/3", path); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -285,7 +285,7 @@ export function tests() { }, "parse grid url path with focused room": assert => { const path = createEmptyPath(); - const segments = parseUrlPath("/session/1/rooms/a,b,c/1", path, ""); + const segments = parseUrlPath("/session/1/rooms/a,b,c/1", path); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -296,7 +296,7 @@ export function tests() { }, "parse empty grid url": assert => { const path = createEmptyPath(); - const segments = parseUrlPath("/session/1/rooms/", path, ""); + const segments = parseUrlPath("/session/1/rooms/", path); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -307,7 +307,7 @@ export function tests() { }, "parse empty grid url with focus": assert => { const path = createEmptyPath(); - const segments = parseUrlPath("/session/1/rooms//1", path, ""); + const segments = parseUrlPath("/session/1/rooms//1", path); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -323,7 +323,7 @@ export function tests() { new Segment("rooms", ["a", "b", "c"]), new Segment("room", "b") ]); - const segments = parseUrlPath("/session/1/open-room/d", path, ""); + const segments = parseUrlPath("/session/1/open-room/d", path); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -339,7 +339,7 @@ export function tests() { new Segment("rooms", ["a", "b", "c"]), new Segment("room", "b") ]); - const segments = parseUrlPath("/session/1/open-room/a", path, ""); + const segments = parseUrlPath("/session/1/open-room/a", path); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -357,7 +357,7 @@ export function tests() { new Segment("right-panel", true), new Segment("details", true) ]); - const segments = parseUrlPath("/session/1/open-room/a", path, ""); + const segments = parseUrlPath("/session/1/open-room/a", path); assert.equal(segments.length, 5); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -379,7 +379,7 @@ export function tests() { new Segment("right-panel", true), new Segment("members", true) ]); - const segments = parseUrlPath("/session/1/open-room/a/member/foo", path, ""); + const segments = parseUrlPath("/session/1/open-room/a/member/foo", path); assert.equal(segments.length, 5); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -399,7 +399,7 @@ export function tests() { new Segment("rooms", ["a", "b", "c"]), new Segment("empty-grid-tile", 4) ]); - const segments = parseUrlPath("/session/1/open-room/d", path, ""); + const segments = parseUrlPath("/session/1/open-room/d", path); assert.equal(segments.length, 3); assert.equal(segments[0].type, "session"); assert.equal(segments[0].value, "1"); @@ -410,7 +410,7 @@ export function tests() { }, "parse session url path without id": assert => { const path = createEmptyPath(); - const segments = parseUrlPath("/session", path, ""); + const segments = parseUrlPath("/session", path); assert.equal(segments.length, 1); assert.equal(segments[0].type, "session"); assert.strictEqual(segments[0].value, true); From 09bc0f1b603323f8dbfce2a7c832657842e5acb1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 13:30:37 +0530 Subject: [PATCH 15/29] Extract complex type as type alias --- src/domain/navigation/Navigation.ts | 6 +++--- src/domain/navigation/index.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index 5157f86c..6cde56c2 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -41,7 +41,7 @@ export class Navigation { return this._path; } - push(type: K, ...value: T[K] extends true? [(undefined | true)?]: [T[K]]): void { + push(type: K, ...value: OptionalValue): void { const newPath = this.path.with(new Segment(type, ...value)); if (newPath) { this.applyPath(newPath); @@ -93,7 +93,7 @@ export class Navigation { return new Path(segments, this._allowsChild); } - segment(type: K, ...value: T[K] extends true? [(undefined | true)?]: [T[K]]): Segment { + segment(type: K, ...value: OptionalValue): Segment { return new Segment(type, ...value); } } @@ -119,7 +119,7 @@ function segmentValueEqual(a?: T[keyof T], b?: T[keyof T]): boolean { export class Segment { public value: T[K]; - constructor(public type: K, ...value: T[K] extends true? [(undefined | true)?]: [T[K]]) { + constructor(public type: K, ...value: OptionalValue) { this.value = (value[0] === undefined ? true : value[0]) as unknown as T[K]; } } diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index bcce8936..06920e61 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -16,7 +16,7 @@ limitations under the License. import {Navigation, Segment} from "./Navigation"; import {URLRouter} from "./URLRouter"; -import type {Path} from "./Navigation"; +import type {Path, OptionalValue} from "./Navigation"; export type SegmentType = { "login": true; @@ -107,7 +107,7 @@ function roomsSegmentWithRoom(rooms: Segment, roomId: stri } // todo-self: verify code change here is okay -function pushRightPanelSegment(array: Segment[], segment: T, ...value: SegmentType[T] extends true? [(undefined | true)?]: [SegmentType[T]]) { +function pushRightPanelSegment(array: Segment[], segment: T, ...value: OptionalValue) { array.push(new Segment("right-panel")); array.push(new Segment(segment, ...value)); } From e7f4ce61751769f91ff388dae7f962d779c398cb Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 16:24:12 +0530 Subject: [PATCH 16/29] Mark methods as private --- src/domain/navigation/URLRouter.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/domain/navigation/URLRouter.ts b/src/domain/navigation/URLRouter.ts index 06a67c25..f250ab74 100644 --- a/src/domain/navigation/URLRouter.ts +++ b/src/domain/navigation/URLRouter.ts @@ -40,7 +40,7 @@ export class URLRouter { this._defaultSessionId = this._getLastSessionId(); } - _getLastSessionId(): string | undefined { + private _getLastSessionId(): string | undefined { const navPath = this._urlAsNavPath(this._history.getLastUrl() || ""); const sessionId = navPath.get("session")?.value; if (typeof sessionId === "string") { @@ -62,7 +62,7 @@ export class URLRouter { if (this._pathSubscription) { this._pathSubscription = this._pathSubscription(); } } - _applyNavPathToHistory(path: Path): void { + private _applyNavPathToHistory(path: Path): void { const url = this.urlForPath(path); if (url !== this._history.get()) { if (this._isApplyingUrl) { @@ -74,7 +74,7 @@ export class URLRouter { } } - _applyNavPathToNavigation(navPath: Path): void { + private _applyNavPathToNavigation(navPath: Path): void { // this will cause _applyNavPathToHistory to be called, // so set a flag whether this request came from ourselves // (in which case it is a redirect if the url does not match the current one) @@ -83,12 +83,12 @@ export class URLRouter { this._isApplyingUrl = false; } - _urlAsNavPath(url: string): Path { + private _urlAsNavPath(url: string): Path { const urlPath = this._history.urlAsPath(url); return this._navigation.pathFrom(this._parseUrlPath(urlPath, this._navigation.path, this._defaultSessionId)); } - _applyUrl(url: string): void { + private _applyUrl(url: string): void { const navPath = this._urlAsNavPath(url); this._applyNavPathToNavigation(navPath); } From 5be00f051f566686b7117c6e184157947b08f3f5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 16:39:33 +0530 Subject: [PATCH 17/29] Use subtype instead of whole SegmentType --- src/domain/navigation/URLRouter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/domain/navigation/URLRouter.ts b/src/domain/navigation/URLRouter.ts index f250ab74..2d06ca5f 100644 --- a/src/domain/navigation/URLRouter.ts +++ b/src/domain/navigation/URLRouter.ts @@ -17,12 +17,11 @@ limitations under the License. import type {History} from "../../platform/web/dom/History.js"; import type {Navigation, Segment, Path, OptionalValue} from "./Navigation"; import type {SubscriptionHandle} from "../../observable/BaseObservable"; -import type {SegmentType} from "./index"; type ParseURLPath = (urlPath: string, currentNavPath: Path, defaultSessionId?: string) => Segment[]; type StringifyPath = (path: Path) => string; -export class URLRouter { +export class URLRouter { private readonly _history: History; private readonly _navigation: Navigation; private readonly _parseUrlPath: ParseURLPath; From 4ae3a5bf7a05e8805de85100d8e2fb86fb6ec696 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 16:44:27 +0530 Subject: [PATCH 18/29] Use undefined instead of null --- src/domain/navigation/Navigation.ts | 8 ++++---- src/domain/navigation/URLRouter.ts | 2 +- src/domain/navigation/index.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index 6cde56c2..d6ffa3c5 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -137,7 +137,7 @@ class Path { return new Path(this._segments.slice(), this._allowsChild); } - with(segment: Segment): Path | null { + with(segment: Segment): Path | undefined { let index = this._segments.length - 1; do { if (this._allowsChild(this._segments[index], segment)) { @@ -149,7 +149,7 @@ class Path { index -= 1; } while(index >= -1); // allow -1 as well so we check if the segment is allowed as root - return null; + return undefined; } until(type: keyof T): Path { @@ -164,7 +164,7 @@ class Path { return this._segments.find(s => s.type === type); } - replace(segment: Segment): Path | null { + replace(segment: Segment): Path | undefined { const index = this._segments.findIndex(s => s.type === segment.type); if (index !== -1) { const parent = this._segments[index - 1]; @@ -177,7 +177,7 @@ class Path { } } } - return null; + return undefined; } get segments(): Segment[] { diff --git a/src/domain/navigation/URLRouter.ts b/src/domain/navigation/URLRouter.ts index 2d06ca5f..31dffe58 100644 --- a/src/domain/navigation/URLRouter.ts +++ b/src/domain/navigation/URLRouter.ts @@ -106,7 +106,7 @@ export class URLRouter { } urlForSegments(segments: Segment[]): string | undefined { - let path: Path | null = this._navigation.path; + let path: Path | undefined = this._navigation.path; for (const segment of segments) { path = path.with(segment); if (!path) { diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 06920e61..f6533580 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -63,8 +63,8 @@ function allowsChild(parent: Segment | undefined, child: Segment, roomId: string): Path | null { - let newPath: Path | null = path; +export function removeRoomFromPath(path: Path, roomId: string): Path | undefined { + let newPath: Path | undefined = path; const rooms = newPath.get("rooms"); let roomIdGridIndex = -1; // first delete from rooms segment From 4fd1918202845a9e38d443d48c98ff0306778679 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 16:47:48 +0530 Subject: [PATCH 19/29] Remove comment --- src/domain/navigation/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index f6533580..3715d817 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -106,7 +106,6 @@ function roomsSegmentWithRoom(rooms: Segment, roomId: stri } } -// todo-self: verify code change here is okay function pushRightPanelSegment(array: Segment[], segment: T, ...value: OptionalValue) { array.push(new Segment("right-panel")); array.push(new Segment(segment, ...value)); From 7a24059337056da7a7aa2c5ac21ead6ed52a7e23 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 16:48:15 +0530 Subject: [PATCH 20/29] Remove empty line --- src/domain/navigation/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 3715d817..5f084d0f 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -224,7 +224,6 @@ export function stringifyPath(path: Path): string { } export function tests() { - function createEmptyPath() { const nav: Navigation = new Navigation(allowsChild); const path = nav.pathFrom([]); From 52f0690c702f51de30559bbb59c22c30b5a20dc3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 16:53:52 +0530 Subject: [PATCH 21/29] Add return type --- src/domain/navigation/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 5f084d0f..0c36e21b 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -106,7 +106,7 @@ function roomsSegmentWithRoom(rooms: Segment, roomId: stri } } -function pushRightPanelSegment(array: Segment[], segment: T, ...value: OptionalValue) { +function pushRightPanelSegment(array: Segment[], segment: T, ...value: OptionalValue): void { array.push(new Segment("right-panel")); array.push(new Segment(segment, ...value)); } From 263948faa384d132637d567d4c659f5ae1009f1a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 16:58:13 +0530 Subject: [PATCH 22/29] Remove unwanted export --- src/domain/navigation/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index 0c36e21b..b8652f18 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -18,7 +18,7 @@ import {Navigation, Segment} from "./Navigation"; import {URLRouter} from "./URLRouter"; import type {Path, OptionalValue} from "./Navigation"; -export type SegmentType = { +type SegmentType = { "login": true; "session": string; "sso": string; From f49d580d49afbbbb60e6d6186b3b1728902b175b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 20:17:51 +0530 Subject: [PATCH 23/29] WIP --- src/domain/ViewModel.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 0bc52f6e..743815ee 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -29,15 +29,15 @@ import type {ILogger} from "../logging/types"; import type {Navigation} from "./navigation/Navigation"; import type {URLRouter} from "./navigation/URLRouter"; -export type Options = { +type Options = { platform: Platform logger: ILogger - urlCreator: URLRouter - navigation: Navigation + urlCreator: URLRouter + navigation: Navigation emitChange?: (params: any) => void } -export class ViewModel extends EventEmitter<{change: never}> { +export class ViewModel = Options> extends EventEmitter<{change: never}> { private disposables?: Disposables; private _isDisposed = false; private _options: Readonly; @@ -47,7 +47,7 @@ export class ViewModel extends EventEmitter<{change this._options = options; } - childOptions(explicitOptions: T): T & Options { + childOptions(explicitOptions: T): T & Options { return Object.assign({}, this._options, explicitOptions); } @@ -135,11 +135,11 @@ export class ViewModel extends EventEmitter<{change return this.platform.logger; } - get urlCreator(): URLRouter { + get urlCreator(): URLRouter { return this._options.urlCreator; } - get navigation(): Navigation { + get navigation(): Navigation { return this._options.navigation; } } From 9300347e9bd2b73dd667d9ff42a6ff32b423761c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 22 Feb 2022 22:23:52 +0530 Subject: [PATCH 24/29] Give defaultt type --- src/domain/ViewModel.ts | 3 ++- src/domain/navigation/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 743815ee..5bec39e8 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -27,6 +27,7 @@ import type {Platform} from "../platform/web/Platform"; import type {Clock} from "../platform/web/dom/Clock"; import type {ILogger} from "../logging/types"; import type {Navigation} from "./navigation/Navigation"; +import type {SegmentType} from "./navigation/index"; import type {URLRouter} from "./navigation/URLRouter"; type Options = { @@ -37,7 +38,7 @@ type Options = { emitChange?: (params: any) => void } -export class ViewModel = Options> extends EventEmitter<{change: never}> { +export class ViewModel = Options> extends EventEmitter<{change: never}> { private disposables?: Disposables; private _isDisposed = false; private _options: Readonly; diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index b8652f18..f739b668 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -18,7 +18,7 @@ import {Navigation, Segment} from "./Navigation"; import {URLRouter} from "./URLRouter"; import type {Path, OptionalValue} from "./Navigation"; -type SegmentType = { +export type SegmentType = { "login": true; "session": string; "sso": string; @@ -124,7 +124,7 @@ export function addPanelIfNeeded(navigation: Navigation, defaultSessionId?: string): Segment[] { - // substr(1) to take of initial / + // substring(1) to take of initial / const parts = urlPath.substring(1).split("/"); const iterator = parts[Symbol.iterator](); const segments: Segment[] = []; From a336623f3ac8cbf8bcf0bb86df8f6e1a563571ea Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 2 Mar 2022 17:57:03 +0530 Subject: [PATCH 25/29] Generic parameter should extend object --- src/domain/navigation/Navigation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index d6ffa3c5..c786d7a7 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -21,7 +21,7 @@ type AllowsChild = (parent: Segment | undefined, child: Segment) => boo export type OptionalValue = T extends true? [(undefined | true)?]: [T]; -export class Navigation { +export class Navigation { private readonly _allowsChild: AllowsChild; private _path: Path; private readonly _observables: Map> = new Map(); @@ -189,7 +189,7 @@ class Path { * custom observable so it always returns what is in navigation.path, even if we haven't emitted the change yet. * This ensures that observers of a segment can also read the most recent value of other segments. */ -class SegmentObservable extends BaseObservableValue { +class SegmentObservable extends BaseObservableValue { private readonly _navigation: Navigation; private _type: keyof T; private _lastSetValue?: T[keyof T]; From ec1cc89cf9555c6f18bee31d1b084266869ee6eb Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 3 Mar 2022 20:17:15 +0530 Subject: [PATCH 26/29] Make URLRouter in options conditional on generic URLRouter can be passed in option to vm only if the SegmentType used contains session. ViewModel.urlCreator returns undefined when used with a SegmentType that lacks session. --- src/domain/ViewModel.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 5bec39e8..7442d921 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -30,15 +30,17 @@ import type {Navigation} from "./navigation/Navigation"; import type {SegmentType} from "./navigation/index"; import type {URLRouter} from "./navigation/URLRouter"; -type Options = { +type OptionsWithoutUrlCreator = { platform: Platform logger: ILogger - urlCreator: URLRouter navigation: Navigation emitChange?: (params: any) => void } +type OptionsWithUrlCreator = OptionsWithoutUrlCreator & {urlCreator: URLRouter}; -export class ViewModel = Options> extends EventEmitter<{change: never}> { +type Options = N extends { session: string } ? OptionsWithUrlCreator : OptionsWithoutUrlCreator; + +export class ViewModel = Options> extends EventEmitter<{change: never}> { private disposables?: Disposables; private _isDisposed = false; private _options: Readonly; @@ -136,11 +138,13 @@ export class ViewModel { - return this._options.urlCreator; + get urlCreator(): N extends { session: string }? URLRouter: undefined { + // typescript needs a little help here + return (this._options as unknown as {urlCreator: any}).urlCreator; } get navigation(): Navigation { - return this._options.navigation; + // typescript needs a little help here + return this._options.navigation as unknown as Navigation; } } From fc873757d80c264b9f01995029bfcda093f69ed7 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 27 May 2022 22:38:53 +0530 Subject: [PATCH 27/29] WIP --- src/domain/LogoutViewModel.ts | 5 +++-- src/domain/ViewModel.ts | 20 +++++++++----------- src/domain/navigation/URLRouter.ts | 18 ++++++++++++++++-- src/domain/navigation/index.ts | 2 +- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/domain/LogoutViewModel.ts b/src/domain/LogoutViewModel.ts index 3edfcad5..9a39f601 100644 --- a/src/domain/LogoutViewModel.ts +++ b/src/domain/LogoutViewModel.ts @@ -16,10 +16,11 @@ limitations under the License. import {Options, ViewModel} from "./ViewModel"; import {Client} from "../matrix/Client.js"; +import {SegmentType} from "./navigation/index"; type LogoutOptions = { sessionId: string; } & Options; -export class LogoutViewModel extends ViewModel { +export class LogoutViewModel extends ViewModel { private _sessionId: string; private _busy: boolean; private _showConfirm: boolean; @@ -41,7 +42,7 @@ export class LogoutViewModel extends ViewModel { return this._busy; } - get cancelUrl(): string { + get cancelUrl(): string | undefined { return this.urlCreator.urlForSegment("session", true); } diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 7442d921..64db4266 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -28,17 +28,16 @@ import type {Clock} from "../platform/web/dom/Clock"; import type {ILogger} from "../logging/types"; import type {Navigation} from "./navigation/Navigation"; import type {SegmentType} from "./navigation/index"; -import type {URLRouter} from "./navigation/URLRouter"; +import type {IURLRouter} from "./navigation/URLRouter"; -type OptionsWithoutUrlCreator = { - platform: Platform - logger: ILogger - navigation: Navigation - emitChange?: (params: any) => void +export type Options = { + platform: Platform; + logger: ILogger; + urlCreator: IURLRouter; + navigation: Navigation; + emitChange?: (params: any) => void; } -type OptionsWithUrlCreator = OptionsWithoutUrlCreator & {urlCreator: URLRouter}; -type Options = N extends { session: string } ? OptionsWithUrlCreator : OptionsWithoutUrlCreator; export class ViewModel = Options> extends EventEmitter<{change: never}> { private disposables?: Disposables; @@ -138,9 +137,8 @@ export class ViewModel = Op return this.platform.logger; } - get urlCreator(): N extends { session: string }? URLRouter: undefined { - // typescript needs a little help here - return (this._options as unknown as {urlCreator: any}).urlCreator; + get urlCreator(): IURLRouter { + return this._options.urlCreator; } get navigation(): Navigation { diff --git a/src/domain/navigation/URLRouter.ts b/src/domain/navigation/URLRouter.ts index 31dffe58..923c9b43 100644 --- a/src/domain/navigation/URLRouter.ts +++ b/src/domain/navigation/URLRouter.ts @@ -21,7 +21,21 @@ import type {SubscriptionHandle} from "../../observable/BaseObservable"; type ParseURLPath = (urlPath: string, currentNavPath: Path, defaultSessionId?: string) => Segment[]; type StringifyPath = (path: Path) => string; -export class URLRouter { +export interface IURLRouter { + attach(): void; + dispose(): void; + pushUrl(url: string): void; + tryRestoreLastUrl(): boolean; + urlForSegments(segments: Segment[]): string | undefined; + urlForSegment(type: K, ...value: OptionalValue): string | undefined; + urlUntilSegment(type: keyof T): string; + urlForPath(path: Path): string; + openRoomActionUrl(roomId: string): string; + createSSOCallbackURL(): string; + normalizeUrl(): void; +} + +export class URLRouter implements IURLRouter { private readonly _history: History; private readonly _navigation: Navigation; private readonly _parseUrlPath: ParseURLPath; @@ -128,7 +142,7 @@ export class URLRouter { return this._history.pathAsUrl(this._stringifyPath(path)); } - openRoomActionUrl(roomId: string) { + openRoomActionUrl(roomId: string): string { // not a segment to navigation knowns about, so append it manually const urlPath = `${this._stringifyPath(this._navigation.path.until("session"))}/open-room/${roomId}`; return this._history.pathAsUrl(urlPath); diff --git a/src/domain/navigation/index.ts b/src/domain/navigation/index.ts index f739b668..afba0d86 100644 --- a/src/domain/navigation/index.ts +++ b/src/domain/navigation/index.ts @@ -20,7 +20,7 @@ import type {Path, OptionalValue} from "./Navigation"; export type SegmentType = { "login": true; - "session": string; + "session": string | boolean; "sso": string; "logout": true; "room": string; From ba647d012dc0c778df81e4e152e2be8736297c04 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 29 May 2022 20:38:14 +0530 Subject: [PATCH 28/29] Fix type in observeNavigation --- src/domain/ViewModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 64db4266..f63f569d 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -60,9 +60,9 @@ export class ViewModel = Op return this._options[name]; } - observeNavigation(type: string, onChange: (value: string | true | undefined, type: string) => void) { + observeNavigation(type: T, onChange: (value: N[T], type: T) => void) { const segmentObservable = this.navigation.observe(type); - const unsubscribe = segmentObservable.subscribe((value: string | true | undefined) => { + const unsubscribe = segmentObservable.subscribe((value: N[T]) => { onChange(value, type); }) this.track(unsubscribe); From d31f127982a60ac2bd2df611524d7caca8629664 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 7 Jun 2022 13:28:56 +0530 Subject: [PATCH 29/29] Add explaining comment --- src/domain/navigation/Navigation.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/domain/navigation/Navigation.ts b/src/domain/navigation/Navigation.ts index c786d7a7..f5039732 100644 --- a/src/domain/navigation/Navigation.ts +++ b/src/domain/navigation/Navigation.ts @@ -19,6 +19,16 @@ import {BaseObservableValue, ObservableValue} from "../../observable/ObservableV type AllowsChild = (parent: Segment | undefined, child: Segment) => boolean; +/** + * OptionalValue is basically stating that if SegmentType[type] = true: + * - Allow this type to be optional + * - Give it a default value of undefined + * - Also allow it to be true + * This lets us do: + * const s: Segment = new Segment("create-room"); + * instead of + * const s: Segment = new Segment("create-room", undefined); + */ export type OptionalValue = T extends true? [(undefined | true)?]: [T]; export class Navigation {