mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-24 20:14:53 +01:00
db3fd3d1ae
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
433 lines
18 KiB
JavaScript
433 lines
18 KiB
JavaScript
/*
|
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
import {Navigation, Segment} from "./Navigation.js";
|
|
import {URLRouter} from "./URLRouter.js";
|
|
|
|
export function createNavigation() {
|
|
return new Navigation(allowsChild);
|
|
}
|
|
|
|
export function createRouter({history, navigation}) {
|
|
return new URLRouter({history, navigation, stringifyPath, parseUrlPath});
|
|
}
|
|
|
|
function allowsChild(parent, child) {
|
|
const {type} = child;
|
|
switch (parent?.type) {
|
|
case undefined:
|
|
// allowed root segments
|
|
return type === "login" || type === "session" || type === "sso";
|
|
case "session":
|
|
return type === "room" || type === "rooms" || type === "settings";
|
|
case "rooms":
|
|
// downside of the approach: both of these will control which tile is selected
|
|
return type === "room" || type === "empty-grid-tile";
|
|
case "room":
|
|
return type === "lightbox" || type === "right-panel";
|
|
case "right-panel":
|
|
return type === "details"|| type === "members" || type === "member";
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function removeRoomFromPath(path, roomId) {
|
|
const rooms = path.get("rooms");
|
|
let roomIdGridIndex = -1;
|
|
// first delete from rooms segment
|
|
if (rooms) {
|
|
roomIdGridIndex = rooms.value.indexOf(roomId);
|
|
if (roomIdGridIndex !== -1) {
|
|
const idsWithoutRoom = rooms.value.slice();
|
|
idsWithoutRoom[roomIdGridIndex] = "";
|
|
path = path.replace(new Segment("rooms", idsWithoutRoom));
|
|
}
|
|
}
|
|
const room = path.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));
|
|
} else {
|
|
path = path.until("session");
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
function roomsSegmentWithRoom(rooms, roomId, path) {
|
|
if(!rooms.value.includes(roomId)) {
|
|
const emptyGridTile = path.get("empty-grid-tile");
|
|
const oldRoom = path.get("room");
|
|
let index = 0;
|
|
if (emptyGridTile) {
|
|
index = emptyGridTile.value;
|
|
} else if (oldRoom) {
|
|
index = rooms.value.indexOf(oldRoom.value);
|
|
}
|
|
const roomIds = rooms.value.slice();
|
|
roomIds[index] = roomId;
|
|
return new Segment("rooms", roomIds);
|
|
} else {
|
|
return rooms;
|
|
}
|
|
}
|
|
|
|
function pushRightPanelSegment(array, segment, value = true) {
|
|
array.push(new Segment("right-panel"));
|
|
array.push(new Segment(segment, value));
|
|
}
|
|
|
|
export function addPanelIfNeeded(navigation, 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]);
|
|
}
|
|
return _path;
|
|
}
|
|
|
|
export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
|
|
// substr(1) to take of initial /
|
|
const parts = urlPath.substr(1).split("/");
|
|
const iterator = parts[Symbol.iterator]();
|
|
const segments = [];
|
|
let next;
|
|
while (!(next = iterator.next()).done) {
|
|
const type = next.value;
|
|
if (type === "rooms") {
|
|
const roomsValue = iterator.next().value;
|
|
if (roomsValue === undefined) { break; }
|
|
const roomIds = roomsValue.split(",");
|
|
segments.push(new Segment(type, roomIds));
|
|
const selectedIndex = parseInt(iterator.next().value || "0", 10);
|
|
const roomId = roomIds[selectedIndex];
|
|
if (roomId) {
|
|
segments.push(new Segment("room", roomId));
|
|
} else {
|
|
segments.push(new Segment("empty-grid-tile", selectedIndex));
|
|
}
|
|
} else if (type === "open-room") {
|
|
const roomId = iterator.next().value;
|
|
if (!roomId) { break; }
|
|
const rooms = currentNavPath.get("rooms");
|
|
if (rooms) {
|
|
segments.push(roomsSegmentWithRoom(rooms, roomId, currentNavPath));
|
|
}
|
|
segments.push(new Segment("room", roomId));
|
|
// Add right-panel segments from previous path
|
|
const previousSegments = currentNavPath.segments;
|
|
const i = previousSegments.findIndex(s => s.type === "right-panel");
|
|
if (i !== -1) {
|
|
segments.push(...previousSegments.slice(i));
|
|
}
|
|
} else if (type === "last-session") {
|
|
let sessionSegment = currentNavPath.get("session");
|
|
if (typeof sessionSegment?.value !== "string" && defaultSessionId) {
|
|
sessionSegment = new Segment("session", defaultSessionId);
|
|
}
|
|
if (sessionSegment) {
|
|
segments.push(sessionSegment);
|
|
}
|
|
} else if (type === "details" || type === "members") {
|
|
pushRightPanelSegment(segments, type);
|
|
} else if (type === "member") {
|
|
const userId = iterator.next().value;
|
|
if (!userId) { break; }
|
|
pushRightPanelSegment(segments, type, userId);
|
|
} else if (type.includes("loginToken")) {
|
|
// Special case for SSO-login with query parameter loginToken=<token>
|
|
const loginToken = type.split("=").pop();
|
|
segments.push(new Segment("sso", loginToken));
|
|
} else {
|
|
// might be undefined, which will be turned into true by Segment
|
|
const value = iterator.next().value;
|
|
segments.push(new Segment(type, value));
|
|
}
|
|
}
|
|
return segments;
|
|
}
|
|
|
|
export function stringifyPath(path) {
|
|
let urlPath = "";
|
|
let prevSegment;
|
|
for (const segment of path.segments) {
|
|
switch (segment.type) {
|
|
case "rooms":
|
|
urlPath += `/rooms/${segment.value.join(",")}`;
|
|
break;
|
|
case "empty-grid-tile":
|
|
urlPath += `/${segment.value}`;
|
|
break;
|
|
case "room":
|
|
if (prevSegment?.type === "rooms") {
|
|
const index = prevSegment.value.indexOf(segment.value);
|
|
urlPath += `/${index}`;
|
|
} else {
|
|
urlPath += `/${segment.type}/${segment.value}`;
|
|
}
|
|
break;
|
|
case "right-panel":
|
|
case "sso":
|
|
// Do not put these segments in URL
|
|
continue;
|
|
default:
|
|
urlPath += `/${segment.type}`;
|
|
if (segment.value && segment.value !== true) {
|
|
urlPath += `/${segment.value}`;
|
|
}
|
|
}
|
|
prevSegment = segment;
|
|
}
|
|
return urlPath;
|
|
}
|
|
|
|
export function tests() {
|
|
return {
|
|
"stringify grid url with focused empty tile": assert => {
|
|
const nav = new Navigation(allowsChild);
|
|
const path = nav.pathFrom([
|
|
new Segment("session", 1),
|
|
new Segment("rooms", ["a", "b", "c"]),
|
|
new Segment("empty-grid-tile", 3)
|
|
]);
|
|
const urlPath = stringifyPath(path);
|
|
assert.equal(urlPath, "/session/1/rooms/a,b,c/3");
|
|
},
|
|
"stringify grid url with focused room": assert => {
|
|
const nav = new Navigation(allowsChild);
|
|
const path = nav.pathFrom([
|
|
new Segment("session", 1),
|
|
new Segment("rooms", ["a", "b", "c"]),
|
|
new Segment("room", "b")
|
|
]);
|
|
const urlPath = stringifyPath(path);
|
|
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 path = nav.pathFrom([
|
|
new Segment("session", 1),
|
|
new Segment("rooms", ["a", "b", "c"]),
|
|
new Segment("room", "b"),
|
|
new Segment("right-panel"),
|
|
new Segment("details")
|
|
]);
|
|
const urlPath = stringifyPath(path);
|
|
assert.equal(urlPath, "/session/1/rooms/a,b,c/1/details");
|
|
},
|
|
"Parse loginToken query parameter into SSO segment": assert => {
|
|
const segments = parseUrlPath("?loginToken=a1232aSD123");
|
|
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");
|
|
assert.equal(segments.length, 3);
|
|
assert.equal(segments[0].type, "session");
|
|
assert.equal(segments[0].value, "1");
|
|
assert.equal(segments[1].type, "rooms");
|
|
assert.deepEqual(segments[1].value, ["a", "b", "c"]);
|
|
assert.equal(segments[2].type, "empty-grid-tile");
|
|
assert.equal(segments[2].value, 3);
|
|
},
|
|
"parse grid url path with focused room": assert => {
|
|
const segments = parseUrlPath("/session/1/rooms/a,b,c/1");
|
|
assert.equal(segments.length, 3);
|
|
assert.equal(segments[0].type, "session");
|
|
assert.equal(segments[0].value, "1");
|
|
assert.equal(segments[1].type, "rooms");
|
|
assert.deepEqual(segments[1].value, ["a", "b", "c"]);
|
|
assert.equal(segments[2].type, "room");
|
|
assert.equal(segments[2].value, "b");
|
|
},
|
|
"parse empty grid url": assert => {
|
|
const segments = parseUrlPath("/session/1/rooms/");
|
|
assert.equal(segments.length, 3);
|
|
assert.equal(segments[0].type, "session");
|
|
assert.equal(segments[0].value, "1");
|
|
assert.equal(segments[1].type, "rooms");
|
|
assert.deepEqual(segments[1].value, [""]);
|
|
assert.equal(segments[2].type, "empty-grid-tile");
|
|
assert.equal(segments[2].value, 0);
|
|
},
|
|
"parse empty grid url with focus": assert => {
|
|
const segments = parseUrlPath("/session/1/rooms//1");
|
|
assert.equal(segments.length, 3);
|
|
assert.equal(segments[0].type, "session");
|
|
assert.equal(segments[0].value, "1");
|
|
assert.equal(segments[1].type, "rooms");
|
|
assert.deepEqual(segments[1].value, [""]);
|
|
assert.equal(segments[2].type, "empty-grid-tile");
|
|
assert.equal(segments[2].value, 1);
|
|
},
|
|
"parse open-room action replacing the current focused room": assert => {
|
|
const nav = 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);
|
|
assert.equal(segments.length, 3);
|
|
assert.equal(segments[0].type, "session");
|
|
assert.equal(segments[0].value, "1");
|
|
assert.equal(segments[1].type, "rooms");
|
|
assert.deepEqual(segments[1].value, ["a", "d", "c"]);
|
|
assert.equal(segments[2].type, "room");
|
|
assert.equal(segments[2].value, "d");
|
|
},
|
|
"parse open-room action changing focus to an existing room": assert => {
|
|
const nav = 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);
|
|
assert.equal(segments.length, 3);
|
|
assert.equal(segments[0].type, "session");
|
|
assert.equal(segments[0].value, "1");
|
|
assert.equal(segments[1].type, "rooms");
|
|
assert.deepEqual(segments[1].value, ["a", "b", "c"]);
|
|
assert.equal(segments[2].type, "room");
|
|
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 path = nav.pathFrom([
|
|
new Segment("session", 1),
|
|
new Segment("rooms", ["a", "b", "c"]),
|
|
new Segment("room", "b"),
|
|
new Segment("right-panel", true),
|
|
new Segment("details", true)
|
|
]);
|
|
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");
|
|
assert.equal(segments[1].type, "rooms");
|
|
assert.deepEqual(segments[1].value, ["a", "b", "c"]);
|
|
assert.equal(segments[2].type, "room");
|
|
assert.equal(segments[2].value, "a");
|
|
assert.equal(segments[3].type, "right-panel");
|
|
assert.equal(segments[3].value, true);
|
|
assert.equal(segments[4].type, "details");
|
|
assert.equal(segments[4].value, true);
|
|
},
|
|
"parse open-room action setting a room in an empty tile": assert => {
|
|
const nav = 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);
|
|
assert.equal(segments.length, 3);
|
|
assert.equal(segments[0].type, "session");
|
|
assert.equal(segments[0].value, "1");
|
|
assert.equal(segments[1].type, "rooms");
|
|
assert.deepEqual(segments[1].value, ["a", "b", "c", , "d"]); //eslint-disable-line no-sparse-arrays
|
|
assert.equal(segments[2].type, "room");
|
|
assert.equal(segments[2].value, "d");
|
|
},
|
|
"parse session url path without id": assert => {
|
|
const segments = parseUrlPath("/session");
|
|
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 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);
|
|
},
|
|
"remove inactive room from grid path": assert => {
|
|
const nav = 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");
|
|
},
|
|
"remove inactive room from grid path with empty tile": assert => {
|
|
const nav = 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);
|
|
},
|
|
"remove active room": assert => {
|
|
const nav = 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);
|
|
},
|
|
"remove inactive room doesn't do anything": assert => {
|
|
const nav = 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");
|
|
},
|
|
|
|
}
|
|
}
|