mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-23 03:25:12 +01:00
reduce navigation boilerplate
this makes the url router adjust the url when the navigation path is changed, instead of doing urlRouter.applyUrl() and urlRouter.history.pushUrl(). This history field and applyUrl method on URLRouter are now private, as the URLRouter should only be used to generate urls you want to put in an <a href="..."></a>, anything else should use navigator.push()
This commit is contained in:
parent
ddf7d01760
commit
788bce7904
@ -35,13 +35,13 @@ export class RootViewModel extends ViewModel {
|
|||||||
this._sessionViewModel = null;
|
this._sessionViewModel = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(lastUrlHash) {
|
async load() {
|
||||||
this.track(this.navigation.observe("login").subscribe(() => this._applyNavigation()));
|
this.track(this.navigation.observe("login").subscribe(() => this._applyNavigation()));
|
||||||
this.track(this.navigation.observe("session").subscribe(() => this._applyNavigation()));
|
this.track(this.navigation.observe("session").subscribe(() => this._applyNavigation()));
|
||||||
this._applyNavigation(lastUrlHash);
|
this._applyNavigation(this.urlRouter.getLastUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
async _applyNavigation(restoreHashIfAtDefault) {
|
async _applyNavigation(restoreUrlIfAtDefault) {
|
||||||
const isLogin = this.navigation.observe("login").get();
|
const isLogin = this.navigation.observe("login").get();
|
||||||
const sessionId = this.navigation.observe("session").get();
|
const sessionId = this.navigation.observe("session").get();
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
@ -58,30 +58,24 @@ export class RootViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
let url = restoreHashIfAtDefault;
|
if (restoreUrlIfAtDefault) {
|
||||||
if (!url) {
|
this.urlRouter.pushUrl(restoreUrlIfAtDefault);
|
||||||
// redirect depending on what sessions are already present
|
} else {
|
||||||
const sessionInfos = await this._sessionInfoStorage.getAll();
|
const sessionInfos = await this._sessionInfoStorage.getAll();
|
||||||
url = this._urlForSessionInfos(sessionInfos);
|
if (sessionInfos.length === 0) {
|
||||||
|
this.navigation.push("login");
|
||||||
|
} else if (sessionInfos.length === 1) {
|
||||||
|
this.navigation.push("session", sessionInfos[0].id);
|
||||||
|
} else {
|
||||||
|
this.navigation.push("session");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.urlRouter.history.replaceUrl(url);
|
|
||||||
this.urlRouter.applyUrl(url);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._setSection(() => this._error = err);
|
this._setSection(() => this._error = err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_urlForSessionInfos(sessionInfos) {
|
|
||||||
if (sessionInfos.length === 0) {
|
|
||||||
return this.urlRouter.urlForSegment("login");
|
|
||||||
} else if (sessionInfos.length === 1) {
|
|
||||||
return this.urlRouter.urlForSegment("session", sessionInfos[0].id);
|
|
||||||
} else {
|
|
||||||
return this.urlRouter.urlForSegment("session");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _showPicker() {
|
async _showPicker() {
|
||||||
this._setSection(() => {
|
this._setSection(() => {
|
||||||
this._sessionPickerViewModel = new SessionPickerViewModel(this.childOptions({
|
this._sessionPickerViewModel = new SessionPickerViewModel(this.childOptions({
|
||||||
@ -102,10 +96,8 @@ export class RootViewModel extends ViewModel {
|
|||||||
defaultHomeServer: "https://matrix.org",
|
defaultHomeServer: "https://matrix.org",
|
||||||
createSessionContainer: this._createSessionContainer,
|
createSessionContainer: this._createSessionContainer,
|
||||||
ready: sessionContainer => {
|
ready: sessionContainer => {
|
||||||
const url = this.urlRouter.urlForSegment("session", sessionContainer.sessionId);
|
|
||||||
this.urlRouter.applyUrl(url);
|
|
||||||
this.urlRouter.history.replaceUrl(url);
|
|
||||||
this._showSession(sessionContainer);
|
this._showSession(sessionContainer);
|
||||||
|
this.navigation.push("session", sessionContainer.sessionId);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -14,19 +14,28 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableValue} from "../../observable/ObservableValue.js";
|
import {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue.js";
|
||||||
|
|
||||||
export class Navigation {
|
export class Navigation {
|
||||||
constructor(allowsChild) {
|
constructor(allowsChild) {
|
||||||
this._allowsChild = allowsChild;
|
this._allowsChild = allowsChild;
|
||||||
this._path = new Path([], allowsChild);
|
this._path = new Path([], allowsChild);
|
||||||
this._observables = new Map();
|
this._observables = new Map();
|
||||||
|
this._pathObservable = new ObservableValue(this._path);
|
||||||
|
}
|
||||||
|
|
||||||
|
get pathObservable() {
|
||||||
|
return this._pathObservable;
|
||||||
}
|
}
|
||||||
|
|
||||||
get path() {
|
get path() {
|
||||||
return this._path;
|
return this._path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
push(type, value = undefined) {
|
||||||
|
return this.applyPath(this.path.with(new Segment(type, value)));
|
||||||
|
}
|
||||||
|
|
||||||
applyPath(path) {
|
applyPath(path) {
|
||||||
// Path is not exported, so you can only create a Path through Navigation,
|
// Path is not exported, so you can only create a Path through Navigation,
|
||||||
// so we assume it respects the allowsChild rules
|
// so we assume it respects the allowsChild rules
|
||||||
@ -45,6 +54,10 @@ export class Navigation {
|
|||||||
const observable = this._observables.get(segment.type);
|
const observable = this._observables.get(segment.type);
|
||||||
observable?.emitIfChanged();
|
observable?.emitIfChanged();
|
||||||
}
|
}
|
||||||
|
// to observe the whole path having changed
|
||||||
|
// Since paths are immutable,
|
||||||
|
// we can just use set here which will compare the references
|
||||||
|
this._pathObservable.set(this._path);
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(type) {
|
observe(type) {
|
||||||
|
@ -14,40 +14,50 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Segment} from "./Navigation.js";
|
|
||||||
|
|
||||||
export class URLRouter {
|
export class URLRouter {
|
||||||
constructor({history, navigation, parseUrlPath, stringifyPath}) {
|
constructor({history, navigation, parseUrlPath, stringifyPath}) {
|
||||||
this._subscription = null;
|
|
||||||
this._history = history;
|
this._history = history;
|
||||||
this._navigation = navigation;
|
this._navigation = navigation;
|
||||||
this._parseUrlPath = parseUrlPath;
|
this._parseUrlPath = parseUrlPath;
|
||||||
this._stringifyPath = stringifyPath;
|
this._stringifyPath = stringifyPath;
|
||||||
|
this._subscription = null;
|
||||||
|
this._pathSubscription = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
attach() {
|
attach() {
|
||||||
this._subscription = this._history.subscribe(url => {
|
this._subscription = this._history.subscribe(url => {
|
||||||
const redirectedUrl = this.applyUrl(url);
|
const redirectedUrl = this._applyUrl(url);
|
||||||
if (redirectedUrl !== url) {
|
if (redirectedUrl !== url) {
|
||||||
this._history.replaceUrl(redirectedUrl);
|
this._history.replaceUrlSilently(redirectedUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._applyUrl(this._history.get());
|
||||||
|
this._pathSubscription = this._navigation.pathObservable.subscribe(path => {
|
||||||
|
const url = this.urlForPath(path);
|
||||||
|
if (url !== this._history.get()) {
|
||||||
|
this._history.pushUrlSilently(url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.applyUrl(this._history.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this._subscription = this._subscription();
|
this._subscription = this._subscription();
|
||||||
|
this._pathSubscription = this._pathSubscription();
|
||||||
}
|
}
|
||||||
|
|
||||||
applyUrl(url) {
|
_applyUrl(url) {
|
||||||
const urlPath = this._history.urlAsPath(url)
|
const urlPath = this._history.urlAsPath(url)
|
||||||
const navPath = this._navigation.pathFrom(this._parseUrlPath(urlPath, this._navigation.path));
|
const navPath = this._navigation.pathFrom(this._parseUrlPath(urlPath, this._navigation.path));
|
||||||
this._navigation.applyPath(navPath);
|
this._navigation.applyPath(navPath);
|
||||||
return this._history.pathAsUrl(this._stringifyPath(navPath));
|
return this._history.pathAsUrl(this._stringifyPath(navPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
get history() {
|
pushUrl(url) {
|
||||||
return this._history;
|
this._history.pushUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastUrl() {
|
||||||
|
return this._history.getLastUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
urlForSegments(segments) {
|
urlForSegments(segments) {
|
||||||
@ -70,7 +80,7 @@ export class URLRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
urlForPath(path) {
|
urlForPath(path) {
|
||||||
return this.history.pathAsUrl(this._stringifyPath(path));
|
return this._history.pathAsUrl(this._stringifyPath(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
openRoomActionUrl(roomId) {
|
openRoomActionUrl(roomId) {
|
||||||
@ -78,26 +88,4 @@ export class URLRouter {
|
|||||||
const urlPath = `${this._stringifyPath(this._navigation.path.until("session"))}/open-room/${roomId}`;
|
const urlPath = `${this._stringifyPath(this._navigation.path.until("session"))}/open-room/${roomId}`;
|
||||||
return this._history.pathAsUrl(urlPath);
|
return this._history.pathAsUrl(urlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableGridUrl() {
|
|
||||||
let path = this._navigation.path.until("session");
|
|
||||||
const room = this._navigation.path.get("room");
|
|
||||||
if (room) {
|
|
||||||
path = path.with(room);
|
|
||||||
}
|
|
||||||
return this.urlForPath(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
enableGridUrl() {
|
|
||||||
let path = this._navigation.path.until("session");
|
|
||||||
const room = this._navigation.path.get("room");
|
|
||||||
if (room) {
|
|
||||||
path = path.with(this._navigation.segment("rooms", [room.value]));
|
|
||||||
path = path.with(room);
|
|
||||||
} else {
|
|
||||||
path = path.with(this._navigation.segment("rooms", []));
|
|
||||||
path = path.with(this._navigation.segment("empty-grid-tile", 0));
|
|
||||||
}
|
|
||||||
return this.urlForPath(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -83,16 +83,12 @@ export class RoomGridViewModel extends ViewModel {
|
|||||||
if (index === this._selectedIndex) {
|
if (index === this._selectedIndex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let path = this.navigation.path;
|
|
||||||
const vm = this._viewModels[index];
|
const vm = this._viewModels[index];
|
||||||
if (vm) {
|
if (vm) {
|
||||||
path = path.with(this.navigation.segment("room", vm.id));
|
this.navigation.push("room", vm.id);
|
||||||
} else {
|
} else {
|
||||||
path = path.with(this.navigation.segment("empty-grid-tile", index));
|
this.navigation.push("empty-grid-tile", index);
|
||||||
}
|
}
|
||||||
let url = this.urlRouter.urlForPath(path);
|
|
||||||
url = this.urlRouter.applyUrl(url);
|
|
||||||
this.urlRouter.history.pushUrl(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** called from SessionViewModel */
|
/** called from SessionViewModel */
|
||||||
|
@ -76,14 +76,25 @@ export class LeftPanelViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleGrid() {
|
toggleGrid() {
|
||||||
let url;
|
|
||||||
if (this.gridEnabled) {
|
if (this.gridEnabled) {
|
||||||
url = this.urlRouter.disableGridUrl();
|
let path = this.navigation.path.until("session");
|
||||||
|
const room = this.navigation.path.get("room");
|
||||||
|
if (room) {
|
||||||
|
path = path.with(room);
|
||||||
|
}
|
||||||
|
this.navigation.applyPath(path);
|
||||||
} else {
|
} else {
|
||||||
url = this.urlRouter.enableGridUrl();
|
let path = this.navigation.path.until("session");
|
||||||
|
const room = this.navigation.path.get("room");
|
||||||
|
if (room) {
|
||||||
|
path = path.with(this.navigation.segment("rooms", [room.value]));
|
||||||
|
path = path.with(room);
|
||||||
|
} else {
|
||||||
|
path = path.with(this.navigation.segment("rooms", []));
|
||||||
|
path = path.with(this.navigation.segment("empty-grid-tile", 0));
|
||||||
|
}
|
||||||
|
this.navigation.applyPath(path);
|
||||||
}
|
}
|
||||||
url = this.urlRouter.applyUrl(url);
|
|
||||||
this.urlRouter.history.pushUrl(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get roomList() {
|
get roomList() {
|
||||||
|
@ -118,8 +118,7 @@ export async function main(container, paths, legacyExtras) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const navigation = createNavigation();
|
const navigation = createNavigation();
|
||||||
const history = new History();
|
const urlRouter = createRouter({navigation, history: new History()});
|
||||||
const urlRouter = createRouter({navigation, history});
|
|
||||||
urlRouter.attach();
|
urlRouter.attach();
|
||||||
|
|
||||||
const vm = new RootViewModel({
|
const vm = new RootViewModel({
|
||||||
@ -143,7 +142,7 @@ export async function main(container, paths, legacyExtras) {
|
|||||||
navigation
|
navigation
|
||||||
});
|
});
|
||||||
window.__brawlViewModel = vm;
|
window.__brawlViewModel = vm;
|
||||||
await vm.load(history.getLastUrl());
|
await vm.load();
|
||||||
// TODO: replace with platform.createAndMountRootView(vm, container);
|
// TODO: replace with platform.createAndMountRootView(vm, container);
|
||||||
const view = new RootView(vm);
|
const view = new RootView(vm);
|
||||||
container.appendChild(view.mount());
|
container.appendChild(view.mount());
|
||||||
|
@ -20,14 +20,9 @@ export class History extends BaseObservableValue {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._boundOnHashChange = null;
|
this._boundOnHashChange = null;
|
||||||
this._expectSetEcho = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onHashChange() {
|
_onHashChange() {
|
||||||
if (this._expectSetEcho) {
|
|
||||||
this._expectSetEcho = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.emit(this.get());
|
this.emit(this.get());
|
||||||
this._storeHash(this.get());
|
this._storeHash(this.get());
|
||||||
}
|
}
|
||||||
@ -37,28 +32,19 @@ export class History extends BaseObservableValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** does not emit */
|
/** does not emit */
|
||||||
replaceUrl(url) {
|
replaceUrlSilently(url) {
|
||||||
window.history.replaceState(null, null, url);
|
window.history.replaceState(null, null, url);
|
||||||
this._storeHash(url);
|
this._storeHash(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** does not emit */
|
/** does not emit */
|
||||||
pushUrl(url) {
|
pushUrlSilently(url) {
|
||||||
window.history.pushState(null, null, url);
|
window.history.pushState(null, null, url);
|
||||||
this._storeHash(url);
|
this._storeHash(url);
|
||||||
// const hash = this.urlAsPath(url);
|
}
|
||||||
// // important to check before we expect an echo
|
|
||||||
// // as setting the hash to it's current value doesn't
|
pushUrl(url) {
|
||||||
// // trigger onhashchange
|
document.location.hash = url;
|
||||||
// if (hash === document.location.hash) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// // this operation is silent,
|
|
||||||
// // so avoid emitting on echo hashchange event
|
|
||||||
// if (this._boundOnHashChange) {
|
|
||||||
// this._expectSetEcho = true;
|
|
||||||
// }
|
|
||||||
// document.location.hash = hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
urlAsPath(url) {
|
urlAsPath(url) {
|
||||||
|
Loading…
Reference in New Issue
Block a user