From 534cef8574d8790c3adc4c187fa7985b9db8d82d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 7 Sep 2022 21:07:21 +0200 Subject: [PATCH 1/4] Don't save functions and launchers --- @zapp/core/src/working_tree/SavedTreeState.ts | 2 +- .../working_tree/effects/RememberedValue.ts | 18 ++++++++++++++++++ .../core/src/working_tree/effects/remember.ts | 2 ++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/@zapp/core/src/working_tree/SavedTreeState.ts b/@zapp/core/src/working_tree/SavedTreeState.ts index 9b27a80..f2e83b8 100644 --- a/@zapp/core/src/working_tree/SavedTreeState.ts +++ b/@zapp/core/src/working_tree/SavedTreeState.ts @@ -46,7 +46,7 @@ export class SavedTreeState { return result } - } else if (node instanceof RememberNode) { + } else if (node instanceof RememberNode && node.remembered.shouldBeSaved()) { return { id: node.id, state: node.remembered.value, diff --git a/@zapp/core/src/working_tree/effects/RememberedValue.ts b/@zapp/core/src/working_tree/effects/RememberedValue.ts index f2c8b6e..eb94ab1 100644 --- a/@zapp/core/src/working_tree/effects/RememberedValue.ts +++ b/@zapp/core/src/working_tree/effects/RememberedValue.ts @@ -1,12 +1,20 @@ import { RememberNode } from '../RememberNode.js' +export enum RememberedValueType { + Immutable, + Mutable, + Launcher, +} + export class RememberedValue { protected _value: T protected context: RememberNode + protected type: RememberedValueType constructor(val: T, context: RememberNode) { this._value = val this.context = context + this.type = RememberedValueType.Immutable } public get value() { @@ -17,4 +25,14 @@ export class RememberedValue { public switchContext(context: RememberNode) { this.context = context } + + /** @internal */ + public setType(type: RememberedValueType) { + this.type = type + } + + /** @internal */ + public shouldBeSaved() { + return this.type !== RememberedValueType.Launcher && typeof this._value !== 'function' + } } diff --git a/@zapp/core/src/working_tree/effects/remember.ts b/@zapp/core/src/working_tree/effects/remember.ts index 48a1edb..a83828a 100644 --- a/@zapp/core/src/working_tree/effects/remember.ts +++ b/@zapp/core/src/working_tree/effects/remember.ts @@ -3,6 +3,7 @@ import { RememberNode } from '../RememberNode.js' import { ViewNode } from '../ViewNode.js' import { WorkingTree } from '../WorkingTree.js' import { RememberedMutableValue } from './RememberedMutableValue.js' +import { RememberedValueType } from './RememberedValue.js' export function remember(value: T): RememberedMutableValue { const current = WorkingTree.current as ViewNode @@ -28,6 +29,7 @@ export function remember(value: T): RememberedMutableValue { } const result = savedRemembered === null ? new RememberedMutableValue(value, context) : savedRemembered + result.setType(RememberedValueType.Mutable) context.remembered = result From 038da592f57f18a9b85cc34814bf2a9eb72fb459 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 7 Sep 2022 21:34:14 +0200 Subject: [PATCH 2/4] Make HashNavigator implement NavigatorInterface --- @zapp/core/src/Navigator.ts | 5 +++++ @zapp/watch/src/Navigator.ts | 4 ++++ @zapp/web/src/HashNavigator.ts | 36 +++++++++++++++++++--------------- @zapp/web/src/index.ts | 13 ++++++++++-- web-test/src/NavBar.ts | 8 ++++---- web-test/src/index.ts | 4 ++-- 6 files changed, 46 insertions(+), 24 deletions(-) diff --git a/@zapp/core/src/Navigator.ts b/@zapp/core/src/Navigator.ts index 19196e1..123119e 100644 --- a/@zapp/core/src/Navigator.ts +++ b/@zapp/core/src/Navigator.ts @@ -1,4 +1,5 @@ export interface NavigatorInterface { + readonly currentPage: string navigate(route: string, params?: Record): void goBack(): void } @@ -6,6 +7,10 @@ export interface NavigatorInterface { let navigator: NavigatorInterface export abstract class Navigator { + public static get currentPage(): string { + return navigator.currentPage + } + public static navigate(route: string, params?: Record): void { navigator.navigate(route, params) } diff --git a/@zapp/watch/src/Navigator.ts b/@zapp/watch/src/Navigator.ts index d4bf93b..1083919 100644 --- a/@zapp/watch/src/Navigator.ts +++ b/@zapp/watch/src/Navigator.ts @@ -1,6 +1,10 @@ import { NavigatorInterface } from '@zapp/core' export class Navigator implements NavigatorInterface { + get currentPage(): string { + throw new Error('Method not implemented.') + } + public navigate(page: string, params: Record) { hmApp.gotoPage({ url: page, param: JSON.stringify(params) }) } diff --git a/@zapp/web/src/HashNavigator.ts b/@zapp/web/src/HashNavigator.ts index 3d96ab7..c3ce84e 100644 --- a/@zapp/web/src/HashNavigator.ts +++ b/@zapp/web/src/HashNavigator.ts @@ -1,40 +1,44 @@ -import { SavedTreeState, WorkingTree } from '@zapp/core' +import { SavedTreeState, WorkingTree, NavigatorInterface } from '@zapp/core' const historyStack: SavedTreeState[] = [] // not using common interface for now due to platform specific differences -export abstract class HashNavigator { - private static routes: Record) => void> - private static _currentRoute: string +export class HashNavigator implements NavigatorInterface { + private routes: Record) => void> + private currentRoute: string - public static register(startingRoute: string, routes: Record) => void>) { + public register(startingRoute: string, routes: Record) => void>) { const routeToRender = window.location.hash.length === 0 ? startingRoute : window.location.hash.substring(1) - HashNavigator.routes = routes - HashNavigator.changeRoute(routeToRender) + this.routes = routes + this.changeRoute(routeToRender) history.replaceState(undefined, '', `#${routeToRender}`) window.addEventListener('popstate', (e) => { WorkingTree.restoreState(historyStack.pop()!) - HashNavigator.changeRoute(window.location.hash.substring(1), e.state) + this.changeRoute(window.location.hash.substring(1), e.state) }) } - public static navigate(route: string, params?: Record) { - if (HashNavigator._currentRoute !== route && HashNavigator.routes[route] !== undefined) { + public navigate(route: string, params?: Record) { + if (this.currentRoute !== route && this.routes[route] !== undefined) { historyStack.push(WorkingTree.saveState()) - HashNavigator.changeRoute(route, params) + this.changeRoute(route, params) history.pushState(params, '', `#${route}`) } } - private static changeRoute(route: string, params?: Record) { - HashNavigator._currentRoute = route + public goBack(): void { + history.back() + } + + private changeRoute(route: string, params?: Record) { + this.currentRoute = route WorkingTree.dropAll() - HashNavigator.routes[route](params) + this.routes[route](params) } - public static get currentRoute(): string { - return HashNavigator._currentRoute + public get currentPage(): string { + return this.currentRoute } } diff --git a/@zapp/web/src/index.ts b/@zapp/web/src/index.ts index 2d2dfb6..e460005 100644 --- a/@zapp/web/src/index.ts +++ b/@zapp/web/src/index.ts @@ -1,8 +1,17 @@ -import { __setViewManager, __setZappInterface } from '@zapp/core' +import { __setViewManager, __setZappInterface, __setNavigator } from '@zapp/core' import { WebViewManager } from './WebViewManager.js' import { ZappWeb } from './ZappWeb.js' +import { HashNavigator } from './HashNavigator.js' -export { HashNavigator } from './HashNavigator.js' +const navigator = new HashNavigator() __setZappInterface(new ZappWeb()) __setViewManager(new WebViewManager()) +__setNavigator(navigator) + +export function registerNavigationRoutes( + startingRoute: string, + routes: Record) => void> +) { + navigator.register(startingRoute, routes) +} diff --git a/web-test/src/NavBar.ts b/web-test/src/NavBar.ts index 992f544..808942b 100644 --- a/web-test/src/NavBar.ts +++ b/web-test/src/NavBar.ts @@ -8,13 +8,13 @@ import { Custom, remember, ColumnConfig, + Navigator, } from '@zapp/core' -import { HashNavigator } from '@zapp/web' function NavButton(text: string, route: string) { Custom(ColumnConfig(`wrapperbutton#${route}`).padding(5, 0), {}, () => { - const defaultColor = route === HashNavigator.currentRoute ? 0x555555 : 0x333333 - const pressedColor = route === HashNavigator.currentRoute ? 0x666666 : 0x444444 + const defaultColor = route === Navigator.currentPage ? 0x555555 : 0x333333 + const pressedColor = route === Navigator.currentPage ? 0x666666 : 0x444444 const pressed = remember(false) const background = remember(defaultColor) @@ -35,7 +35,7 @@ function NavButton(text: string, route: string) { pressed.value = false background.value = defaultColor - HashNavigator.navigate(route, { from: HashNavigator.currentRoute }) + Navigator.navigate(route, { from: Navigator.currentPage }) } }) .onPointerLeave(() => { diff --git a/web-test/src/index.ts b/web-test/src/index.ts index 9661a05..64799a4 100644 --- a/web-test/src/index.ts +++ b/web-test/src/index.ts @@ -1,4 +1,4 @@ -import { HashNavigator } from '@zapp/web' +import { registerNavigationRoutes } from '@zapp/web' import { ActivityIndicator } from '@zapp/ui' import { WorkingTree, @@ -433,7 +433,7 @@ function DynamicLayoutExample() { }) } -HashNavigator.register('dynamicLayout', { +registerNavigationRoutes('dynamicLayout', { dynamicLayout: DynamicLayoutExample, stack: StackExample, column: ColumnExample, From 787dee918cfc7941b4a7100d0b91cd3c8eebac62 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 7 Sep 2022 22:56:31 +0200 Subject: [PATCH 3/4] Make `rememberLauncherForResult` API with implementation on web --- @zapp/core/src/Navigator.ts | 22 ++++++++ @zapp/core/src/index.ts | 3 +- .../effects/rememberLauncherForResult.ts | 29 +++++++++++ @zapp/watch/src/Navigator.ts | 14 ++++- @zapp/web/src/HashNavigator.ts | 42 ++++++++++++++- web-test/src/index.ts | 51 +++++++++++++++++++ 6 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 @zapp/core/src/working_tree/effects/rememberLauncherForResult.ts diff --git a/@zapp/core/src/Navigator.ts b/@zapp/core/src/Navigator.ts index 123119e..80b3b1c 100644 --- a/@zapp/core/src/Navigator.ts +++ b/@zapp/core/src/Navigator.ts @@ -1,7 +1,17 @@ +export interface RegisteredCallback { + targetPage: string + callbackPath: string[] + result?: Record + ready: boolean +} + export interface NavigatorInterface { readonly currentPage: string navigate(route: string, params?: Record): void goBack(): void + registerResultCallback(page: string, path: string[]): void + tryPoppingLauncherResult(page: string, path: string[]): RegisteredCallback | undefined + finishWithResult(params: Record): void } let navigator: NavigatorInterface @@ -18,6 +28,18 @@ export abstract class Navigator { public static goBack(): void { navigator.goBack() } + + public static registerResultCallback(page: string, path: string[]) { + navigator.registerResultCallback(page, path) + } + + public static tryPoppingLauncherResult(page: string, path: string[]): RegisteredCallback | undefined { + return navigator.tryPoppingLauncherResult(page, path) + } + + public static finishWithResult(params: Record) { + navigator.finishWithResult(params) + } } export function setNavigator(navigatorInstance: NavigatorInterface): void { diff --git a/@zapp/core/src/index.ts b/@zapp/core/src/index.ts index 128802d..2b991f2 100644 --- a/@zapp/core/src/index.ts +++ b/@zapp/core/src/index.ts @@ -1,6 +1,7 @@ export type { ConfigBuilderArg } from './working_tree/props/Config.js' export { remember } from './working_tree/effects/remember.js' +export { rememberLauncherForResult } from './working_tree/effects/rememberLauncherForResult.js' export { RememberedMutableValue } from './working_tree/effects/RememberedMutableValue.js' export { sideEffect } from './working_tree/effects/sideEffect.js' export { Arc } from './working_tree/views/Arc.js' @@ -37,5 +38,5 @@ export { ViewManager } from './renderer/ViewManager.js' export { EventManager } from './renderer/EventManager.js' export { NodeType } from './NodeType.js' export { ZappInterface, Zapp, setZappInterface as __setZappInterface } from './ZappInterface.js' -export { NavigatorInterface, Navigator, setNavigator as __setNavigator } from './Navigator.js' +export { NavigatorInterface, Navigator, RegisteredCallback, setNavigator as __setNavigator } from './Navigator.js' export { SavedTreeState } from './working_tree/SavedTreeState.js' diff --git a/@zapp/core/src/working_tree/effects/rememberLauncherForResult.ts b/@zapp/core/src/working_tree/effects/rememberLauncherForResult.ts new file mode 100644 index 0000000..c659e9e --- /dev/null +++ b/@zapp/core/src/working_tree/effects/rememberLauncherForResult.ts @@ -0,0 +1,29 @@ +import { ViewNode } from '../ViewNode.js' +import { WorkingTree } from '../WorkingTree.js' +import { Navigator } from '../../Navigator.js' + +type CallbackType = (result: Record | undefined) => void + +interface LauncherForResult { + launch: (params?: Record) => void +} + +export function rememberLauncherForResult(page: string, callback: CallbackType): LauncherForResult { + const current = WorkingTree.current as ViewNode + const context = current.remember() + + const result = Navigator.tryPoppingLauncherResult(page, context.path.concat(context.id)) + if (result !== undefined && result.ready) { + callback(result.result) + } + + // we don't need to push created node to parent context as we only need the path to trigger + // the correct callback when the opened screen finishes + + return { + launch: (params: Record) => { + Navigator.registerResultCallback(page, context.path.concat(context.id)) + Navigator.navigate(page, params) + }, + } +} diff --git a/@zapp/watch/src/Navigator.ts b/@zapp/watch/src/Navigator.ts index 1083919..59eea5b 100644 --- a/@zapp/watch/src/Navigator.ts +++ b/@zapp/watch/src/Navigator.ts @@ -1,4 +1,4 @@ -import { NavigatorInterface } from '@zapp/core' +import { NavigatorInterface, RegisteredCallback } from '@zapp/core' export class Navigator implements NavigatorInterface { get currentPage(): string { @@ -12,4 +12,16 @@ export class Navigator implements NavigatorInterface { public goBack() { hmApp.goBack() } + + registerResultCallback(page: string, path: string[]): void { + throw new Error('Method not implemented.') + } + + tryPoppingLauncherResult(page: string, path: string[]): RegisteredCallback | undefined { + throw new Error('Method not implemented.') + } + + finishWithResult(params: Record): void { + throw new Error('Method not implemented.') + } } diff --git a/@zapp/web/src/HashNavigator.ts b/@zapp/web/src/HashNavigator.ts index c3ce84e..09275fa 100644 --- a/@zapp/web/src/HashNavigator.ts +++ b/@zapp/web/src/HashNavigator.ts @@ -1,6 +1,7 @@ -import { SavedTreeState, WorkingTree, NavigatorInterface } from '@zapp/core' +import { SavedTreeState, WorkingTree, NavigatorInterface, RegisteredCallback } from '@zapp/core' const historyStack: SavedTreeState[] = [] +const registeredCallbacks: RegisteredCallback[] = [] // not using common interface for now due to platform specific differences export class HashNavigator implements NavigatorInterface { @@ -32,6 +33,45 @@ export class HashNavigator implements NavigatorInterface { history.back() } + registerResultCallback(page: string, path: string[]): void { + registeredCallbacks.push({ + targetPage: page, + callbackPath: path, + ready: false, + }) + } + + tryPoppingLauncherResult(page: string, path: string[]): RegisteredCallback | undefined { + if (registeredCallbacks.length > 0) { + const top = registeredCallbacks[registeredCallbacks.length - 1] + + if (top.ready && page === top.targetPage && top.callbackPath.length === path.length) { + for (let i = 0; i < path.length; i++) { + if (path[i] !== top.callbackPath[i]) { + return undefined + } + } + + return registeredCallbacks.pop() + } + } + + return undefined + } + + finishWithResult(params: Record): void { + if (registeredCallbacks.length > 0) { + const top = registeredCallbacks[registeredCallbacks.length - 1] + + if (top.targetPage === this.currentPage) { + top.ready = true + top.result = params + } + } + + this.goBack() + } + private changeRoute(route: string, params?: Record) { this.currentRoute = route WorkingTree.dropAll() diff --git a/web-test/src/index.ts b/web-test/src/index.ts index 64799a4..2a190c4 100644 --- a/web-test/src/index.ts +++ b/web-test/src/index.ts @@ -24,6 +24,8 @@ import { Arc, ArcConfig, Zapp, + rememberLauncherForResult, + Navigator, } from '@zapp/core' import { NavBar, RouteInfo } from './NavBar' import { Page } from './Page' @@ -37,6 +39,7 @@ const routesInfo: RouteInfo[] = [ { displayName: 'Column example', routeName: 'column' }, { displayName: 'Row example', routeName: 'row' }, { displayName: 'Animation example', routeName: 'animation' }, + { displayName: 'StartForResult example', routeName: 'startForResult' }, ] function StackExample() { @@ -433,10 +436,58 @@ function DynamicLayoutExample() { }) } +function StartForResultExample() { + Page(routesInfo, () => { + Column(ColumnConfig('column').alignment(Alignment.Center).arrangement(Arrangement.Center), () => { + const value = remember('nothing') + const anim = remember(0) + const launcher = rememberLauncherForResult('picker', (result) => { + value.value = result!.res as string + }) + + sideEffect(() => { + anim.value = withTiming(400, { duration: 1000 }) + }) + + Stack(StackConfig('box').width(100).height(100).background(0xff0000).offset(0, anim.value)) + + Text(TextConfig('value-text').textColor(0xffffff).textSize(40), value.value) + + Button(Config('btn'), 'Open', () => { + launcher.launch() + }) + + Button(Config('clear'), 'Clear', () => { + value.value = 'nothing' + }) + }) + }) +} + +function NumberPickerExample() { + Page(routesInfo, () => { + Column(ColumnConfig('column').alignment(Alignment.Center).arrangement(Arrangement.Center), () => { + Button(Config('btn1'), 'Send 1', () => { + Navigator.finishWithResult({ res: '1' }) + }) + + Button(Config('btn2'), 'Send 2', () => { + Navigator.finishWithResult({ res: '2' }) + }) + + Button(Config('btn3'), 'Send 3', () => { + Navigator.finishWithResult({ res: '3' }) + }) + }) + }) +} + registerNavigationRoutes('dynamicLayout', { dynamicLayout: DynamicLayoutExample, stack: StackExample, column: ColumnExample, row: RowExample, animation: AnimationExample, + startForResult: StartForResultExample, + picker: NumberPickerExample, }) From 67dac414fa41b51e70dbae30fa4a60eb1acc69c1 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 7 Sep 2022 23:00:18 +0200 Subject: [PATCH 4/4] Remove remember type --- .../src/working_tree/effects/RememberedValue.ts | 15 +-------------- @zapp/core/src/working_tree/effects/remember.ts | 3 --- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/@zapp/core/src/working_tree/effects/RememberedValue.ts b/@zapp/core/src/working_tree/effects/RememberedValue.ts index eb94ab1..670decb 100644 --- a/@zapp/core/src/working_tree/effects/RememberedValue.ts +++ b/@zapp/core/src/working_tree/effects/RememberedValue.ts @@ -1,20 +1,12 @@ import { RememberNode } from '../RememberNode.js' -export enum RememberedValueType { - Immutable, - Mutable, - Launcher, -} - export class RememberedValue { protected _value: T protected context: RememberNode - protected type: RememberedValueType constructor(val: T, context: RememberNode) { this._value = val this.context = context - this.type = RememberedValueType.Immutable } public get value() { @@ -26,13 +18,8 @@ export class RememberedValue { this.context = context } - /** @internal */ - public setType(type: RememberedValueType) { - this.type = type - } - /** @internal */ public shouldBeSaved() { - return this.type !== RememberedValueType.Launcher && typeof this._value !== 'function' + return typeof this._value !== 'function' } } diff --git a/@zapp/core/src/working_tree/effects/remember.ts b/@zapp/core/src/working_tree/effects/remember.ts index a83828a..74b6bb4 100644 --- a/@zapp/core/src/working_tree/effects/remember.ts +++ b/@zapp/core/src/working_tree/effects/remember.ts @@ -3,7 +3,6 @@ import { RememberNode } from '../RememberNode.js' import { ViewNode } from '../ViewNode.js' import { WorkingTree } from '../WorkingTree.js' import { RememberedMutableValue } from './RememberedMutableValue.js' -import { RememberedValueType } from './RememberedValue.js' export function remember(value: T): RememberedMutableValue { const current = WorkingTree.current as ViewNode @@ -29,10 +28,8 @@ export function remember(value: T): RememberedMutableValue { } const result = savedRemembered === null ? new RememberedMutableValue(value, context) : savedRemembered - result.setType(RememberedValueType.Mutable) context.remembered = result - current.children.push(context) return result