Skip to content

Commit ef85ad1

Browse files
committed
attempt #1 for navigation routing
1 parent 7106d05 commit ef85ad1

File tree

7 files changed

+142
-17
lines changed

7 files changed

+142
-17
lines changed

mobile/src/router/index.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,14 @@ export async function goBackOrNavigateTo(ionRouter: UseIonRouterResult, fallback
113113
// Calling ionRouter.back() can't be done in the same thread than router.go(-x) on iOS
114114
// To reproduce: navigate to schedule page from event selector screen, navigate on another tab (ex: favorites tab)
115115
// then click on 'back to event list screen' button in event header
116-
if(isPlatform('ios') && platformRouterGoBacks < 0) {
117-
setTimeout(() => { ionRouter.back(); }, 0)
118-
} else {
119-
ionRouter.back();
120-
}
116+
// if(isPlatform('ios') && platformRouterGoBacks < 0) {
117+
await new Promise((resolve) => setTimeout(() => {
118+
ionRouter.back();
119+
setTimeout(() => resolve(null), 0);
120+
}, 0));
121+
// } else {
122+
// ionRouter.back();
123+
// }
121124
// Sometimes, we might be on a tabbed page without having navigated from another context
122125
// Typical case: if we "refresh" the page on the schedule page, canGoBack() will return false
123126
// because ionic doesn't have into its (memory) history the previous page we would like to navigate to

mobile/src/state/useDevUtilities.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import {ISODatetime} from "../../../shared/type-utils";
88
import {Temporal} from "temporal-polyfill";
99
import {match, P} from "ts-pattern";
1010
import {VoxxrinConferenceDescriptor} from "@/models/VoxxrinConferenceDescriptor";
11-
import {managedRef as ref} from "@/views/vue-utils";
11+
import {getCurrentComponentInstancePath, managedRef as ref} from "@/views/vue-utils";
1212
import {ListableVoxxrinEvent} from "@/models/VoxxrinEvent";
1313
import {Ref} from "vue";
1414
import {updateLogConfigTo} from "@/services/Logger";
15+
import router from "@/router";
16+
import {useIonRouter} from "@ionic/vue";
1517

1618

1719
// if(import.meta.env.DEV) {
@@ -69,6 +71,11 @@ export function useDevUtilities() {
6971
window.location.reload();
7072
}
7173
(window as any).updateLogConfigTo = updateLogConfigTo;
74+
(window as any).getCurrentComponentInstancePath = getCurrentComponentInstancePath;
75+
(window as any)._router = router;
76+
77+
const ionRouter = useIonRouter();
78+
(window as any)._ionRouter = ionRouter;
7279
}
7380

7481
// }

mobile/src/state/useTabbedPageNav.ts

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import {onMounted, onUnmounted} from "vue";
1+
import {onMounted, onUnmounted, getCurrentInstance} from "vue";
22
import {RouteAction, RouteDirection} from "@ionic/vue-router/dist/types/types";
33
import {useIonRouter} from "@ionic/vue";
44
import {
55
createTypedCustomEventClass,
66
TypedCustomEventData,
77
} from "@/models/utils";
8-
import {useRouter} from "vue-router";
98
import {goBackOrNavigateTo} from "@/router";
9+
import {getCurrentComponentInstancePath} from "@/views/vue-utils";
10+
import {match, P} from "ts-pattern";
1011

1112

1213
/**
@@ -52,6 +53,28 @@ const NavigationEvent = createTypedCustomEventClass<{
5253
}>(NavigationEventName)
5354

5455

56+
/**
57+
* This map allows to declare multiple navigation callback per component 'path'
58+
* because, for some (unknown) reasons, when we refresh tabbed page (like, event schedule)
59+
* we have 2 instances of event-tabs component after a tab navigation:
60+
* - first <event-tabs> instance when loading page
61+
* - second <event-tabs> instance on first tab switch
62+
*
63+
* Note that this behaviour only happens on page load occuring on a tabbed page: when we navigate
64+
* from event-selector screen, only a single <event-tabs> instance is created
65+
*
66+
* I guess this has something to do with _BaseEventPages "parent" route which is instantiated twice
67+
* instead of once, in case of a parent-child route refresh
68+
*
69+
* Map below allows to track callback registrations on a per-component path basis (in duplicated <event-tabs> case
70+
* described above, we will have a single entry in PER_COMPONENT_PATH_CALLBACKS, with an array of 2 callbacks declared)
71+
* And when a navigation will be triggered, every callbacks in array will be triggered)
72+
*/
73+
type TabbedPageNavigationCallbacks = {
74+
navCallback: (event: Event) => Promise<void>,
75+
tabExitOrNavigateCallback: (event: Event) => Promise<void>,
76+
};
77+
const PER_COMPONENT_PATH_CALLBACKS = new Map<string, TabbedPageNavigationCallbacks[]>();
5578

5679
export function useTabbedPageNav() {
5780
return {
@@ -76,44 +99,110 @@ export function useTabbedPageNav() {
7699
// Please, call this listeners registration whenever you open a page containing tabs, so that
77100
// tabbed views are able to communicate root-level navigation calls through triggerXXX hooks
78101
registerTabbedPageNavListeners: function(opts?: { skipNavRegistration: boolean, skipExitOrNavRegistration: boolean }) {
102+
const { path: currentComponentInstancePath } = getCurrentComponentInstancePath()
103+
79104
const ionRouter = useIonRouter();
80105
const startingHistoryPosition = history.state.position;
81106

82107
const navCallback = async (event: Event) => {
108+
const perComponentPathCallbacks = PER_COMPONENT_PATH_CALLBACKS.get(currentComponentInstancePath);
109+
if(!perComponentPathCallbacks) {
110+
return;
111+
}
112+
if(perComponentPathCallbacks[perComponentPathCallbacks.length-1].navCallback !== navCallback) {
113+
return;
114+
}
115+
83116
if(isNavigationEvent(event)) {
84117
if(event.detail.onEventCaught) {
85118
await event.detail.onEventCaught();
86119
}
87120

88121
// This navigate() call will happen inside tabbed page context
89122
ionRouter.navigate(event.detail.url, event.detail.routerDirection, event.detail.routerAction);
123+
124+
perComponentPathCallbacks.pop();
125+
if(perComponentPathCallbacks.length) {
126+
await new Promise((resolve) => {
127+
setTimeout(async () => {
128+
await perComponentPathCallbacks[perComponentPathCallbacks.length-1].navCallback(event);
129+
resolve(null);
130+
}, 0);
131+
});
132+
} else {
133+
PER_COMPONENT_PATH_CALLBACKS.delete(currentComponentInstancePath);
134+
}
90135
} else {
91136
throw new Error(`Unexpected event type ${event.type} in tabbed-page-navigation callback registration !`)
92137
}
93138
}
94139
const tabExitOrNavigateCallback = async (event: Event) => {
140+
const perComponentPathCallbacks = PER_COMPONENT_PATH_CALLBACKS.get(currentComponentInstancePath);
141+
if(!perComponentPathCallbacks) {
142+
return;
143+
}
144+
if(perComponentPathCallbacks[perComponentPathCallbacks.length-1].tabExitOrNavigateCallback !== tabExitOrNavigateCallback) {
145+
return;
146+
}
147+
95148
if(isTabExitOrNavigateEvent(event)) {
96149
const routerGoBacks = startingHistoryPosition - history.state.position;
97150
await goBackOrNavigateTo(ionRouter, event.detail.url, routerGoBacks, event.detail.routerDirection, event.detail.onEventCaught);
151+
152+
perComponentPathCallbacks.pop();
153+
if(perComponentPathCallbacks.length) {
154+
await new Promise(async (resolve) => {
155+
await perComponentPathCallbacks[perComponentPathCallbacks.length-1].tabExitOrNavigateCallback(event);
156+
setTimeout(() => resolve(null), 0);
157+
});
158+
} else {
159+
PER_COMPONENT_PATH_CALLBACKS.delete(currentComponentInstancePath);
160+
}
98161
} else {
99162
throw new Error(`Unexpected event type ${event.type} in tabbed-page-navigation callback registration !`)
100163
}
101164
}
102165

166+
const componentCallbacks: TabbedPageNavigationCallbacks = { navCallback, tabExitOrNavigateCallback }
167+
103168
onMounted(() => {
169+
const perComponentPathCallbacks = match(PER_COMPONENT_PATH_CALLBACKS.get(currentComponentInstancePath))
170+
.with(P.nullish, () => {
171+
const callbacks: TabbedPageNavigationCallbacks[] = [];
172+
PER_COMPONENT_PATH_CALLBACKS.set(currentComponentInstancePath, callbacks);
173+
return callbacks;
174+
}).otherwise(callbacks => callbacks);
175+
104176
if(!opts?.skipNavRegistration) {
105-
window.addEventListener(NavigationEventName, navCallback);
177+
window.addEventListener(NavigationEventName, componentCallbacks.navCallback);
106178
}
107179
if(!opts?.skipExitOrNavRegistration) {
108-
window.addEventListener(TabExitOrNavigateEventName, tabExitOrNavigateCallback);
180+
window.addEventListener(TabExitOrNavigateEventName, componentCallbacks.tabExitOrNavigateCallback);
109181
}
182+
183+
perComponentPathCallbacks.push(componentCallbacks);
110184
})
111185
onUnmounted(() => {
112-
if(!opts?.skipNavRegistration) {
113-
window.removeEventListener(NavigationEventName, navCallback);
186+
const maybePerComponentPathCallbacks = PER_COMPONENT_PATH_CALLBACKS.get(currentComponentInstancePath);
187+
if(!maybePerComponentPathCallbacks) {
188+
return;
114189
}
115-
if(!opts?.skipExitOrNavRegistration) {
116-
window.removeEventListener(TabExitOrNavigateEventName, tabExitOrNavigateCallback);
190+
191+
const componentCallbacksIndex = maybePerComponentPathCallbacks.findIndex(callbacks => callbacks === componentCallbacks);
192+
if(componentCallbacksIndex !== -1) {
193+
maybePerComponentPathCallbacks.splice(componentCallbacksIndex, 1);
194+
195+
if(!opts?.skipNavRegistration) {
196+
window.removeEventListener(NavigationEventName, componentCallbacks.navCallback);
197+
}
198+
if(!opts?.skipExitOrNavRegistration) {
199+
window.removeEventListener(TabExitOrNavigateEventName, componentCallbacks.tabExitOrNavigateCallback);
200+
}
201+
}
202+
if(maybePerComponentPathCallbacks.length === 0) {
203+
PER_COMPONENT_PATH_CALLBACKS.delete(currentComponentInstancePath);
204+
} else {
205+
console.log(`remaining callbacks in component ${currentComponentInstancePath}`)
117206
}
118207
})
119208
}

mobile/src/views/SpeakerDetailsPage.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<ion-header class="stickyHeader">
1111
<ion-toolbar>
1212
<ion-button class="stickyHeader-close" shape="round" slot="start" size="small" fill="outline" @click="closeAndNavigateBack()"
13-
:aria-label="LL.Close_speaker_details()">
13+
:aria-label="LL.Close_speaker_details()" data-testid="close-speaker-details">
1414
<ion-icon src="/assets/icons/solid/close.svg"></ion-icon>
1515
</ion-button>
1616
<div class="speakerInfoHeader" slot="start">

mobile/src/views/event/SchedulePage.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ const { schedule: currentSchedule } = useSchedule(confDescriptor, selectedDayId)
183183
184184
const preparingOfflineScheduleToastMessageRef = ref<string | undefined>(undefined);
185185
const preparingOfflineScheduleToastIsOpenRef = ref<boolean>(false);
186-
useOfflineEventPreparation(user, confDescriptor, currentSchedule, availableDaysRef, preparingOfflineScheduleToastMessageRef, preparingOfflineScheduleToastIsOpenRef);
186+
// useOfflineEventPreparation(user, confDescriptor, currentSchedule, availableDaysRef, preparingOfflineScheduleToastMessageRef, preparingOfflineScheduleToastIsOpenRef);
187187
188188
const talkIdsRef = computed(() => {
189189
const schedule = toValue(currentSchedule);

mobile/src/views/event/_BaseEventPages.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import EventTabs from "@/components/events/EventTabs.vue";
1010
import {typesafeI18n} from "@/i18n/i18n-vue";
1111
import {useSharedConferenceDescriptor} from "@/state/useConferenceDescriptor";
12-
import {computed, toValue} from "vue";
12+
import {computed, toValue, onMounted, onUnmounted, onBeforeMount, onBeforeUnmount} from "vue";
1313
import {areFeedbacksEnabled} from "@/models/VoxxrinConferenceDescriptor";
1414
import {getResolvedEventRootPathFromSpacedEventIdRef, useCurrentSpaceEventIdRef} from "@/services/Spaces";
1515
import {match} from "ts-pattern";
@@ -27,6 +27,9 @@ const tabs = computed(() => {
2727
icon: '/assets/icons/line/calendar-line.svg',
2828
selectedIcon: '/assets/icons/solid/calendar.svg',
2929
}]).concat([{
30+
// TODO: Seems like we have a navigation error when hitting back button while being on speakers list (after having navigated to a speaker talk)
31+
// TODO: There is a persistent background loading toaster on top of the screen which appears randomly (particularly while stopping in devtools for a long time)
32+
// => need to sort it out
3033
id: 'speakers', url: `${getResolvedEventRootPathFromSpacedEventIdRef(spacedEventIdRef)}/speakers`, label: LL.value.Speakers(),
3134
icon: '/assets/icons/line/megaphone-line.svg',
3235
selectedIcon: '/assets/icons/solid/megaphone.svg',
@@ -48,6 +51,12 @@ const customFontUrls = computed(() => {
4851
.exhaustive()
4952
)
5053
})
54+
55+
console.log(`In _BaseEventPages::setup()`);
56+
onBeforeMount(() => console.log(`In _BaseEventPages::onBeforeMount`))
57+
onMounted(() => console.log(`In _BaseEventPages::onMounted`))
58+
onBeforeUnmount(() => console.log(`In _BaseEventPages::onBeforeUnmount`))
59+
onUnmounted(() => console.log(`In _BaseEventPages::onUnmounted`))
5160
</script>
5261

5362
<style lang="scss" scoped>

mobile/src/views/vue-utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {LocationQueryValue, RouteLocationNormalizedLoaded} from "vue-router";
22
import {
3+
getCurrentInstance,
34
MaybeRef,
45
onUnmounted,
56
Ref,
@@ -414,4 +415,20 @@ export function deferredVuefireUseCollection<SOURCES extends MultiWatchSources,
414415
return collectionRef;
415416
}
416417

418+
export function getCurrentComponentInstancePath() {
419+
let elem = getCurrentInstance();
420+
const parents = [];
421+
422+
while(elem) {
423+
parents.push(elem);
424+
elem = elem.parent;
425+
}
426+
427+
return {
428+
parents,
429+
path: parents.map(elem => `{type:${elem.type.name || elem.type.__name}}`).join(" > "),
430+
}
431+
}
432+
433+
417434
export const MAX_NUMBER_OF_PARAMS_IN_FIREBASE_IN_CLAUSES = 30;

0 commit comments

Comments
 (0)