Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make rememberLauncherForResult API with web implementation #19

Merged
merged 4 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions @zapp/core/src/Navigator.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
export interface RegisteredCallback {
targetPage: string
callbackPath: string[]
result?: Record<string, unknown>
ready: boolean
}

export interface NavigatorInterface {
readonly currentPage: string
navigate(route: string, params?: Record<string, unknown>): void
goBack(): void
registerResultCallback(page: string, path: string[]): void
tryPoppingLauncherResult(page: string, path: string[]): RegisteredCallback | undefined
finishWithResult(params: Record<string, unknown>): void
}

let navigator: NavigatorInterface

export abstract class Navigator {
public static get currentPage(): string {
return navigator.currentPage
}

public static navigate(route: string, params?: Record<string, unknown>): void {
navigator.navigate(route, params)
}

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<string, unknown>) {
navigator.finishWithResult(params)
}
}

export function setNavigator(navigatorInstance: NavigatorInterface): void {
Expand Down
3 changes: 2 additions & 1 deletion @zapp/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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'
2 changes: 1 addition & 1 deletion @zapp/core/src/working_tree/SavedTreeState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions @zapp/core/src/working_tree/effects/RememberedValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ export class RememberedValue<T> {
public switchContext(context: RememberNode) {
this.context = context
}

/** @internal */
public shouldBeSaved() {
return typeof this._value !== 'function'
}
}
1 change: 0 additions & 1 deletion @zapp/core/src/working_tree/effects/remember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export function remember<T>(value: T): RememberedMutableValue<T> {
const result = savedRemembered === null ? new RememberedMutableValue(value, context) : savedRemembered

context.remembered = result

current.children.push(context)

return result
Expand Down
29 changes: 29 additions & 0 deletions @zapp/core/src/working_tree/effects/rememberLauncherForResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ViewNode } from '../ViewNode.js'
import { WorkingTree } from '../WorkingTree.js'
import { Navigator } from '../../Navigator.js'

type CallbackType = (result: Record<string, unknown> | undefined) => void

interface LauncherForResult {
launch: (params?: Record<string, unknown>) => 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<string, unknown>) => {
Navigator.registerResultCallback(page, context.path.concat(context.id))
Navigator.navigate(page, params)
},
}
}
18 changes: 17 additions & 1 deletion @zapp/watch/src/Navigator.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { NavigatorInterface } from '@zapp/core'
import { NavigatorInterface, RegisteredCallback } from '@zapp/core'

export class Navigator implements NavigatorInterface {
get currentPage(): string {
throw new Error('Method not implemented.')
}

public navigate(page: string, params: Record<string, unknown>) {
hmApp.gotoPage({ url: page, param: JSON.stringify(params) })
}

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<string, unknown>): void {
throw new Error('Method not implemented.')
}
}
76 changes: 60 additions & 16 deletions @zapp/web/src/HashNavigator.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,84 @@
import { SavedTreeState, WorkingTree } 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 abstract class HashNavigator {
private static routes: Record<string, (params?: Record<string, unknown>) => void>
private static _currentRoute: string
export class HashNavigator implements NavigatorInterface {
private routes: Record<string, (params?: Record<string, unknown>) => void>
private currentRoute: string

public static register(startingRoute: string, routes: Record<string, (params?: Record<string, unknown>) => void>) {
public register(startingRoute: string, routes: Record<string, (params?: Record<string, unknown>) => 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<string, unknown>) {
if (HashNavigator._currentRoute !== route && HashNavigator.routes[route] !== undefined) {
public navigate(route: string, params?: Record<string, unknown>) {
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<string, unknown>) {
HashNavigator._currentRoute = route
public goBack(): void {
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<string, unknown>): 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<string, unknown>) {
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
}
}
13 changes: 11 additions & 2 deletions @zapp/web/src/index.ts
Original file line number Diff line number Diff line change
@@ -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<string, (params?: Record<string, unknown>) => void>
) {
navigator.register(startingRoute, routes)
}
8 changes: 4 additions & 4 deletions web-test/src/NavBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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(() => {
Expand Down
55 changes: 53 additions & 2 deletions web-test/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HashNavigator } from '@zapp/web'
import { registerNavigationRoutes } from '@zapp/web'
import { ActivityIndicator } from '@zapp/ui'
import {
WorkingTree,
Expand All @@ -24,6 +24,8 @@ import {
Arc,
ArcConfig,
Zapp,
rememberLauncherForResult,
Navigator,
} from '@zapp/core'
import { NavBar, RouteInfo } from './NavBar'
import { Page } from './Page'
Expand All @@ -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() {
Expand Down Expand Up @@ -433,10 +436,58 @@ function DynamicLayoutExample() {
})
}

HashNavigator.register('dynamicLayout', {
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,
})