feature: Add reactvvm lib
This commit is contained in:
parent
77b167c695
commit
2c9b9c5b8f
@ -32,6 +32,7 @@
|
|||||||
"react-cookie": "^7.2.2",
|
"react-cookie": "^7.2.2",
|
||||||
"react-dom": "19.0.0-rc-69d4b800-20241021",
|
"react-dom": "19.0.0-rc-69d4b800-20241021",
|
||||||
"react-i18next": "^15.1.0",
|
"react-i18next": "^15.1.0",
|
||||||
|
"reactvvm": "^1.1.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
|
@ -15,7 +15,7 @@ type LinkItem = {
|
|||||||
* so they come together always and there is no need to be connected with interface for reusable
|
* so they come together always and there is no need to be connected with interface for reusable
|
||||||
* vms.
|
* vms.
|
||||||
*/
|
*/
|
||||||
export default function navLinkPersonalVM() {
|
export default function useNavLinkPersonalVM() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
// Map of links to display in the side navigation.
|
// Map of links to display in the side navigation.
|
||||||
// Depending on the size of the application, this would be stored in a database.
|
// Depending on the size of the application, this would be stored in a database.
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import navLinkPersonalVM from "@/app/[lang]/dashboard/components/client/nav-links/nav-link.personal-vm";
|
import useNavLinkPersonalVM from "@/app/[lang]/dashboard/components/client/nav-links/nav-link.personal-vm";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function NavLinks() {
|
export default function NavLinks() {
|
||||||
const { links, isLinkActive } = navLinkPersonalVM();
|
const { links, isLinkActive } = useNavLinkPersonalVM();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{links.map((link) => {
|
{links.map((link) => {
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
import SideNav from "@/app/[lang]/dashboard/components/server/sidenav";
|
import SideNav from "@/app/[lang]/dashboard/components/server/sidenav";
|
||||||
import dashboardAppModule from "@/app/[lang]/dashboard/module/dashboard.app-module";
|
import dashboardAppModule from "@/app/[lang]/dashboard/module/dashboard.app-module";
|
||||||
import { DiContext } from "@/bootstrap/di/di-context";
|
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
|
import { ReactVVMDiProvider } from "reactvvm";
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const di = useRef(dashboardAppModule());
|
const di = useRef(dashboardAppModule());
|
||||||
return (
|
return (
|
||||||
<DiContext.Provider value={di.current}>
|
<ReactVVMDiProvider diContainer={di.current}>
|
||||||
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
|
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
|
||||||
<div className="w-full flex-none md:w-64">
|
<div className="w-full flex-none md:w-64">
|
||||||
<SideNav />
|
<SideNav />
|
||||||
@ -17,6 +17,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DiContext.Provider>
|
</ReactVVMDiProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,12 @@ import createInvoiceController from "@/app/[lang]/dashboard/controller/create-in
|
|||||||
import ButtonVm from "@/app/components/button/button.i-vm";
|
import ButtonVm from "@/app/components/button/button.i-vm";
|
||||||
import { useServerAction } from "@/bootstrap/helpers/hooks/use-server-action";
|
import { useServerAction } from "@/bootstrap/helpers/hooks/use-server-action";
|
||||||
import useThrottle from "@/bootstrap/helpers/hooks/use-throttle";
|
import useThrottle from "@/bootstrap/helpers/hooks/use-throttle";
|
||||||
import BaseVM from "@/bootstrap/helpers/vm/base-vm";
|
|
||||||
import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
|
import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
|
||||||
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice.param";
|
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice.param";
|
||||||
import { faker } from "@faker-js/faker";
|
import { faker } from "@faker-js/faker";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { BaseVM } from "reactvvm";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Viewmodel for the button view to connect to business logics and all UI logics
|
* Viewmodel for the button view to connect to business logics and all UI logics
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import BaseView, { BuildProps } from "@/bootstrap/helpers/view/base-view";
|
|
||||||
import ButtonVm from "@/app/components/button/button.i-vm";
|
import ButtonVm from "@/app/components/button/button.i-vm";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cn } from "@/bootstrap/helpers/lib/ui-utils";
|
import { cn } from "@/bootstrap/helpers/lib/ui-utils";
|
||||||
|
import { BaseView, BuildProps } from "reactvvm";
|
||||||
|
|
||||||
export default class Button extends BaseView<ButtonVm> {
|
export default class Button extends BaseView<ButtonVm> {
|
||||||
protected Build(props: BuildProps<ButtonVm>): ReactNode {
|
protected Build(props: BuildProps<ButtonVm>): ReactNode {
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import di from "@/bootstrap/di/init-di";
|
|
||||||
import { createContext, use } from "react";
|
|
||||||
import { DependencyContainer } from "tsyringe";
|
|
||||||
|
|
||||||
const DiContext = createContext<null | DependencyContainer>(di);
|
|
||||||
|
|
||||||
const useDI = () => {
|
|
||||||
const di = use(DiContext);
|
|
||||||
|
|
||||||
if (!di) {
|
|
||||||
throw new Error("Di has not provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
return di;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { DiContext, useDI };
|
|
@ -1,147 +0,0 @@
|
|||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
|
||||||
/* eslint-disable react/display-name */
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable react/jsx-props-no-spreading */
|
|
||||||
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { useDI } from "@/bootstrap/di/di-context";
|
|
||||||
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
|
|
||||||
import { Component, ReactNode, FC, PropsWithChildren, memo } from "react";
|
|
||||||
import { InjectionToken } from "tsyringe";
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* Connector Component */
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
interface IVvmConnector<IVM, PROPS> extends PropsWithChildren {
|
|
||||||
View: FC<any & { vm: IVM }>;
|
|
||||||
Vm: IBaseVM<IVM>;
|
|
||||||
restProps?: PROPS;
|
|
||||||
memoizedByVM?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is just will be used in
|
|
||||||
*/
|
|
||||||
const VvmConnector = memo(
|
|
||||||
<IVM, PROPS>(props: IVvmConnector<IVM, PROPS>) => {
|
|
||||||
const { View, Vm, restProps, children } = props;
|
|
||||||
const vm = Vm.useVM();
|
|
||||||
|
|
||||||
const allProps = {
|
|
||||||
restProps,
|
|
||||||
vm,
|
|
||||||
};
|
|
||||||
|
|
||||||
return <View {...allProps}>{children}</View>;
|
|
||||||
},
|
|
||||||
(prevProps) => {
|
|
||||||
if (prevProps.memoizedByVM) return true;
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* BaseView */
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
type IVMParent = Record<string, any>;
|
|
||||||
type IPropParent = Record<string, any> | undefined;
|
|
||||||
|
|
||||||
type BaseProps<PROPS extends IPropParent = undefined> = {
|
|
||||||
restProps?: PROPS;
|
|
||||||
/**
|
|
||||||
* By default it's true.
|
|
||||||
* If you pass true this view will update just by changes of vm not rest props
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
memoizedByVM?: boolean;
|
|
||||||
children?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
type BasePropsWithVM<
|
|
||||||
IVM extends IVMParent,
|
|
||||||
PROPS extends IPropParent = undefined,
|
|
||||||
> = BaseProps<PROPS> & {
|
|
||||||
/**
|
|
||||||
* Directly instantiated vm
|
|
||||||
*/
|
|
||||||
vm: IBaseVM<IVM>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type BasePropsWithVMKey<PROPS extends IPropParent = undefined> =
|
|
||||||
BaseProps<PROPS> & {
|
|
||||||
/**
|
|
||||||
* TSyringe key for vm to be injected
|
|
||||||
*/
|
|
||||||
vmKey: InjectionToken;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BuildProps<
|
|
||||||
IVM extends IVMParent,
|
|
||||||
PROPS extends IPropParent = undefined,
|
|
||||||
> = {
|
|
||||||
vm: IVM;
|
|
||||||
restProps: PROPS;
|
|
||||||
children?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ViewProps<
|
|
||||||
IVM extends IVMParent,
|
|
||||||
PROPS extends IPropParent = undefined,
|
|
||||||
> = BasePropsWithVM<IVM, PROPS> | BasePropsWithVMKey<PROPS>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base view is base component for all views in mvvm architecture which gets
|
|
||||||
* vm as props and connect it to the view and memoize the component by default
|
|
||||||
* to just render just on changes of its vm.
|
|
||||||
*/
|
|
||||||
export default abstract class BaseView<
|
|
||||||
IVM extends IVMParent,
|
|
||||||
PROPS extends IPropParent = undefined,
|
|
||||||
> extends Component<ViewProps<IVM, PROPS>> {
|
|
||||||
private vm: IBaseVM<IVM> | undefined;
|
|
||||||
|
|
||||||
constructor(props: ViewProps<IVM, PROPS>) {
|
|
||||||
super(props);
|
|
||||||
this.vm = this.initVm;
|
|
||||||
}
|
|
||||||
|
|
||||||
private get initVm() {
|
|
||||||
if (Object.hasOwn(this.props, "vmKey")) {
|
|
||||||
const { vmKey } = this.props as BasePropsWithVMKey<PROPS>;
|
|
||||||
const di = useDI();
|
|
||||||
return di.resolve(vmKey) as IBaseVM<IVM>;
|
|
||||||
}
|
|
||||||
return (this.props as BasePropsWithVM<IVM, PROPS>).vm;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get componentName() {
|
|
||||||
return this.constructor.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Build(props: BuildProps<IVM, PROPS>): ReactNode;
|
|
||||||
|
|
||||||
render(): ReactNode {
|
|
||||||
const { restProps, memoizedByVM, children, ...rest } = this.props;
|
|
||||||
VvmConnector.displayName = this.componentName;
|
|
||||||
const vm = memoizedByVM ? this.vm : this.initVm;
|
|
||||||
if (!vm) {
|
|
||||||
const isVmKey = Object.hasOwn(this.props, "vmKey");
|
|
||||||
const message = isVmKey
|
|
||||||
? "vm is not defined, check your di configuration"
|
|
||||||
: "pass correct vm";
|
|
||||||
throw new Error(`Vm is not defined${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VvmConnector
|
|
||||||
View={this.Build}
|
|
||||||
Vm={vm}
|
|
||||||
memoizedByVM={typeof memoizedByVM === "undefined" ? true : memoizedByVM}
|
|
||||||
restProps={{ ...restProps, ...rest }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</VvmConnector>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
|
import { IBaseVM } from "reactvvm";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To use with mvvm library to make a vm based on props so you can pass the result to the view
|
* To use with mvvm library to make a vm based on props so you can pass the result to the view
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useDI } from "@/bootstrap/di/di-context";
|
|
||||||
import { NoOverride } from "@/bootstrap/helpers/type-helper";
|
|
||||||
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for all viewmodels. It provides
|
|
||||||
* - dependency injection: To get closes di which serves from di provider
|
|
||||||
* - rerender method: to rerender your component manually
|
|
||||||
* - produce method: to produce your vm dynamically by passing and attaching dependencies to it
|
|
||||||
*/
|
|
||||||
export default abstract class BaseVM<
|
|
||||||
IVM,
|
|
||||||
DEP extends object | undefined = undefined,
|
|
||||||
> implements IBaseVM<IVM>
|
|
||||||
{
|
|
||||||
/* ------------------------------ Dependencies ------------------------------ */
|
|
||||||
protected deps!: DEP;
|
|
||||||
|
|
||||||
/* -------------------------------- Abstracts ------------------------------- */
|
|
||||||
abstract useVM(): IVM;
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
produce(dep?: DEP) {
|
|
||||||
if (dep) this.deps = dep;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------- Getters -------------------------------- */
|
|
||||||
/**
|
|
||||||
* You can pass your rerender method after calling useRerender on your vm
|
|
||||||
* so you can access to it in any method
|
|
||||||
*/
|
|
||||||
protected rerender?: () => void;
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
protected get di() {
|
|
||||||
return useDI();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/**
|
|
||||||
* You can use this hook in your useVm method to get rerender method
|
|
||||||
* @returns Rerender Method that when ever you call it you can rerender your component
|
|
||||||
* for showing new values
|
|
||||||
*/
|
|
||||||
protected useRerender(): NoOverride<() => void> {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const [_, reState] = useState(false);
|
|
||||||
|
|
||||||
const rerender = () => reState((prev) => !prev);
|
|
||||||
return rerender as NoOverride<() => void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
/**
|
|
||||||
* All viewmodels should implement this interface.
|
|
||||||
*/
|
|
||||||
export default interface IBaseVM<VM> {
|
|
||||||
useVM(): VM;
|
|
||||||
}
|
|
@ -6493,6 +6493,11 @@ react@19.0.0-rc-69d4b800-20241021:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
|
|
||||||
|
reactvvm@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/reactvvm/-/reactvvm-1.1.0.tgz#2cdb7ebb75ec0fed4121960077c13474536d18e1"
|
||||||
|
integrity sha512-HBb/9SN5gAFhYGaQ5qzxmT+YZ0ML+7hfFfAH0milq8T+hJWY9seUsHUUvYxROuxg1fY+WIR04ENGB1H4HhAYJg==
|
||||||
|
|
||||||
read-cache@^1.0.0:
|
read-cache@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
|
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user