diff --git a/src/domain/navigation/URLRouter.js b/src/domain/navigation/URLRouter.js index 5757ca10..05da4ea7 100644 --- a/src/domain/navigation/URLRouter.js +++ b/src/domain/navigation/URLRouter.js @@ -17,10 +17,11 @@ limitations under the License. import {Segment} from "./Navigation.js"; export class URLRouter { - constructor(history, navigation) { + constructor({history, navigation, redirect}) { this._subscription = null; this._history = history; this._navigation = navigation; + this._redirect = redirect; } attach() { @@ -32,7 +33,7 @@ export class URLRouter { applyUrl(url) { const segments = this._segmentsFromUrl(url); - const path = this._navigation.pathFrom(segments); + const path = this._redirect(segments, this._navigation); this._navigation.applyPath(path); } diff --git a/src/domain/navigation/index.js b/src/domain/navigation/index.js index b4b9890e..36627763 100644 --- a/src/domain/navigation/index.js +++ b/src/domain/navigation/index.js @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {Navigation} from "./Navigation.js"; +import {Navigation, Segment} from "./Navigation.js"; +import {URLRouter} from "./URLRouter.js"; export function createNavigation() { return new Navigation(function allowsChild(parent, child) { @@ -25,8 +26,65 @@ export function createNavigation() { return type === "login" || type === "session"; 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"; default: return false; } }); } + +export function createRouter({history, navigation}) { + return new URLRouter({history, navigation, redirect}); +} + +function redirect(urlParts, navigation) { + const {path} = navigation; + const segments = urlParts.reduce((output, s) => { + // redirect open-room action to grid/non-grid url + if (s.type === "open-room") { + const rooms = path.get("rooms"); + if (rooms) { + output = output.concat(roomsSegmentWithRoom(rooms, s.value, path)); + } + return rooms.concat(new Segment("room", s.value)); + } + return output.concat(s); + }, []); + return navigation.pathFrom(segments); +} + +function roomsSegmentWithRoom(rooms, roomId, path) { + // find the index of either the current room, + // or the current selected empty tile, + // to put the new room in + + // TODO: is rooms.value a string or an array? + const room = path.get("room"); + let index = 0; + if (room) { + index = rooms.value.indexOf(room.value); + } else { + const emptyGridTile = path.get("empty-grid-tile"); + if (emptyGridTile) { + index = emptyGridTile.value; + } + } + const newRooms = rooms.slice(); + newRooms[index] = roomId; + return new Segment("rooms", newRooms); +} + +function parseUrlValue(type, iterator) { + if (type === "rooms") { + const roomIds = iterator.next().value.split(","); + const selectedIndex = parseInt(iterator.next().value, 10); + const roomId = roomIds[selectedIndex]; + if (roomId) { + return [new Segment(type, roomIds), new Segment("room", roomId)]; + } else { + return [new Segment(type, roomIds), new Segment("empty-grid-tile", selectedIndex)]; + } + } +} diff --git a/src/domain/session/RoomGridViewModel.js b/src/domain/session/RoomGridViewModel.js index 9d91cd99..13fbcd44 100644 --- a/src/domain/session/RoomGridViewModel.js +++ b/src/domain/session/RoomGridViewModel.js @@ -26,7 +26,7 @@ export class RoomGridViewModel extends ViewModel { } roomViewModelAt(i) { - return this._viewModels[i]?.vm; + return this._viewModels[i]; } get focusIndex() { @@ -37,14 +37,9 @@ export class RoomGridViewModel extends ViewModel { if (idx === this._selectedIndex) { return; } - const oldItem = this._viewModels[this._selectedIndex]; - oldItem?.tileVM?.close(); this._selectedIndex = idx; - const newItem = this._viewModels[this._selectedIndex]; - if (newItem) { - newItem.vm.focus(); - newItem.tileVM.open(); - } + const vm = this._viewModels[this._selectedIndex]; + vm?.focus(); this.emitChange("focusedIndex"); } get width() { @@ -58,14 +53,12 @@ export class RoomGridViewModel extends ViewModel { /** * Sets a pair of room and room tile view models at the current index * @param {RoomViewModel} vm - * @param {RoomTileViewModel} tileVM * @package */ - setRoomViewModel(vm, tileVM) { + setRoomViewModel(vm) { const old = this._viewModels[this._selectedIndex]; - this.disposeTracked(old?.vm); - old?.tileVM?.close(); - this._viewModels[this._selectedIndex] = {vm: this.track(vm), tileVM}; + this.disposeTracked(old); + this._viewModels[this._selectedIndex] = this.track(vm); this.emitChange(`${this._selectedIndex}`); } @@ -73,7 +66,7 @@ export class RoomGridViewModel extends ViewModel { * @package */ tryFocusRoom(roomId) { - const index = this._viewModels.findIndex(vms => vms?.vm.id === roomId); + const index = this._viewModels.findIndex(vm => vm.id === roomId); if (index >= 0) { this.setFocusIndex(index); return true; @@ -82,15 +75,15 @@ export class RoomGridViewModel extends ViewModel { } /** - * Returns the first set of room and room tile vm, - * and untracking them so they are not owned by this view model anymore. + * Returns the first set of room vm, + * and untracking it so it is not owned by this view model anymore. * @package */ getAndUntrackFirst() { - for (const item of this._viewModels) { - if (item) { - this.untrack(item.vm); - return item; + for (const vm of this._viewModels) { + if (vm) { + this.untrack(vm); + return vm; } } } diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index f49d5ff1..8acab44a 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -33,22 +33,33 @@ export class SessionViewModel extends ViewModel { }))); this._leftPanelViewModel = new LeftPanelViewModel(this.childOptions({ rooms: this._sessionContainer.session.rooms, - openRoom: this._openRoom.bind(this), + // this will go over navigation as well gridEnabled: { get: () => !!this._gridViewModel, set: value => this._enabledGrid(value) } })); - this._currentRoomTileViewModel = null; this._currentRoomViewModel = null; this._gridViewModel = null; + // this gives us the active room, also in the grid? + this.track(this.navigator.observe("room").subscribe(roomId => { + + })); + // this gives us a set of room ids in the grid + this.track(this.navigator.observe("rooms").subscribe(value => { + if (value) { + const roomIds = typeof value === "string" ? value.split(",") : []; + // also update grid + this._enabledGrid(roomIds); + } + })); } start() { this._sessionStatusViewModel.start(); } - get selectionId() { + get activeSection() { if (this._currentRoomViewModel) { return this._currentRoomViewModel.id; } else if (this._gridViewModel) { @@ -73,62 +84,56 @@ export class SessionViewModel extends ViewModel { return this._roomList; } - get currentRoom() { + get currentRoomViewModel() { return this._currentRoomViewModel; } + // TODO: this should also happen based on URLs _enabledGrid(enabled) { if (enabled) { this._gridViewModel = this.track(new RoomGridViewModel(this.childOptions({width: 3, height: 2}))); // transfer current room if (this._currentRoomViewModel) { this.untrack(this._currentRoomViewModel); - this._gridViewModel.setRoomViewModel(this._currentRoomViewModel, this._currentRoomTileViewModel); + this._gridViewModel.setRoomViewModel(this._currentRoomViewModel); this._currentRoomViewModel = null; - this._currentRoomTileViewModel = null; } } else { - const VMs = this._gridViewModel.getAndUntrackFirst(); - if (VMs) { - this._currentRoomViewModel = this.track(VMs.vm); - this._currentRoomTileViewModel = VMs.tileVM; - this._currentRoomTileViewModel.open(); + const vm = this._gridViewModel.getAndUntrackFirst(); + if (vm) { + this._currentRoomViewModel = this.track(vm); } this._gridViewModel = this.disposeTracked(this._gridViewModel); } this.emitChange("middlePanelViewType"); } - _closeCurrentRoom() { - // no closing in grid for now as it is disabled on narrow viewports - if (!this._gridViewModel) { - this._currentRoomTileViewModel?.close(); - this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel); - return true; - } - } - - _openRoom(room, roomTileVM) { - if (this._gridViewModel?.tryFocusRoom(room.id)) { + _openRoom(roomId) { + // already open? + if (this._gridViewModel?.tryFocusRoom(roomId)) { return; - } else if (this._currentRoomViewModel?.id === room.id) { + } else if (this._currentRoomViewModel?.id === roomId) { + return; + } + const room = this._session.rooms.get(roomId); + // not found? close current room and show placeholder + if (!room) { + if (this._gridViewModel) { + this._gridViewModel.setRoomViewModel(null); + } else { + this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel); + } return; } const roomVM = new RoomViewModel(this.childOptions({ room, ownUserId: this._sessionContainer.session.user.id, - closeCallback: () => { - if (this._closeCurrentRoom()) { - this.emitChange("currentRoom"); - } - }, })); roomVM.load(); if (this._gridViewModel) { - this._gridViewModel.setRoomViewModel(roomVM, roomTileVM); + this._gridViewModel.setRoomViewModel(roomVM); } else { - this._closeCurrentRoom(); - this._currentRoomTileViewModel = roomTileVM; + this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel); this._currentRoomViewModel = this.track(roomVM); this.emitChange("currentRoom"); } diff --git a/src/domain/session/leftpanel/RoomTileViewModel.js b/src/domain/session/leftpanel/RoomTileViewModel.js index 2b25294b..112e1ffa 100644 --- a/src/domain/session/leftpanel/RoomTileViewModel.js +++ b/src/domain/session/leftpanel/RoomTileViewModel.js @@ -45,7 +45,6 @@ export class RoomTileViewModel extends ViewModel { } } - // called by parent for now (later should integrate with router) close() { if (this._isOpen) { this._isOpen = false; diff --git a/src/ui/web/session/SessionView.js b/src/ui/web/session/SessionView.js index a85ff3dd..2c6885de 100644 --- a/src/ui/web/session/SessionView.js +++ b/src/ui/web/session/SessionView.js @@ -32,14 +32,14 @@ export class SessionView extends TemplateView { t.view(new SessionStatusView(vm.sessionStatusViewModel)), t.div({className: "main"}, [ t.view(new LeftPanelView(vm.leftPanelViewModel)), - t.mapView(vm => vm.selectionId, selectionId => { - switch (selectionId) { + t.mapView(vm => vm.activeSection, activeSection => { + switch (activeSection) { case "roomgrid": return new RoomGridView(vm.roomGridViewModel); case "placeholder": return new StaticView(t => t.div({className: "room-placeholder"}, t.h2(vm.i18n`Choose a room on the left side.`))); default: //room id - return new RoomView(vm.currentRoom); + return new RoomView(vm.currentRoomViewModel); } }) ])