develop #3

Merged
behnam merged 39 commits from develop into main 2024-11-26 15:47:00 +00:00
15 changed files with 3119 additions and 6 deletions
Showing only changes of commit 69640da28e - Show all commits

View File

@ -1,3 +1,13 @@
{ {
"overrides": [
{
"files": [
"src/**/*-vm.ts"
],
"rules": {
"react-hooks/rules-of-hooks": "off"
}
}
],
"extends": ["next/core-web-vitals", "next/typescript"] "extends": ["next/core-web-vitals", "next/typescript"]
} }

View File

@ -5,22 +5,24 @@
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start --port 4000",
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"next": "15.0.1",
"react": "19.0.0-rc-69d4b800-20241021", "react": "19.0.0-rc-69d4b800-20241021",
"react-dom": "19.0.0-rc-69d4b800-20241021", "react-dom": "19.0.0-rc-69d4b800-20241021",
"next": "15.0.1" "reflect-metadata": "^0.2.2",
"tsyringe": "^4.8.0"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "15.0.1",
"postcss": "^8", "postcss": "^8",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"eslint": "^8", "typescript": "^5"
"eslint-config-next": "15.0.1"
} }
} }

View File

@ -1,7 +1,7 @@
import "reflect-metadata"
import type { Metadata } from "next"; import type { Metadata } from "next";
import localFont from "next/font/local"; import localFont from "next/font/local";
import "./globals.css"; import "./globals.css";
const geistSans = localFont({ const geistSans = localFont({
src: "./fonts/GeistVF.woff", src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans", variable: "--font-geist-sans",

8
src/app/test/page.tsx Normal file
View File

@ -0,0 +1,8 @@
"use client"
import "reflect-metadata"
import TestButtonVM from "@/app/test/vm/test-button-vm";
import Button from "@/components/button/button";
export default function Page() {
return <Button vmName={TestButtonVM.name} />
}

View File

@ -0,0 +1,17 @@
import BaseVM from "@/bootstrap/helpers/vm/base-vm";
import injectableVm from "@/bootstrap/helpers/vm/vm-decorator";
import ButtonVm from "@/components/button/button-vm";
@injectableVm()
export default class TestButtonVM extends BaseVM<ButtonVm> {
useVM(): ButtonVm {
return {
props: {
title: "Test Button"
},
onClick: () => {
console.log("clicked on the button");
}
}
}
}

View File

@ -0,0 +1,18 @@
import "reflect-metadata"
import { container, DependencyContainer } from "tsyringe";
/**
* Serves as a central point for initializing and configuring
* the DI container, ensuring that all necessary dependencies
* are registered and available for injection throughout the application.
*/
const InitDI = (): DependencyContainer => {
const di = container;
return di;
};
const di = InitDI();
export default di;

View File

@ -0,0 +1,8 @@
declare const _: unique symbol;
type Forbidden = { [_]: typeof _ };
/**
* You can use this type to make your parent class method forbidden to overwrite
*/
export type NoOverride<T = void> = T & Forbidden;

View File

@ -0,0 +1,95 @@
"use client"
/* eslint-disable react/display-name */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react/jsx-props-no-spreading */
import di from "@/bootstrap/di/init-di";
import BaseVM from "@/bootstrap/helpers/vm/base-vm";
import { Component, ReactNode, FC, PropsWithChildren, memo, MemoExoticComponent } from "react";
/* -------------------------------------------------------------------------- */
/* Connector Component */
/* -------------------------------------------------------------------------- */
interface IVvmConnector<IVM, PROPS> extends PropsWithChildren {
View: FC<any & { vm: IVM }>;
vmName: string;
restProps?: PROPS;
memoizedByVM?: boolean;
}
/**
* This function is just will be used in
*/
const VvmConnector = memo(
<IVM, PROPS>(props: IVvmConnector<IVM, PROPS>) => {
const { View, vmName, restProps, children } = props;
const VmInstance = di.resolve(vmName) as new () => BaseVM<IVM>;
if (!VmInstance) throw new Error(`Provided vm as ${vmName} is not exists`)
const vm = new VmInstance().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> = {
vmName: string;
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;
};
export type BuildProps<
IVM extends IVMParent,
PROPS extends IPropParent = undefined,
> = {
vm: IVM;
restProps: PROPS;
children?: ReactNode;
};
export default abstract class BaseView<
IVM extends IVMParent,
PROPS extends IPropParent = undefined,
> extends Component<BaseProps<PROPS>> {
/* -------------------------------- Abstracts ------------------------------- */
protected abstract Build(props: BuildProps<IVM, PROPS>): ReactNode;
/* -------------------------------- Renderer -------------------------------- */
render(): ReactNode {
const { vmName, restProps, memoizedByVM, children, ...rest } = this.props;
const Connector = VvmConnector as MemoExoticComponent<((props: IVvmConnector<IVM, PROPS>) => JSX.Element)>;
return (
<Connector
View={this.Build}
vmName={vmName}
memoizedByVM={typeof memoizedByVM === "undefined" ? true : memoizedByVM}
restProps={{ ...restProps, ...rest } as PROPS}
>
{children}
</Connector>
);
}
/* -------------------------------------------------------------------------- */
}

View File

@ -0,0 +1,46 @@
"use client"
import { NoOverride } from "@/bootstrap/helpers/type-helper";
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
import { useState } from "react";
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;
/* -------------------------------------------------------------------------- */
/**
* 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>;
}
/* -------------------------------------------------------------------------- */
}

View File

@ -0,0 +1,3 @@
export default interface IBaseVM<VM> {
useVM(): VM;
}

View File

@ -0,0 +1,9 @@
"use client"
import di from "@/bootstrap/di/init-di";
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
export default function injectableVm() {
return function (target: new (...args: unknown[]) => IBaseVM<object>) {
di.registerInstance(target.name, target);
};
}

View File

@ -0,0 +1,6 @@
export default interface ButtonVm {
props: {
title: string
}
onClick(): void
}

View File

@ -0,0 +1,10 @@
import BaseView, { BuildProps } from "@/bootstrap/helpers/view/base-view";
import ButtonVm from "@/components/button/button-vm";
import { ReactNode } from "react";
export default class Button extends BaseView<ButtonVm> {
protected Build(props: BuildProps<ButtonVm>): ReactNode {
const {vm} = props
return <button onClick={vm.onClick} >{vm.props.title}</button>
}
}

View File

@ -9,6 +9,8 @@
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",

2879
yarn.lock Normal file

File diff suppressed because it is too large Load Diff