Skip to content

Commit

Permalink
Merge branch 'feature/tanstack-router' into feature/next
Browse files Browse the repository at this point in the history
  • Loading branch information
gius committed Feb 18, 2023
2 parents 754a6b5 + 4c65452 commit 61ce345
Show file tree
Hide file tree
Showing 20 changed files with 469 additions and 164 deletions.
2 changes: 1 addition & 1 deletion examples/navigation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"dependencies": {
"@frui.ts/views": "^999.0.0",
"@tanstack/react-location": "^3.7.4",
"@tanstack/react-router": "^0.0.1-beta.82",
"mobx": "6.7.0",
"mobx-react-lite": "3",
"react": "^18.2.0",
Expand Down
22 changes: 18 additions & 4 deletions examples/navigation/src/customers/customersViewModel.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
import type { IViewModel, NavigationContext, NoParams } from "@frui.ts/views";
import { makeObservable, observable, runInAction } from "mobx";
import type { IViewModel, SearchType } from "@frui.ts/views";

export default class CustomersViewModel implements IViewModel {
type SearchScheme = { name?: string };

export default class CustomersViewModel implements IViewModel<NoParams, SearchScheme> {
@observable
search?: string;

constructor() {
makeObservable(this);
}

onNavigate(search: SearchType) {
onNavigate({ search }: NavigationContext<NoParams, SearchScheme>) {
console.log("customers navigate", search);

runInAction(() => {
this.search = search.name as string;
this.search = search.name;
});
}

onDeactivate(context: NavigationContext<NoParams, SearchScheme>) {
console.log("customers deactivate");
}

static validateSearch(search: Record<string, unknown>) {
return {
name: search.name as string | undefined,
};
}
}
31 changes: 24 additions & 7 deletions examples/navigation/src/home/homeView.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
import { registerViewComponent } from "@frui.ts/views";
import { Link, Outlet } from "@tanstack/react-location";
import { Link, Outlet } from "@tanstack/react-router";
import { Observer } from "mobx-react-lite";
import React from "react";
import HomeViewModel from "./homeViewModel";

export const HomeView = registerViewComponent(HomeViewModel, vm => {
const linkProps = {
activeProps: { style: { color: "red" } },
activeOptions: {
exact: true,
},
};

return (
<div>
<header>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
<Link to="/" {...linkProps}>
Home
</Link>
</li>
<li>
<Link to="/invoices">Invoices</Link>
<Link to="/invoices" {...linkProps}>
Invoices
</Link>
<ul>
<li>
<Link to="/invoices/4">Invoice 4</Link>
<Link to="/invoices/$invoiceId" params={{ invoiceId: "4" }} {...linkProps}>
Invoice 4
</Link>
</li>
<li>
<Link to="/invoices/5">Invoice 5</Link>
<Link to="/invoices/$invoiceId" params={{ invoiceId: "5" }} {...linkProps}>
Invoice 5
</Link>
</li>
</ul>
</li>
<li>
<Link to="/customers">Customers</Link>
<Link to="/customers" {...linkProps}>
Customers
</Link>
<ul>
<li>
<Link to="/customers" search={{ name: "abc" }}>
<Link to="/customers" search={{ name: "abc" }} {...linkProps}>
Customers search
</Link>
</li>
Expand Down
1 change: 0 additions & 1 deletion examples/navigation/src/home/homeViewModel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { IViewModel } from "@frui.ts/views";
import { action, makeObservable, observable } from "mobx";

export default class HomeViewModel implements IViewModel {
text = "foo";

Expand Down
10 changes: 6 additions & 4 deletions examples/navigation/src/invoices/invoiceDetailViewModel.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type { IViewModel, NavigationContext } from "@frui.ts/views";
import { makeObservable, observable, runInAction } from "mobx";
import type { IViewModel, RouteMatch } from "@frui.ts/views";

export default class InvoiceDetailViewModel implements IViewModel {
type ParamsScheme = Record<"invoiceId", string>;

export default class InvoiceDetailViewModel implements IViewModel<ParamsScheme> {
@observable
id = -1;

constructor() {
makeObservable(this);
}

onInitialize(routeMatch: RouteMatch) {
onNavigate({ params }: NavigationContext<ParamsScheme>) {
runInAction(() => {
this.id = +routeMatch.params.id;
this.id = +params.invoiceId;
});
}
}
2 changes: 1 addition & 1 deletion examples/navigation/src/invoices/invoicesView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { registerViewComponent } from "@frui.ts/views";
import { Outlet } from "@tanstack/react-location";
import { Outlet } from "@tanstack/react-router";
import React from "react";
import InvoicesViewModel from "./invoicesViewModel";

Expand Down
14 changes: 7 additions & 7 deletions examples/navigation/src/invoices/invoicesViewModel.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type { IViewModel, RouteMatch } from "@frui.ts/views";
import type { IViewModel, NavigationContext } from "@frui.ts/views";

export default class InvoicesViewModel implements IViewModel {
onInitialize(routeMatch: RouteMatch) {
console.log("invoices on initialize", routeMatch);
onInitialize(context: NavigationContext) {
console.log("invoices on initialize", context);
}

onActivate(routeMatch: RouteMatch) {
console.log("invoices on activate", routeMatch);
onActivate(context: NavigationContext) {
console.log("invoices on activate", context);
}

onDeactivate(routeMatch: RouteMatch) {
console.log("invoices on deactivate", routeMatch);
onDeactivate(context: NavigationContext) {
console.log("invoices on deactivate", context);
}
}
69 changes: 41 additions & 28 deletions examples/navigation/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { LocationGenerics } from "@frui.ts/views";
import { buildRoutes, RouteView } from "@frui.ts/views";
import { ReactLocation, Router } from "@tanstack/react-location";
import { buildRootRoute, buildRoute } from "@frui.ts/views";
import { ReactRouter, Route, RouterProvider } from "@tanstack/react-router";
import React from "react";
import { createRoot } from "react-dom/client";
import CustomersViewModel from "./customers/customersViewModel";
Expand All @@ -10,37 +9,51 @@ import InvoiceDetailViewModel from "./invoices/invoiceDetailViewModel";
import InvoicesViewModel from "./invoices/invoicesViewModel";
import "./viewsRegistry";

const location = new ReactLocation<LocationGenerics>();

const routes = buildRoutes([
{
path: "/",
vmFactory: () => new HomeViewModel(),
children: [
{
path: "invoices",
vmFactory: () => new InvoicesViewModel(),
children: [
{
path: ":id",
vmFactory: () => new InvoiceDetailViewModel(),
},
{ element: "invoices list" },
],
},
{
path: "customers",
vmFactory: () => new CustomersViewModel(),
},
],
},
const homeRoute = buildRootRoute(() => new HomeViewModel(), {});

const indexRoute = new Route({ getParentRoute: () => homeRoute, path: "/" });

const invoicesRoute = buildRoute(() => new InvoicesViewModel(), {
getParentRoute: () => homeRoute,
path: "invoices",
});

const invoiceDetailRoute = buildRoute(() => new InvoiceDetailViewModel(), {
getParentRoute: () => invoicesRoute,
path: "$invoiceId",
});

const defaultInvoiceRoute = new Route({
getParentRoute: () => invoicesRoute,
path: "/",
component: () => <div>Invoices list</div>,
});

const customersRoute = buildRoute(() => new CustomersViewModel(), {
getParentRoute: () => homeRoute,
path: "customers",
validateSearch: CustomersViewModel.validateSearch,
});

const routeTree = homeRoute.addChildren([
indexRoute,
invoicesRoute.addChildren([invoiceDetailRoute, defaultInvoiceRoute]),
customersRoute,
]);

const router = new ReactRouter({ routeTree });

declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}

const container = document.getElementById("root");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const root = createRoot(container!);
root.render(
<React.StrictMode>
<Router location={location} routes={routes} defaultElement={<RouteView />} />
<RouterProvider router={router} />
</React.StrictMode>
);
2 changes: 1 addition & 1 deletion examples/todolist/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"dependencies": {
"@frui.ts/htmlcontrols": "^999.0.0",
"@frui.ts/views": "^999.0.0",
"@tanstack/react-location": "^3.7.4",
"@tanstack/react-router": "^0.0.1-beta.82",
"mobx": "6.7.0",
"mobx-react-lite": "3",
"react": "^18.2.0",
Expand Down
6 changes: 3 additions & 3 deletions examples/todolist/src/list/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createViewComponent } from "@frui.ts/views";
import { Link } from "@tanstack/react-location";
import { Link } from "@tanstack/react-router";
import React from "react";
import { pluralize } from "../helpers";
import type TodoListViewModel from "./todoListViewModel";
Expand All @@ -20,12 +20,12 @@ export const Footer = createViewComponent<TodoListViewModel>(vm => {
</Link>
</li>
<li>
<Link to="/active" className={getFilterClass("active")}>
<Link to="/$filter" params={{ filter: "active" }} className={getFilterClass("active")}>
Active
</Link>
</li>
<li>
<Link to="/completed" className={getFilterClass("completed")}>
<Link to="/$filter" params={{ filter: "completed" }} className={getFilterClass("completed")}>
Completed
</Link>
</li>
Expand Down
27 changes: 19 additions & 8 deletions examples/todolist/src/list/todoListViewModel.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { IViewModel } from "@frui.ts/views";
import { type RouteMatch } from "@frui.ts/views";
import { action, computed, makeObservable, observable } from "mobx";
import type { IViewModel, NavigationContext } from "@frui.ts/views";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import { v4 as uuid } from "uuid";
import { type TodoItem } from "../models/todoItem";

type FilterType = "all" | "completed" | "active";

export default class TodoListViewModel implements IViewModel {
type ParamsScheme = Record<"filter", string>;
export default class TodoListViewModel implements IViewModel<ParamsScheme> {
name = "TODO List";
@observable list: TodoItem[] = [];
@observable newItem!: TodoItem;
Expand Down Expand Up @@ -38,12 +37,24 @@ export default class TodoListViewModel implements IViewModel {
}

onInitialize() {
console.log("initializing");
this.setNewItem();
}

@action onNavigate(routeMatch: RouteMatch) {
console.log("navigate", routeMatch.params, routeMatch.pathname, routeMatch);
// TODO set filter
onDeactivate(context: NavigationContext<ParamsScheme, unknown>) {
console.log("deactivating", context);
}

onNavigate(routeMatch: NavigationContext<ParamsScheme>) {
switch (routeMatch.params.filter) {
case "completed":
case "active":
runInAction(() => (this.filter = routeMatch.params.filter as FilterType));
break;
default:
runInAction(() => (this.filter = "all"));
break;
}
}

@action private setNewItem() {
Expand Down
29 changes: 19 additions & 10 deletions examples/todolist/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import type { LocationGenerics } from "@frui.ts/views";
import { buildRoutes, RouteView } from "@frui.ts/views";
import { ReactLocation, Router } from "@tanstack/react-location";
import { buildRoute } from "@frui.ts/views";
import { ReactRouter, RootRoute, RouterProvider } from "@tanstack/react-router";
import React from "react";
import { createRoot } from "react-dom/client";
import "todomvc-app-css/index.css";
import "todomvc-common/base.css";
import ListViewModel from "./list/todoListViewModel";
import "./viewsRegistry";

const location = new ReactLocation<LocationGenerics>();
const rootRoute = new RootRoute();
const homeRoute = buildRoute(() => new ListViewModel(), {
getParentRoute: () => rootRoute,
path: "$filter",
});

const routes = buildRoutes([
{
vmFactory: () => new ListViewModel(),
},
]);
const routeTree = rootRoute.addChildren([homeRoute]);

const router = new ReactRouter({
routeTree,
});

declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}

const container = document.getElementById("root");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const root = createRoot(container!);
root.render(
<React.StrictMode>
<Router location={location} routes={routes} defaultElement={<RouteView />} />
<RouterProvider router={router} />
</React.StrictMode>
);
2 changes: 1 addition & 1 deletion packages/views/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
},
"dependencies": {
"@frui.ts/helpers": "^999.0.0",
"@tanstack/react-location": "^3.7.4",
"@tanstack/react-router": "^0.0.1-beta.82",
"inversify": "^6.0.1",
"lodash-es": "^4.17.21"
},
Expand Down
6 changes: 4 additions & 2 deletions packages/views/src/binding/useBinding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ export function getValue<
TProperty extends TypedBindingProperty<TTarget, TValueRestriction>
>(target: TTarget | undefined, property: TProperty | undefined, ensureObservable = true): TValueRestriction {
if (!target) {
throw new Error("'target' prop has not been set");
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Cannot read property '${property}', because target has not been set`);
}
if (property === undefined) {
throw new Error("'property' prop has not been set");
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`'property' prop has not been set for target '${target}'`);
}

if (isObservableMap(target)) {
Expand Down

0 comments on commit 61ce345

Please sign in to comment.