From 55a8a8fa4c6c025c12f13afa69df493fe06f1cc9 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Fri, 24 Feb 2023 17:47:53 -0600 Subject: [PATCH] Sync browser navigation with app --- src/App.web.tsx | 1 + src/lib/analytics.tsx | 1 + src/lib/analytics.web.tsx | 1 + src/platform/urls.tsx | 9 ---- src/state/models/navigation.ts | 86 +++++++++++++++++++++++++--------- 5 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/App.web.tsx b/src/App.web.tsx index 1c6efba8..84d3b6cd 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -16,6 +16,7 @@ function App() { view.setup() setupState().then(store => { setRootStore(store) + store.nav.bindWebNavigation() getInitialURL().then(url => { if (url) { store.nav.handleLink(url) diff --git a/src/lib/analytics.tsx b/src/lib/analytics.tsx index 441cdc45..e12b8cf7 100644 --- a/src/lib/analytics.tsx +++ b/src/lib/analytics.tsx @@ -7,6 +7,7 @@ const segmentClient = createClient({ writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI', trackAppLifecycleEvents: false, }) +export const track = segmentClient.track export {useAnalytics} from '@segment/analytics-react-native' diff --git a/src/lib/analytics.web.tsx b/src/lib/analytics.web.tsx index f04d945f..e2db537f 100644 --- a/src/lib/analytics.web.tsx +++ b/src/lib/analytics.web.tsx @@ -6,6 +6,7 @@ const _analytics = { screen(_name: string) {}, track(_name: string, _opts: any) {}, } +export const track = _analytics.track export function useAnalytics() { return _analytics } diff --git a/src/platform/urls.tsx b/src/platform/urls.tsx index f544262d..fd844d93 100644 --- a/src/platform/urls.tsx +++ b/src/platform/urls.tsx @@ -1,5 +1,4 @@ import {Linking} from 'react-native' -import {createBrowserHistory, createMemoryHistory} from 'history' import {isNative, isWeb} from './detection' export async function getInitialURL(): Promise { @@ -24,11 +23,3 @@ export function clearHash() { window.location.hash = '' } } - -export function getHistory() { - if (isWeb) { - return createBrowserHistory() - } else { - return createMemoryHistory() - } -} diff --git a/src/state/models/navigation.ts b/src/state/models/navigation.ts index 9cbc678d..9836121b 100644 --- a/src/state/models/navigation.ts +++ b/src/state/models/navigation.ts @@ -1,8 +1,8 @@ import {RootStoreModel} from './root-store' import {makeAutoObservable} from 'mobx' import {TABS_ENABLED} from 'lib/build-flags' -import {segmentClient} from 'lib/analytics' -import {getHistory} from 'platform/urls' +import * as analytics from 'lib/analytics' +import {isNative} from 'platform/detection' let __id = 0 function genId() { @@ -42,7 +42,6 @@ export type HistoryPtr = string // `{tabId}-{historyId}` export class NavigationTabModel { id = genId() history: HistoryItem[] - browserHistory: any index = 0 isNewTab = false @@ -50,13 +49,11 @@ export class NavigationTabModel { this.history = [ {url: TabPurposeMainPath[fixedTabPurpose], ts: Date.now(), id: genId()}, ] - this.browserHistory = getHistory() makeAutoObservable(this, { serialize: false, hydrate: false, }) } - // accessors // = @@ -108,7 +105,7 @@ export class NavigationTabModel { navigate(url: string, title?: string) { try { const path = url.split('/')[1] - segmentClient.track('Navigation', { + analytics.track('Navigation', { path, }) } catch (error) {} @@ -125,8 +122,10 @@ export class NavigationTabModel { this.history.push({url: fixedUrl, ts: Date.now(), id: genId()}) } this.history.push({url, title, ts: Date.now(), id: genId()}) - this.browserHistory.push(url) this.index = this.history.length - 1 + if (!isNative) { + window.history.pushState({hindex: this.index, hurl: url}, '', url) + } } } @@ -146,7 +145,9 @@ export class NavigationTabModel { goBack() { if (this.canGoBack) { this.index-- - this.browserHistory.back() + if (!isNative) { + window.history.back() + } } } @@ -160,14 +161,19 @@ export class NavigationTabModel { goForward() { if (this.canGoForward) { this.index++ - this.browserHistory.forward() + if (!isNative) { + window.history.forward() + } } } goToIndex(index: number) { if (index >= 0 && index <= this.history.length - 1) { + const delta = index - this.index this.index = index - this.browserHistory.go(index) + if (!isNative) { + window.history.go(delta) + } } } @@ -184,6 +190,15 @@ export class NavigationTabModel { this.isNewTab = v } + // browser only + // = + + resetTo(url: string) { + this.index = 0 + this.history.push({url, title: '', ts: Date.now(), id: genId()}) + this.index = this.history.length - 1 + } + // persistence // = @@ -229,11 +244,13 @@ export class NavigationTabModel { } export class NavigationModel { - tabs: NavigationTabModel[] = [ - new NavigationTabModel(TabPurpose.Default), - new NavigationTabModel(TabPurpose.Search), - new NavigationTabModel(TabPurpose.Notifs), - ] + tabs: NavigationTabModel[] = isNative + ? [ + new NavigationTabModel(TabPurpose.Default), + new NavigationTabModel(TabPurpose.Search), + new NavigationTabModel(TabPurpose.Notifs), + ] + : [new NavigationTabModel(TabPurpose.Default)] tabIndex = 0 constructor(public rootStore: RootStoreModel) { @@ -244,12 +261,39 @@ export class NavigationModel { }) } + /** + * Used only in the web build to sync with browser history state + */ + bindWebNavigation() { + if (!isNative) { + window.addEventListener('popstate', e => { + const {hindex, hurl} = e.state + if (hindex >= 0 && hindex <= this.tab.history.length - 1) { + this.tab.index = hindex + } + if (this.tab.current.url !== hurl) { + // desynced because they went back to an old tab session- + // do a reset to match that + this.tab.resetTo(hurl) + } + + // sanity check + if (this.tab.current.url !== window.location.pathname) { + // state has completely desynced, reload + window.location.reload() + } + }) + } + } + clear() { - this.tabs = [ - new NavigationTabModel(TabPurpose.Default), - new NavigationTabModel(TabPurpose.Search), - new NavigationTabModel(TabPurpose.Notifs), - ] + this.tabs = isNative + ? [ + new NavigationTabModel(TabPurpose.Default), + new NavigationTabModel(TabPurpose.Search), + new NavigationTabModel(TabPurpose.Notifs), + ] + : [new NavigationTabModel(TabPurpose.Default)] this.tabIndex = 0 } @@ -372,7 +416,7 @@ export class NavigationModel { hydrate(_v: unknown) { // TODO fixme this.clear() - /*if (isObj(v)) { + /*if (isObj(v)) { if (hasProp(v, 'tabs') && Array.isArray(v.tabs)) { for (const tab of v.tabs) { const copy = new NavigationTabModel()