Skip to content

Commit

Permalink
Minor refactoring in navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
gius committed Dec 14, 2021
1 parent 39d0e47 commit 4e60be2
Show file tree
Hide file tree
Showing 22 changed files with 128 additions and 66 deletions.
4 changes: 3 additions & 1 deletion packages/datascreens/src/dataListBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { IPagingInfo } from "@frui.ts/data";
import { observable } from "mobx";

export default abstract class DataListBase<T> implements IPagingInfo {
@observable.shallow itemsValue?: T[];
@observable.shallow
private itemsValue?: T[];

get items() {
return this.itemsValue;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/datascreens/src/detailViewModel.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Awaitable } from "@frui.ts/helpers";
import { BusyWatcher, ScreenBase } from "@frui.ts/screens";
import { action, observable } from "mobx";

Expand All @@ -16,5 +17,5 @@ export default abstract class DetailViewModel<TEntity> extends ScreenBase {
this.setItem(item);
}
}
protected abstract loadDetail(): Promise<TEntity | undefined> | TEntity | undefined;
protected abstract loadDetail(): Awaitable<TEntity | undefined>;
}
3 changes: 2 additions & 1 deletion packages/datascreens/src/filteredList.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { PagedQueryResult } from "@frui.ts/data";
import { IPagingFilter } from "@frui.ts/data";
import { attachDirtyWatcher, AutomaticDirtyWatcher } from "@frui.ts/dirtycheck";
import type { Awaitable } from "@frui.ts/helpers";
import { bound } from "@frui.ts/helpers";
import { validate } from "@frui.ts/validation";
import { action, isArrayLike, observable, runInAction } from "mobx";
Expand Down Expand Up @@ -38,7 +39,7 @@ export default class FilteredList<
}

constructor(
public onLoadData: (filter: TFilter, paging: IPagingFilter) => Promise<PagedQueryResult<TEntity> | void>,
public onLoadData: (filter: TFilter, paging: IPagingFilter) => Awaitable<PagedQueryResult<TEntity> | void>,
private initFilter: () => TFilter = () => ({} as TFilter),
private defaultPagingFilter: (previous?: Readonly<IPagingFilter>) => IPagingFilter = previous => ({
limit: FilteredList.defaultPageSize,
Expand Down
2 changes: 2 additions & 0 deletions packages/helpers/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type Awaitable<T> = T | PromiseLike<T>;

export type BindingTarget = Map<any, any> | Record<string, any>;

export type PropertyName<TTarget> = keyof TTarget & string;
Expand Down
4 changes: 2 additions & 2 deletions packages/screens/__tests__/router/urlRouterBase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("UrlRouterBase", () => {
describe("initialize", () => {
it("serializes and sets the current path", async () => {
const navigator = mock<ScreenNavigator>();
navigator.getNavigationState.mockReturnValue({ name: "my-screen" });
navigator.getNavigationState.mockReturnValue([{ name: "my-screen" }]);

const router = new TestRouter(navigator);

Expand All @@ -28,7 +28,7 @@ describe("UrlRouterBase", () => {
describe("navigate", () => {
it("deserializes path and calls navigate", async () => {
const navigator = mock<ScreenNavigator>();
navigator.getNavigationState.mockReturnValue({ name: "my-screen" });
navigator.getNavigationState.mockReturnValue([{ name: "my-screen" }]);

const router = new TestRouter(navigator);

Expand Down
3 changes: 2 additions & 1 deletion packages/screens/src/busyWatcher.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Awaitable } from "@frui.ts/helpers";
import { action, computed, ObservableMap } from "mobx";

export type BusyWatcherKey = string | symbol;
Expand Down Expand Up @@ -60,7 +61,7 @@ export function watchBusy(target: any, propertyKey?: string, descriptor?: Proper
const key = isCustomKey ? (target as BusyWatcherKey) : Symbol();

const decorator: Decorator = (target, propertyKey, descriptor) => {
const originalFunction = descriptor.value as (...args: any) => Promise<unknown> | unknown;
const originalFunction = descriptor.value as (...args: any) => Awaitable<unknown>;

descriptor.value = function (this: { busyWatcher?: BusyWatcher }, ...args: any[]) {
const ticket = this.busyWatcher?.getBusyTicket(key);
Expand Down
2 changes: 2 additions & 0 deletions packages/screens/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from "./models/findChildResult";
export * from "./models/navigationContext";
export * from "./models/pathElements";
export { default as ScreenBase, getNavigator } from "./screens/screenBase";

export * from "./navigation/types";
Expand Down
20 changes: 20 additions & 0 deletions packages/screens/src/models/findChildResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { PathElement } from "./pathElements";

export type FindChildResult<TChild> = ChildFoundResult<TChild> | NoChildResult;

export interface ChildFoundResult<TChild> {
newChild: TChild;
closePrevious?: boolean;

pathForChild?: PathElement[];
attachToParent?: boolean;
}

export interface NoChildResult {
newChild: undefined;
closePrevious?: boolean;
}

export function isChildFoundResult<T>(result: FindChildResult<T>): result is ChildFoundResult<T> {
return !!result.newChild;
}
2 changes: 1 addition & 1 deletion packages/screens/src/models/navigationContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ScreenNavigator } from "../navigation/types";
import type PathElement from "./pathElements";
import type { PathElement } from "./pathElements";

export interface NavigationContext<TScreen = unknown, TNavigationParams = unknown> {
screen?: TScreen;
Expand Down
4 changes: 2 additions & 2 deletions packages/screens/src/models/pathElements.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default interface PathElement {
export interface PathElement<TParamsValue = string | undefined> {
name: string;
params?: Record<string, string>;
params?: Record<string, TParamsValue>;
}
29 changes: 18 additions & 11 deletions packages/screens/src/navigation/conductors/activeChildConductor.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import type { Awaitable } from "@frui.ts/helpers";
import { computed, observable, runInAction } from "mobx";
import type { FindChildResult } from "../../models/findChildResult";
import { isChildFoundResult } from "../../models/findChildResult";
import type { ClosingNavigationContext, NavigationContext } from "../../models/navigationContext";
import type PathElement from "../../models/pathElements";
import type { PathElement } from "../../models/pathElements";
import { getNavigator } from "../../screens/screenBase";
import LifecycleScreenNavigatorBase from "../lifecycleScreenNavigatorBase";
import type { LifecycleScreenNavigator, ScreenNavigator } from "../types";

export default class ActiveChildConductor<
TChild = unknown,
TScreen = any,
TNavigationParams extends Record<string, string> = Record<string, string>
TNavigationParams extends Record<string, string | undefined> = Record<string, string | undefined>
> extends LifecycleScreenNavigatorBase<TScreen, TNavigationParams> {
@observable.ref private activeChildValue?: TChild = undefined;

Expand All @@ -17,13 +20,13 @@ export default class ActiveChildConductor<
}

// extension point, implement this to decide what navigate should do
canChangeActiveChild?: (context: NavigationContext<TScreen>, currentChild: TChild | undefined) => Promise<boolean>;
canChangeActiveChild?: (context: NavigationContext<TScreen>, currentChild: TChild | undefined) => Awaitable<boolean>;

// extension point, implement this to decide what navigate should do
findNavigationChild?: (
context: NavigationContext<TScreen>,
currentChild: TChild | undefined
) => Promise<{ newChild: TChild | undefined; closePrevious?: boolean }>;
) => Awaitable<FindChildResult<TChild>>;

// default functionality overrides

Expand Down Expand Up @@ -72,18 +75,22 @@ export default class ActiveChildConductor<
await this.callAll("onNavigate", context);

const currentChild = this.activeChild;
const { newChild, closePrevious } = await this.findNavigationChild(context, currentChild);
const childResult = await this.findNavigationChild(context, currentChild);

if (currentChild !== newChild) {
if (currentChild !== childResult.newChild) {
const currentChildNavigator = getNavigator<LifecycleScreenNavigator>(currentChild);
await currentChildNavigator?.deactivate?.(!!closePrevious);
await currentChildNavigator?.deactivate?.(!!childResult.closePrevious);

runInAction(() => (this.activeChildValue = newChild));
runInAction(() => (this.activeChildValue = childResult.newChild));
}

if (newChild) {
const newChildNavigator = getNavigator<LifecycleScreenNavigator>(newChild);
await newChildNavigator?.navigate(path.slice(1));
if (isChildFoundResult(childResult)) {
if (childResult.attachToParent !== false) {
this.connectChild(childResult.newChild);
}

const newChildNavigator = getNavigator<LifecycleScreenNavigator>(childResult.newChild);
await newChildNavigator?.navigate(childResult.pathForChild ?? path.slice(1));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type ScreenLifecycleEventHub from "../screenLifecycleEventHub";
export default class AllChildrenActiveConductor<
TChild = unknown,
TScreen = any,
TNavigationParams extends Record<string, string> = Record<string, string>
TNavigationParams extends Record<string, string | undefined> = Record<string, string | undefined>
> extends LifecycleScreenNavigatorBase<TScreen, TNavigationParams> {
readonly children: TChild[];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { IArrayWillChange, IArrayWillSplice } from "mobx";
import { observable } from "mobx";
import type { FindChildResult } from "../../models/findChildResult";
import type { NavigationContext } from "../../models/navigationContext";
import { getNavigator } from "../../screens/screenBase";
import type ScreenLifecycleEventHub from "../screenLifecycleEventHub";
Expand All @@ -9,7 +10,7 @@ import ActiveChildConductor from "./activeChildConductor";
export default class OneOfListActiveConductor<
TChild = unknown,
TScreen = any,
TNavigationParams extends Record<string, string> = Record<string, string>
TNavigationParams extends Record<string, string | undefined> = Record<string, string | undefined>
> extends ActiveChildConductor<TChild, TScreen, TNavigationParams> {
readonly children: TChild[];

Expand Down Expand Up @@ -38,13 +39,12 @@ export default class OneOfListActiveConductor<
findNavigationChild = (context: NavigationContext<TScreen>, currentChild: TChild | undefined) => {
const searchedNavigationName = context.path[1]?.name;
const newChild = this.findChild(searchedNavigationName);
const result = { newChild, closePrevious: false };
return Promise.resolve(result);
return { newChild, closePrevious: false } as FindChildResult<TChild>;
};

private findChild(navigationName: string | undefined) {
if (this.preserveActiveChild && navigationName === undefined) {
return this.activeChild;
return this.activeChild ?? this.children[0];
}

return navigationName !== undefined ? this.children.find(x => getNavigator(x)?.navigationName === navigationName) : undefined;
Expand Down
4 changes: 2 additions & 2 deletions packages/screens/src/navigation/debugHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { get, isArrayLike } from "mobx";
import type PathElement from "../models/pathElements";
import type { PathElement } from "../models/pathElements";
import type UrlRouterBase from "../router/urlRouterBase";
import type ScreenBase from "../screens/screenBase";
import { getNavigator } from "../screens/screenBase";
Expand All @@ -10,7 +10,7 @@ interface ViewModelInfo {
name?: string;
navigationPath?: string;
navigationName?: string;
navigationState?: PathElement;
navigationState?: PathElement[];
activeChild?: ViewModelInfo;
children?: ViewModelInfo[];
instance: any;
Expand Down
10 changes: 6 additions & 4 deletions packages/screens/src/navigation/lifecycleScreenNavigatorBase.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { computed, observable, runInAction } from "mobx";
import type { ClosingNavigationContext, NavigationContext } from "../models/navigationContext";
import type PathElement from "../models/pathElements";
import type { PathElement } from "../models/pathElements";
import type { HasLifecycleEvents } from "../screens/hasLifecycleHandlers";
import type ScreenBase from "../screens/screenBase";
import type ScreenLifecycleEventHub from "./screenLifecycleEventHub";
import type { LifecycleScreenNavigator, ScreenNavigator } from "./types";

export default abstract class LifecycleScreenNavigatorBase<
TScreen extends Partial<HasLifecycleEvents> & Partial<ScreenBase>,
TNavigationParams extends Record<string, string>
TNavigationParams extends Record<string, string | undefined>
> implements LifecycleScreenNavigator
{
// extension point - you can either set getNavigationName function, or assign navigationName property
Expand Down Expand Up @@ -44,8 +44,10 @@ export default abstract class LifecycleScreenNavigatorBase<
return this.aggregateBooleanAll("canNavigate", context);
}

getNavigationParams?: () => TNavigationParams;
getNavigationState(): PathElement {
getNavigationParams?: () => TNavigationParams | undefined;
getNavigationState: () => PathElement[] = () => [this.createDefaultNavigationState()];

createDefaultNavigationState() {
return {
name: this.navigationName,
params: this.getNavigationParams?.(),
Expand Down
13 changes: 11 additions & 2 deletions packages/screens/src/navigation/simpleScreenNavigator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import type { HasLifecycleEvents } from "../screens/hasLifecycleHandlers";
import type ScreenBase from "../screens/screenBase";
import LifecycleScreenNavigatorBase from "./lifecycleScreenNavigatorBase";
import type ScreenLifecycleEventHub from "./screenLifecycleEventHub";

export default class SimpleScreenNavigator<
TScreen extends Partial<HasLifecycleEvents> & Partial<ScreenBase> = any,
TNavigationParams extends Record<string, string> = Record<string, string>
> extends LifecycleScreenNavigatorBase<TScreen, TNavigationParams> {}
TNavigationParams extends Record<string, string | undefined> = Record<string, string | undefined>
> extends LifecycleScreenNavigatorBase<TScreen, TNavigationParams> {
constructor(screen?: TScreen, navigationPrefix?: string, eventHub?: ScreenLifecycleEventHub<TScreen>) {
super(screen, eventHub);

if (navigationPrefix) {
this.getNavigationState = () => [{ name: navigationPrefix }, this.createDefaultNavigationState()];
}
}
}
9 changes: 5 additions & 4 deletions packages/screens/src/navigation/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import type PathElement from "../models/pathElements";
import type { Awaitable } from "@frui.ts/helpers";
import type { PathElement } from "../models/pathElements";

export interface ScreenNavigator {
readonly isActive: boolean;

canNavigate(path: PathElement[]): Promise<boolean> | boolean;
canNavigate(path: PathElement[]): Awaitable<boolean>;
navigate(path: PathElement[]): Promise<void>;

navigationName: string;
getNavigationState(): PathElement;
getNavigationState(): PathElement[];

parent: ScreenNavigator | undefined;
getPrimaryChild(): ScreenNavigator | undefined;
}

export interface LifecycleScreenNavigator extends ScreenNavigator {
canDeactivate(isClosing: boolean): Promise<boolean> | boolean;
canDeactivate(isClosing: boolean): Awaitable<boolean>;
deactivate(isClosing: boolean): Promise<void>;
}
12 changes: 6 additions & 6 deletions packages/screens/src/router/routerBase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type PathElement from "../models/pathElements";
import type { PathElement } from "../models/pathElements";
import type { ScreenNavigator } from "../navigation/types";

export default abstract class RouterBase {
Expand All @@ -13,7 +13,7 @@ export default abstract class RouterBase {

let navigator = this.rootNavigator;
while (navigator) {
path.push(navigator.getNavigationState());
path.push(...navigator.getNavigationState());
navigator = navigator.getPrimaryChild();
}

Expand All @@ -25,20 +25,20 @@ export default abstract class RouterBase {

const childPath = child?.getNavigationState();
if (childPath) {
path.push(childPath);
path.push(...childPath);
}

let navigator: ScreenNavigator | undefined = parent;
while (navigator) {
path.unshift(navigator.getNavigationState());
path.unshift(...navigator.getNavigationState());
navigator = navigator.parent;
}

return path;
}

protected cloneWithChildPath(path: PathElement[], child: ScreenNavigator | undefined) {
protected cloneWithChildPath(path: PathElement[], child: ScreenNavigator | undefined): PathElement[] {
const childPath = child?.getNavigationState();
return childPath ? [...path, childPath] : path;
return childPath ? [...path, ...childPath] : path;
}
}
Loading

0 comments on commit 4e60be2

Please sign in to comment.