WIP
This commit is contained in:
parent
83ac3eccc5
commit
6c2c29a7da
6 changed files with 114 additions and 58 deletions
|
@ -17,10 +17,11 @@ limitations under the License.
|
||||||
import {Segment} from "./Navigation.js";
|
import {Segment} from "./Navigation.js";
|
||||||
|
|
||||||
export class URLRouter {
|
export class URLRouter {
|
||||||
constructor(history, navigation) {
|
constructor({history, navigation, redirect}) {
|
||||||
this._subscription = null;
|
this._subscription = null;
|
||||||
this._history = history;
|
this._history = history;
|
||||||
this._navigation = navigation;
|
this._navigation = navigation;
|
||||||
|
this._redirect = redirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
attach() {
|
attach() {
|
||||||
|
@ -32,7 +33,7 @@ export class URLRouter {
|
||||||
|
|
||||||
applyUrl(url) {
|
applyUrl(url) {
|
||||||
const segments = this._segmentsFromUrl(url);
|
const segments = this._segmentsFromUrl(url);
|
||||||
const path = this._navigation.pathFrom(segments);
|
const path = this._redirect(segments, this._navigation);
|
||||||
this._navigation.applyPath(path);
|
this._navigation.applyPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Navigation} from "./Navigation.js";
|
import {Navigation, Segment} from "./Navigation.js";
|
||||||
|
import {URLRouter} from "./URLRouter.js";
|
||||||
|
|
||||||
export function createNavigation() {
|
export function createNavigation() {
|
||||||
return new Navigation(function allowsChild(parent, child) {
|
return new Navigation(function allowsChild(parent, child) {
|
||||||
|
@ -25,8 +26,65 @@ export function createNavigation() {
|
||||||
return type === "login" || type === "session";
|
return type === "login" || type === "session";
|
||||||
case "session":
|
case "session":
|
||||||
return type === "room" || type === "rooms" || type === "settings";
|
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:
|
default:
|
||||||
return false;
|
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)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ export class RoomGridViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
roomViewModelAt(i) {
|
roomViewModelAt(i) {
|
||||||
return this._viewModels[i]?.vm;
|
return this._viewModels[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
get focusIndex() {
|
get focusIndex() {
|
||||||
|
@ -37,14 +37,9 @@ export class RoomGridViewModel extends ViewModel {
|
||||||
if (idx === this._selectedIndex) {
|
if (idx === this._selectedIndex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const oldItem = this._viewModels[this._selectedIndex];
|
|
||||||
oldItem?.tileVM?.close();
|
|
||||||
this._selectedIndex = idx;
|
this._selectedIndex = idx;
|
||||||
const newItem = this._viewModels[this._selectedIndex];
|
const vm = this._viewModels[this._selectedIndex];
|
||||||
if (newItem) {
|
vm?.focus();
|
||||||
newItem.vm.focus();
|
|
||||||
newItem.tileVM.open();
|
|
||||||
}
|
|
||||||
this.emitChange("focusedIndex");
|
this.emitChange("focusedIndex");
|
||||||
}
|
}
|
||||||
get width() {
|
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
|
* Sets a pair of room and room tile view models at the current index
|
||||||
* @param {RoomViewModel} vm
|
* @param {RoomViewModel} vm
|
||||||
* @param {RoomTileViewModel} tileVM
|
|
||||||
* @package
|
* @package
|
||||||
*/
|
*/
|
||||||
setRoomViewModel(vm, tileVM) {
|
setRoomViewModel(vm) {
|
||||||
const old = this._viewModels[this._selectedIndex];
|
const old = this._viewModels[this._selectedIndex];
|
||||||
this.disposeTracked(old?.vm);
|
this.disposeTracked(old);
|
||||||
old?.tileVM?.close();
|
this._viewModels[this._selectedIndex] = this.track(vm);
|
||||||
this._viewModels[this._selectedIndex] = {vm: this.track(vm), tileVM};
|
|
||||||
this.emitChange(`${this._selectedIndex}`);
|
this.emitChange(`${this._selectedIndex}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +66,7 @@ export class RoomGridViewModel extends ViewModel {
|
||||||
* @package
|
* @package
|
||||||
*/
|
*/
|
||||||
tryFocusRoom(roomId) {
|
tryFocusRoom(roomId) {
|
||||||
const index = this._viewModels.findIndex(vms => vms?.vm.id === roomId);
|
const index = this._viewModels.findIndex(vm => vm.id === roomId);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
this.setFocusIndex(index);
|
this.setFocusIndex(index);
|
||||||
return true;
|
return true;
|
||||||
|
@ -82,15 +75,15 @@ export class RoomGridViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first set of room and room tile vm,
|
* Returns the first set of room vm,
|
||||||
* and untracking them so they are not owned by this view model anymore.
|
* and untracking it so it is not owned by this view model anymore.
|
||||||
* @package
|
* @package
|
||||||
*/
|
*/
|
||||||
getAndUntrackFirst() {
|
getAndUntrackFirst() {
|
||||||
for (const item of this._viewModels) {
|
for (const vm of this._viewModels) {
|
||||||
if (item) {
|
if (vm) {
|
||||||
this.untrack(item.vm);
|
this.untrack(vm);
|
||||||
return item;
|
return vm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,22 +33,33 @@ export class SessionViewModel extends ViewModel {
|
||||||
})));
|
})));
|
||||||
this._leftPanelViewModel = new LeftPanelViewModel(this.childOptions({
|
this._leftPanelViewModel = new LeftPanelViewModel(this.childOptions({
|
||||||
rooms: this._sessionContainer.session.rooms,
|
rooms: this._sessionContainer.session.rooms,
|
||||||
openRoom: this._openRoom.bind(this),
|
// this will go over navigation as well
|
||||||
gridEnabled: {
|
gridEnabled: {
|
||||||
get: () => !!this._gridViewModel,
|
get: () => !!this._gridViewModel,
|
||||||
set: value => this._enabledGrid(value)
|
set: value => this._enabledGrid(value)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
this._currentRoomTileViewModel = null;
|
|
||||||
this._currentRoomViewModel = null;
|
this._currentRoomViewModel = null;
|
||||||
this._gridViewModel = 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() {
|
start() {
|
||||||
this._sessionStatusViewModel.start();
|
this._sessionStatusViewModel.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectionId() {
|
get activeSection() {
|
||||||
if (this._currentRoomViewModel) {
|
if (this._currentRoomViewModel) {
|
||||||
return this._currentRoomViewModel.id;
|
return this._currentRoomViewModel.id;
|
||||||
} else if (this._gridViewModel) {
|
} else if (this._gridViewModel) {
|
||||||
|
@ -73,62 +84,56 @@ export class SessionViewModel extends ViewModel {
|
||||||
return this._roomList;
|
return this._roomList;
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentRoom() {
|
get currentRoomViewModel() {
|
||||||
return this._currentRoomViewModel;
|
return this._currentRoomViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this should also happen based on URLs
|
||||||
_enabledGrid(enabled) {
|
_enabledGrid(enabled) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
this._gridViewModel = this.track(new RoomGridViewModel(this.childOptions({width: 3, height: 2})));
|
this._gridViewModel = this.track(new RoomGridViewModel(this.childOptions({width: 3, height: 2})));
|
||||||
// transfer current room
|
// transfer current room
|
||||||
if (this._currentRoomViewModel) {
|
if (this._currentRoomViewModel) {
|
||||||
this.untrack(this._currentRoomViewModel);
|
this.untrack(this._currentRoomViewModel);
|
||||||
this._gridViewModel.setRoomViewModel(this._currentRoomViewModel, this._currentRoomTileViewModel);
|
this._gridViewModel.setRoomViewModel(this._currentRoomViewModel);
|
||||||
this._currentRoomViewModel = null;
|
this._currentRoomViewModel = null;
|
||||||
this._currentRoomTileViewModel = null;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const VMs = this._gridViewModel.getAndUntrackFirst();
|
const vm = this._gridViewModel.getAndUntrackFirst();
|
||||||
if (VMs) {
|
if (vm) {
|
||||||
this._currentRoomViewModel = this.track(VMs.vm);
|
this._currentRoomViewModel = this.track(vm);
|
||||||
this._currentRoomTileViewModel = VMs.tileVM;
|
|
||||||
this._currentRoomTileViewModel.open();
|
|
||||||
}
|
}
|
||||||
this._gridViewModel = this.disposeTracked(this._gridViewModel);
|
this._gridViewModel = this.disposeTracked(this._gridViewModel);
|
||||||
}
|
}
|
||||||
this.emitChange("middlePanelViewType");
|
this.emitChange("middlePanelViewType");
|
||||||
}
|
}
|
||||||
|
|
||||||
_closeCurrentRoom() {
|
_openRoom(roomId) {
|
||||||
// no closing in grid for now as it is disabled on narrow viewports
|
// already open?
|
||||||
if (!this._gridViewModel) {
|
if (this._gridViewModel?.tryFocusRoom(roomId)) {
|
||||||
this._currentRoomTileViewModel?.close();
|
|
||||||
this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_openRoom(room, roomTileVM) {
|
|
||||||
if (this._gridViewModel?.tryFocusRoom(room.id)) {
|
|
||||||
return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
const roomVM = new RoomViewModel(this.childOptions({
|
const roomVM = new RoomViewModel(this.childOptions({
|
||||||
room,
|
room,
|
||||||
ownUserId: this._sessionContainer.session.user.id,
|
ownUserId: this._sessionContainer.session.user.id,
|
||||||
closeCallback: () => {
|
|
||||||
if (this._closeCurrentRoom()) {
|
|
||||||
this.emitChange("currentRoom");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
roomVM.load();
|
roomVM.load();
|
||||||
if (this._gridViewModel) {
|
if (this._gridViewModel) {
|
||||||
this._gridViewModel.setRoomViewModel(roomVM, roomTileVM);
|
this._gridViewModel.setRoomViewModel(roomVM);
|
||||||
} else {
|
} else {
|
||||||
this._closeCurrentRoom();
|
this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel);
|
||||||
this._currentRoomTileViewModel = roomTileVM;
|
|
||||||
this._currentRoomViewModel = this.track(roomVM);
|
this._currentRoomViewModel = this.track(roomVM);
|
||||||
this.emitChange("currentRoom");
|
this.emitChange("currentRoom");
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ export class RoomTileViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// called by parent for now (later should integrate with router)
|
|
||||||
close() {
|
close() {
|
||||||
if (this._isOpen) {
|
if (this._isOpen) {
|
||||||
this._isOpen = false;
|
this._isOpen = false;
|
||||||
|
|
|
@ -32,14 +32,14 @@ export class SessionView extends TemplateView {
|
||||||
t.view(new SessionStatusView(vm.sessionStatusViewModel)),
|
t.view(new SessionStatusView(vm.sessionStatusViewModel)),
|
||||||
t.div({className: "main"}, [
|
t.div({className: "main"}, [
|
||||||
t.view(new LeftPanelView(vm.leftPanelViewModel)),
|
t.view(new LeftPanelView(vm.leftPanelViewModel)),
|
||||||
t.mapView(vm => vm.selectionId, selectionId => {
|
t.mapView(vm => vm.activeSection, activeSection => {
|
||||||
switch (selectionId) {
|
switch (activeSection) {
|
||||||
case "roomgrid":
|
case "roomgrid":
|
||||||
return new RoomGridView(vm.roomGridViewModel);
|
return new RoomGridView(vm.roomGridViewModel);
|
||||||
case "placeholder":
|
case "placeholder":
|
||||||
return new StaticView(t => t.div({className: "room-placeholder"}, t.h2(vm.i18n`Choose a room on the left side.`)));
|
return new StaticView(t => t.div({className: "room-placeholder"}, t.h2(vm.i18n`Choose a room on the left side.`)));
|
||||||
default: //room id
|
default: //room id
|
||||||
return new RoomView(vm.currentRoom);
|
return new RoomView(vm.currentRoomViewModel);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
|
|
Reference in a new issue