:create`, upload is deprecated, still compatible with v0.10 but will be Removed.
+
+### signin/signup api changes
+
+The nocobase kernel provides a more powerful [auth module](https://github.com/nocobase/nocobase/tree/main/packages/plugins/auth) with the following changes to the user login, registration, verification, and logout apis:
+
+```bash
+/api/users:signin -> /api/auth:signIn
+/api/users:signup -> /api/auth:signUp
+/api/users:signout -> /api/auth:signOut
+/api/users:check -> /api/auth:check
+```
+
+Note: The above users interface, which is deprecated, is still compatible with v0.10, but will be removed in the next major release.
+
+### Adjustments to date field filtering
+
+If date related filtering was previously configured in the data range, it needs to be deleted and reconfigured.
+
+## Third-party plugin upgrade guide
+
+### Dependencies upgrade
+
+dependencies mainly including
+
+- `react` upgrade to v18
+- `react-dom` upgrade to v18
+- `react-router` upgrade to v6.11
+- `umi` upgrade to v4
+- `dumi` upgrade to v2
+
+The `package.json` dependencies should be changed to the latest version, e.g:
+
+```diff
+{
+ "devDependencies": {
++ "react": "^18".
++ "react-dom": "^18".
++ "react-router-dom": "^6.11.2".
+- "react": "^17".
+- "react-dom": "^17".
+- "react-router-dom": "^5".
+ }
+}
+```
+
+### Code changes
+
+Because react-router has been upgraded, the related code also needs to be changed, the main changes include
+
+### Layout Component
+
+Layout component needs to use ` ` instead of `props.children`.
+
+```diff
+import React from 'react';
++ import { Outlet } from 'react-router-dom';
+
+export default function Layout(props) {
+ return (
+
+- { props.children }
++
+
+ );
+}
+```
+
+if you use `React.cloneElement` to render the route component, you need to change it like this:
+
+```diff
+import React from 'react';
++ import { Outlet } from 'react-router-dom';
+
+export default function RouteComponent(props) {
+ return (
+
+- { React.cloneElement(props.children, { someProp: 'p1' }) }
++
+
+ );
+}
+```
+
+Change the route component to get the value from `useOutletContext`
+
+```diff
+import React from 'react';
++ import { useOutletContext } from 'react-router-dom';
+
+- export function Comp(props){
++ export function Comp() {
++ const props = useOutletContext();
+ return props.someProp;
+}
+```
+
+### Redirect
+
+`` is changed to ` `.
+
+```diff
+-
++
+```
+
+### useHistory
+
+`useNavigate` is changed to `useHistory`.
+
+```diff
+- import { useHistory } from 'react-router-dom';
++ import { useNavigate} from 'react-router-dom';
+
+- const history = useHistory();
++ const navigate = useNavigate();
+
+- history.push('/about')
++ navigate('/about')
+
+- history.replace('/about')
++ navigate('/about', { replace: true })
+```
+
+### useLocation
+
+`useLocation()` is changed to `useLocation`.
+
+```diff
+- const location= useLocation();
++ const location= useLocation();
+```
+
+`const { query } = useLocation()` is changed to `useSearchParams()`。
+
+```diff
+- const location = useLocation();
+- const query = location.query;
+- const name = query.name;
++ const [searchParams, setSearchParams] = useSearchParams();
++ searchParams.get('name');
+```
+
+### path
+
+All of the following are valid route paths in v6:
+
+```
+/groups
+/groups/admin
+/users/:id
+/users/:id/messages
+/files/*
+/files/:id/*
+```
+
+The following RegExp-style route paths are not valid in v6:
+
+```
+/tweets/:id(\d+)
+/files/*/cat.jpg
+/files-*
+```
+
+For more api changes, please refer to [react-router@6](https://reactrouter.com/en/main/upgrading/v5)。
diff --git a/docs/tr-TR/api/client/route-switch.md b/docs/tr-TR/api/client/route-switch.md
index 3aacb7083..6739d10a1 100644
--- a/docs/tr-TR/api/client/route-switch.md
+++ b/docs/tr-TR/api/client/route-switch.md
@@ -23,8 +23,6 @@ interface RedirectProps {
type: 'redirect';
to: any;
path?: string;
- exact?: boolean;
- strict?: boolean;
push?: boolean;
from?: string;
[key: string]: any;
@@ -33,8 +31,6 @@ interface RedirectProps {
interface RouteProps {
type: 'route';
path?: string | string[];
- exact?: boolean;
- strict?: boolean;
sensitive?: boolean;
component?: any;
routes?: RouteProps[];
@@ -56,7 +52,6 @@ const routes: RouteRedirectProps[] = [
{
type: 'route',
path: '/',
- exact: true,
component: 'Home',
},
{
@@ -76,4 +71,4 @@ export default () => {
);
};
-```
\ No newline at end of file
+```
diff --git a/docs/tr-TR/components.md b/docs/tr-TR/components.md
new file mode 100644
index 000000000..0eac4eb52
--- /dev/null
+++ b/docs/tr-TR/components.md
@@ -0,0 +1 @@
+# Components
diff --git a/docs/tr-TR/development/client/test.md b/docs/tr-TR/development/client/test.md
index d8d19e163..a6c082f9f 100644
--- a/docs/tr-TR/development/client/test.md
+++ b/docs/tr-TR/development/client/test.md
@@ -25,7 +25,6 @@ describe('route-switch', () => {
{
type: 'route',
path: '/',
- exact: true,
component: 'Home',
},
]}
diff --git a/docs/tr-TR/development/client/ui-router.md b/docs/tr-TR/development/client/ui-router.md
index c167293cd..bd27b1994 100644
--- a/docs/tr-TR/development/client/ui-router.md
+++ b/docs/tr-TR/development/client/ui-router.md
@@ -17,7 +17,6 @@ const routes: RouteRedirectProps[] = [
{
type: 'route',
path: '/',
- exact: true,
component: 'Home',
},
{
diff --git a/docs/tr-TR/development/client/ui-schema-designer/index.md b/docs/tr-TR/development/client/ui-schema-designer/index.md
index 102eb7c9a..bd5b6fdf7 100644
--- a/docs/tr-TR/development/client/ui-schema-designer/index.md
+++ b/docs/tr-TR/development/client/ui-schema-designer/index.md
@@ -1,3 +1,3 @@
# Overview
-
\ No newline at end of file
+
diff --git a/docs/tr-TR/index.md b/docs/tr-TR/index.md
index ab11f4321..49a1e7610 100644
--- a/docs/tr-TR/index.md
+++ b/docs/tr-TR/index.md
@@ -1 +1,3 @@
-
\ No newline at end of file
+# Index
+
+
diff --git a/docs/tr-TR/welcome/community/contributing.md b/docs/tr-TR/welcome/community/contributing.md
index beb193751..13e065817 100644
--- a/docs/tr-TR/welcome/community/contributing.md
+++ b/docs/tr-TR/welcome/community/contributing.md
@@ -5,7 +5,7 @@
- Çekme isteği(pull request) gönderin
- CLA'yı imzalayın
-## İndir
+## İndir
```bash
# Aşağıdaki git adresini kendi deponuzla değiştirin
@@ -47,4 +47,4 @@ Belgeler docs dizinindedir ve Markdown sözdizimini takip eder
## Diğerleri
-Daha fazla CLI talimatı için lütfen [NocoBase CLI bölümüne bakın](./development/nocobase-cli.md)
+Daha fazla CLI talimatı için lütfen [NocoBase CLI bölümüne bakın](https://docs-cn.nocobase.com/api/cli).
diff --git a/docs/tr-TR/welcome/getting-started/upgrading/index.md b/docs/tr-TR/welcome/getting-started/upgrading/index.md
index 3b022ccb6..3bcfb5f91 100644
--- a/docs/tr-TR/welcome/getting-started/upgrading/index.md
+++ b/docs/tr-TR/welcome/getting-started/upgrading/index.md
@@ -2,6 +2,6 @@
NocoBase, yükseltmelerde küçük farklılıklarla üç tür yüklemeyi destekler.
-- [Docker için Güncelleme](./upgrading/docker-compose.md)
-- [create-nocobase-app için Güncelleme](./upgrading/create-nocobase-app.md)
-- [Git Kaynak koduyla Güncelleme](./upgrading/git-clone.md)
+- [Docker için Güncelleme](./docker-compose.md)
+- [create-nocobase-app için Güncelleme](./create-nocobase-app.md)
+- [Git Kaynak koduyla Güncelleme](./git-clone.md)
diff --git a/docs/tr-TR/welcome/release/v10-changelog.md b/docs/tr-TR/welcome/release/v10-changelog.md
new file mode 100644
index 000000000..b5ff1d43d
--- /dev/null
+++ b/docs/tr-TR/welcome/release/v10-changelog.md
@@ -0,0 +1,156 @@
+# v0.10:update instructions
+
+v0.10 has made major upgrades to dependencies, including `react`, `react-dom`, `react-router`, `umi`, and `dumi`.
+
+## 依赖升级
+
+- Upgrade `react@^17`, `react-dom@^17` to `react@^18`, `react-dom@^18` version
+
+```diff
+{
+ "devDependencies": {
++ "react": "^18",
++ "react-dom": "^18",
+- "react": "^17",
+- "react-dom": "^17",
+ }
+}
+```
+
+- Upgrade `react-router@5` to `react-router@6`
+
+```diff
+{
+ "devDependencies": {
++ "react-router-dom": "^6.11.2",
+- "react-router-dom": "^5",
+ }
+}
+```
+
+## `react-router` change description
+
+because the upgrade of `react-router` requires some code changes.
+
+### Layout Component
+
+Layout component needs to use ` ` instead of `props.children`.
+
+```diff
+import React from 'react';
++ import { Outlet } from 'react-router-dom';
+
+export default function Layout(props) {
+ return (
+
+- { props.children }
++
+
+ );
+}
+```
+
+if you use `React.cloneElement` to render the route component, you need to change it like this:
+
+```diff
+import React from 'react';
++ import { Outlet } from 'react-router-dom';
+
+export default function RouteComponent(props) {
+ return (
+
+- { React.cloneElement(props.children, { someProp: 'p1' }) }
++
+
+ );
+}
+```
+
+Change the route component to get the value from `useOutletContext`
+
+```diff
+import React from 'react';
++ import { useOutletContext } from 'react-router-dom';
+
+- export function Comp(props){
++ export function Comp() {
++ const props = useOutletContext();
+ return props.someProp;
+}
+```
+
+### Redirect
+
+`` is changed to ` `.
+
+```diff
+-
++
+```
+
+### useHistory
+
+`useNavigate` is changed to `useHistory`.
+
+```diff
+- import { useHistory } from 'react-router-dom';
++ import { useNavigate} from 'react-router-dom';
+
+- const history = useHistory();
++ const navigate = useNavigate();
+
+- history.push('/about')
++ navigate('/about')
+
+- history.replace('/about')
++ navigate('/about', { replace: true })
+```
+
+### useLocation
+
+`useLocation()` is changed to `useLocation`.
+
+```diff
+- const location= useLocation();
++ const location= useLocation();
+```
+
+`const { query } = useLocation()` is changed to `useSearchParams()`。
+
+```diff
+- const location = useLocation();
+- const query = location.query;
+- const name = query.name;
++ const [searchParams, setSearchParams] = useSearchParams();
++ searchParams.get('name');
+```
+
+### path
+
+All of the following are valid route paths in v6:
+
+```
+/groups
+/groups/admin
+/users/:id
+/users/:id/messages
+/files/*
+/files/:id/*
+```
+
+The following RegExp-style route paths are not valid in v6:
+
+```
+/tweets/:id(\d+)
+/files/*/cat.jpg
+/files-*
+```
+
+For more changes and api changes, please refer to [react-router@6](https://reactrouter.com/en/main/upgrading/v5)。
+
+## Docs Demo
+
+```diff
+-
++
+```
diff --git a/docs/zh-CN/api/actions.md b/docs/zh-CN/api/actions.md
index c752fc74e..0aa20b605 100644
--- a/docs/zh-CN/api/actions.md
+++ b/docs/zh-CN/api/actions.md
@@ -1,4 +1,4 @@
-# 内置常用资源操作
+# @nocobase/actions
## 概览
diff --git a/docs/zh-CN/api/cli.md b/docs/zh-CN/api/cli.md
index a073396c6..5464c4361 100644
--- a/docs/zh-CN/api/cli.md
+++ b/docs/zh-CN/api/cli.md
@@ -1,4 +1,4 @@
-# NocoBase CLI
+# @nocobase/cli
NocoBase CLI 旨在帮助你开发、构建和部署 NocoBase 应用。
diff --git a/docs/zh-CN/api/client/route-switch.md b/docs/zh-CN/api/client/route-switch.md
index 3aacb7083..6739d10a1 100644
--- a/docs/zh-CN/api/client/route-switch.md
+++ b/docs/zh-CN/api/client/route-switch.md
@@ -23,8 +23,6 @@ interface RedirectProps {
type: 'redirect';
to: any;
path?: string;
- exact?: boolean;
- strict?: boolean;
push?: boolean;
from?: string;
[key: string]: any;
@@ -33,8 +31,6 @@ interface RedirectProps {
interface RouteProps {
type: 'route';
path?: string | string[];
- exact?: boolean;
- strict?: boolean;
sensitive?: boolean;
component?: any;
routes?: RouteProps[];
@@ -56,7 +52,6 @@ const routes: RouteRedirectProps[] = [
{
type: 'route',
path: '/',
- exact: true,
component: 'Home',
},
{
@@ -76,4 +71,4 @@ export default () => {
);
};
-```
\ No newline at end of file
+```
diff --git a/docs/zh-CN/components.md b/docs/zh-CN/components.md
new file mode 100644
index 000000000..0eac4eb52
--- /dev/null
+++ b/docs/zh-CN/components.md
@@ -0,0 +1 @@
+# Components
diff --git a/docs/zh-CN/development/client/test.md b/docs/zh-CN/development/client/test.md
index 452ea9f46..1af06a831 100644
--- a/docs/zh-CN/development/client/test.md
+++ b/docs/zh-CN/development/client/test.md
@@ -25,7 +25,6 @@ describe('route-switch', () => {
{
type: 'route',
path: '/',
- exact: true,
component: 'Home',
},
]}
@@ -38,4 +37,4 @@ describe('route-switch', () => {
expect(container).toMatchSnapshot();
});
});
-```
\ No newline at end of file
+```
diff --git a/docs/zh-CN/development/client/ui-router.md b/docs/zh-CN/development/client/ui-router.md
index 5a620942b..a4c40946e 100644
--- a/docs/zh-CN/development/client/ui-router.md
+++ b/docs/zh-CN/development/client/ui-router.md
@@ -17,7 +17,6 @@ const routes: RouteRedirectProps[] = [
{
type: 'route',
path: '/',
- exact: true,
component: 'Home',
},
{
@@ -60,4 +59,4 @@ export default React.memo((props) => {
});
```
-完整示例查看 [packages/samples/custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page)
\ No newline at end of file
+完整示例查看 [packages/samples/custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page)
diff --git a/docs/zh-CN/development/client/ui-schema-designer/index.md b/docs/zh-CN/development/client/ui-schema-designer/index.md
index 102eb7c9a..bd5b6fdf7 100644
--- a/docs/zh-CN/development/client/ui-schema-designer/index.md
+++ b/docs/zh-CN/development/client/ui-schema-designer/index.md
@@ -1,3 +1,3 @@
# Overview
-
\ No newline at end of file
+
diff --git a/docs/zh-CN/index.md b/docs/zh-CN/index.md
index ab11f4321..49a1e7610 100644
--- a/docs/zh-CN/index.md
+++ b/docs/zh-CN/index.md
@@ -1 +1,3 @@
-
\ No newline at end of file
+# Index
+
+
diff --git a/docs/zh-CN/welcome/community/contributing.md b/docs/zh-CN/welcome/community/contributing.md
index c34b7d6dc..76c982967 100644
--- a/docs/zh-CN/welcome/community/contributing.md
+++ b/docs/zh-CN/welcome/community/contributing.md
@@ -45,4 +45,4 @@ yarn doc --lang=en-US
## 其他
-更多 Commands 使用说明 [参考 NocoBase CLI 章节](./development/nocobase-cli.md)
+更多 Commands 使用说明 [参考 NocoBase CLI 章节](https://docs-cn.nocobase.com/api/cli)。
diff --git a/docs/zh-CN/welcome/getting-started/installation/index.md b/docs/zh-CN/welcome/getting-started/installation/index.md
index 1a5e732a2..6940b17b5 100644
--- a/docs/zh-CN/welcome/getting-started/installation/index.md
+++ b/docs/zh-CN/welcome/getting-started/installation/index.md
@@ -4,9 +4,9 @@
NocoBase 支持三种安装方式:
-- [Docker 安装(推荐)](./installation/docker-compose.md)
-- [create-nocobase-app 安装](./installation/create-nocobase-app.md)
-- [Git 源码安装](./installation/git-clone.md)
+- [Docker 安装(推荐)](./docker-compose.md)
+- [create-nocobase-app 安装](./create-nocobase-app.md)
+- [Git 源码安装](./git-clone.md)
## 如何选择
diff --git a/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md b/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md
index 79285aa43..093a15ea6 100644
--- a/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md
+++ b/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md
@@ -12,19 +12,32 @@ cd my-nocobase-app
git pull
```
-## 3. 更新依赖
+## 3. 删除旧依赖文件(非必须)
+v0.10 进行了依赖的重大升级,如果 v0.9 升级 v0.10,需要删掉以下目录之后再升级
+
+```bash
+# 删除 .umi 相关缓存
+yarn rimraf -rf ./**/{.umi,.umi-production}
+# 删除编译文件
+yarn rimraf -rf packages/*/*/{lib,esm,es,dist,node_modules}
+# 删除全部依赖
+yarn rimraf -rf node_modules
```
+
+## 4. 更新依赖
+
+```bash
yarn install
```
-## 4. 执行更新命令
+## 5. 执行更新命令
```bash
yarn nocobase upgrade
```
-## 5. 启动 NocoBase
+## 6. 启动 NocoBase
开发环境
diff --git a/docs/zh-CN/welcome/getting-started/upgrading/index.md b/docs/zh-CN/welcome/getting-started/upgrading/index.md
index c71ad7416..b914a5ebc 100644
--- a/docs/zh-CN/welcome/getting-started/upgrading/index.md
+++ b/docs/zh-CN/welcome/getting-started/upgrading/index.md
@@ -2,6 +2,6 @@
NocoBase 支持三种安装方式,升级时略有不同。
-- [Docker 安装的升级](./upgrading/docker-compose.md)
-- [create-nocobase-app 安装的升级](./upgrading/create-nocobase-app.md)
-- [Git 源码安装的升级](./upgrading/git-clone.md)
+- [Docker 安装的升级](./docker-compose.md)
+- [create-nocobase-app 安装的升级](./create-nocobase-app.md)
+- [Git 源码安装的升级](./git-clone.md)
diff --git a/docs/zh-CN/welcome/release/v10-changelog.md b/docs/zh-CN/welcome/release/v10-changelog.md
new file mode 100644
index 000000000..5dc68bc6c
--- /dev/null
+++ b/docs/zh-CN/welcome/release/v10-changelog.md
@@ -0,0 +1,232 @@
+# v0.10:更新说明
+
+## 第二季度的新特性
+
+- 关系字段组件改进,支持多种组件切换
+ - 下拉选择器
+ - 数据选择器
+ - 子表单/子详情
+ - 子表格
+ - 文件管理器
+ - 标题(仅阅读模式)
+- 快捷创建关系数据,支持两种快捷创建模式
+ - 下拉菜单里添加,基于标题字段快速创建一条新纪录
+ - 弹窗里添加,可以配置复杂的添加表单
+- 复制操作,支持两种复制模式
+ - 直接复制
+ - 复制到表单里并继续填写
+- 表单数据模板
+- 筛选数据范围支持变量
+- 列表区块
+- 网格卡片区块
+- 移动端插件
+- 用户认证插件,支持不同的登录方式
+ - Email/Password
+ - SMS
+ - OIDC
+ - SAML
+- 工作流新增节点
+ - 人工节点升级,支持从现有数据表里新增和编辑
+ - 循环节点
+ - 聚合查询节点
+- 文件管理器
+ - 提供文件表模板
+ - 提供文件管理器组件
+
+## 应用升级
+
+### Docker 安装的升级
+
+无变化,升级参考 [Docker 镜像升级指南](/welcome/getting-started/upgrading/docker-compose)
+
+### 源码安装的升级
+
+v0.10 进行了依赖的重大升级,源码升级时,以防出错,需要删掉以下目录之后再升级
+
+```bash
+# 删除 .umi 相关缓存
+yarn rimraf -rf "./**/{.umi,.umi-production}"
+# 删除编译文件
+yarn rimraf -rf "packages/*/*/{lib,esm,es,dist,node_modules}"
+# 删除依赖
+yarn rimraf -rf node_modules
+```
+
+更多详情参考 [Git 源码升级指南](/welcome/getting-started/upgrading/git-clone)
+
+### create-nocobase-app 安装的升级
+
+建议 `yarn create` 重新下载新版本,再更新 .env 配置,更多详情参考 [大版本升级指南](/welcome/getting-started/upgrading/create-nocobase-app#大版本升级)
+
+## 即将遗弃和可能不兼容的变化
+
+### 子表格字段组件
+
+不兼容新版,区块字段需要删除重配(只需要 UI 重配)
+
+### 附件上传接口的变更
+
+除了内置的 attachments 表以外,用户也可以自定义文件表,附件的上传接口由 `/api/attachments:upload` 变更为 `/api/:create`,upload 已废弃,依旧兼容 v0.10,但会在下个大版本里移除。
+
+### 登录、注册接口的变更
+
+nocobase 内核提供了更强大的 [auth 模块](https://github.com/nocobase/nocobase/tree/main/packages/plugins/auth),用户登录、注册、校验、注销接口变更如下:
+
+```bash
+/api/users:signin -> /api/auth:signIn
+/api/users:signup -> /api/auth:signUp
+/api/users:signout -> /api/auth:signOut
+/api/users:check -> /api/auth:check
+```
+
+注:以上 users 接口,已废弃,依旧兼容 v0.10,但会在下个大版本里移除。
+
+### 日期字段筛选的调整
+
+如果之前数据范围里配置了日期相关筛选,需要删掉重新配置。
+
+## 第三方插件升级指南
+
+### 依赖升级
+
+v0.10 依赖升级,主要包括
+
+- `react` 升级到 v18
+- `react-dom` 升级到 v18
+- `react-router` 升级到 v6.11
+- `umi` 升级到 v4
+- `dumi` 升级到 v2
+
+插件的 `package.json` 相关依赖要更改为最新版,如:
+
+```diff
+{
+ "devDependencies": {
++ "react": "^18".
++ "react-dom": "^18".
++ "react-router-dom": "^6.11.2".
+- "react": "^17".
+- "react-dom": "^17".
+- "react-router-dom": "^5".
+ }
+}
+```
+
+### 代码修改
+
+由于 react-router 的升级,代码层面也需要改动,主要变更包括
+
+#### Layout 布局组件
+
+Layout 布局组件需要使用 ` ` 代替 `props.children`。
+
+```diff
+import React from 'react';
++ import { Outlet } from 'react-router-dom';
+
+export default function Layout(props) {
+ return (
+
+- { props.children }
++
+
+ );
+}
+```
+
+使用了 `React.cloneElement` 方式渲染的路由组件改造,示例:
+
+```diff
+import React from 'react';
++ import { Outlet } from 'react-router-dom';
+
+export default function RouteComponent(props) {
+ return (
+
+- { React.cloneElement(props.children, { someProp: 'p1' }) }
++
+
+ );
+}
+```
+
+组件改成从 `useOutletContext` 取值
+
+```diff
+import React from 'react';
++ import { useOutletContext } from 'react-router-dom';
+
+- export function Comp(props){
++ export function Comp() {
++ const props = useOutletContext();
+ return props.someProp;
+}
+```
+
+#### Redirect
+
+`` 转为 ` `。
+
+```diff
+-
++
+```
+
+#### useHistory
+
+`useNavigate` 代替 `useHistory`。
+
+```diff
+- import { useHistory } from 'react-router-dom';
++ import { useNavigate} from 'react-router-dom';
+
+- const history = useHistory();
++ const navigate = useNavigate();
+
+- history.push('/about')
++ navigate('/about')
+
+- history.replace('/about')
++ navigate('/about', { replace: true })
+```
+
+#### useLocation
+
+`useLocation()` 改为 `useLocation`。
+
+```diff
+- const location= useLocation();
++ const location= useLocation();
+```
+
+`const { query } = useLocation()` 改为 `useSearchParams()`。
+
+```diff
+- const location = useLocation();
+- const query = location.query;
+- const name = query.name;
++ const [searchParams, setSearchParams] = useSearchParams();
++ searchParams.get('name');
+```
+
+#### path
+
+支持下面的 `path` 方式
+
+```
+/groups
+/groups/admin
+/users/:id
+/users/:id/messages
+/files/*
+/files/:id/*
+```
+
+不再支持如下方式
+```
+/tweets/:id(\d+)
+/files/*/cat.jpg
+/files-*
+```
+
+更多改动和 api 变更,请查阅 [react-router@6](https://reactrouter.com/en/main/upgrading/v5)。
diff --git a/lerna.json b/lerna.json
index b15c5021d..8719d492b 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": [
diff --git a/package.json b/package.json
index 588150a80..e4a812d9e 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,11 @@
},
"resolutions": {
"@types/react": "^17.0.0",
- "@types/react-dom": "^17.0.0"
+ "@types/react-dom": "^17.0.0",
+ "react-router-dom": "^6.11.2",
+ "react-router": "^6.11.2",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
},
"config": {
"ghooks": {
@@ -40,6 +44,10 @@
}
},
"devDependencies": {
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0",
"@commitlint/cli": "^16.1.0",
"@commitlint/config-conventional": "^16.0.0",
"@commitlint/prompt-cli": "^16.1.0",
@@ -48,6 +56,8 @@
"@testing-library/user-event": "^14.4.3",
"@vitejs/plugin-react": "^4.0.0",
"auto-changelog": "^2.4.0",
+ "dumi": "^2.2.0",
+ "dumi-theme-nocobase": "^0.2.11",
"ghooks": "^2.0.4",
"jsdom-worker": "^0.3.0",
"prettier": "^2.2.1",
diff --git a/packages/app/client/.umirc.ts b/packages/app/client/.umirc.ts
index e2b393eb8..31708ed5b 100644
--- a/packages/app/client/.umirc.ts
+++ b/packages/app/client/.umirc.ts
@@ -1,34 +1,50 @@
-import { getUmiConfig, resolveNocobasePackagesAlias } from '@nocobase/devtools/umiConfig';
+import { getUmiConfig } from '@nocobase/devtools/umiConfig';
import { defineConfig } from 'umi';
const umiConfig = getUmiConfig();
process.env.MFSU_AD = 'none';
+process.env.DID_YOU_KNOW = 'none';
export default defineConfig({
+ title: 'Loading...',
+ favicons: ['/favicon/favicon.ico'],
+ metas: [
+ { name: 'viewport', content: 'initial-scale=0.1' },
+ ],
+ links: [
+ { rel: 'apple-touch-icon', size: '180x180', ref: '/favicon/apple-touch-icon.png' },
+ { rel: 'icon', type: 'image/png', size: '32x32', ref: '/favicon/favicon-32x32.png' },
+ { rel: 'icon', type: 'image/png', size: '16x16', ref: '/favicon/favicon-16x16.png' },
+ { rel: 'manifest', href: '/favicon/site.webmanifest' },
+ { rel: 'stylesheet', href: '/global.css' },
+ ],
+ headScripts: [
+ '/browser-checker.js',
+ '/set-router.js',
+ ],
hash: true,
+ alias: {
+ ...umiConfig.alias,
+ },
define: {
...umiConfig.define,
},
- dynamicImportSyntax: {},
- // only proxy when using `umi dev`
- // if the assets are built, will not proxy
proxy: {
...umiConfig.proxy,
},
- nodeModulesTransform: {
- type: 'none',
- },
- routes: [{ path: '/', exact: false, component: '@/pages/index' }],
- // fastRefresh: {},
- chainWebpack(memo) {
- resolveNocobasePackagesAlias(memo);
-
- // 在引入 mermaid 之后,运行 yarn dev 的时候会报错,添加下面的代码可以解决。
- memo.module
- .rule('js-in-node_modules')
- .test(/(react-error-boundary.*\.js|htmlparser2|(.*mermaid.*\.js$))/)
- .include.clear();
- return memo;
+ fastRefresh: false, // 热更新会导致 Context 丢失,不开启
+ mfsu: false,
+ esbuildMinifyIIFE: true,
+ // srcTranspiler: 'esbuild', // 不行,各种报错
+ // mfsu: {
+ // esbuild: true // 不行,各种报错
+ // },
+ // 浏览器兼容性,兼容到 2018 年的浏览器
+ targets: {
+ chrome: 69,
+ edge: 79,
+ safari: 12,
},
+ routes: [{ path: '/*', component: 'index' }],
});
diff --git a/packages/app/client/package.json b/packages/app/client/package.json
index d2fa5077a..fbe15c939 100644
--- a/packages/app/client/package.json
+++ b/packages/app/client/package.json
@@ -1,9 +1,9 @@
{
"name": "@nocobase/app-client",
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"license": "AGPL-3.0",
"devDependencies": {
- "@nocobase/client": "0.9.4-alpha.2"
+ "@nocobase/client": "0.10.0-alpha.1"
},
"repository": {
"type": "git",
diff --git a/packages/app/client/plugin.ts b/packages/app/client/plugin.ts
new file mode 100644
index 000000000..08f5d675e
--- /dev/null
+++ b/packages/app/client/plugin.ts
@@ -0,0 +1,17 @@
+import { IApi } from 'umi';
+
+export default (api: IApi) => {
+ api.addRuntimePlugin(() => ['../plugin-routerBase/runtime.ts']);
+ api.addRuntimePluginKey(() => ['routerBase']);
+
+ api.onGenerateFiles(() => {
+ api.writeTmpFile({
+ path: '../plugin-routerBase/runtime.ts',
+ content: `
+export function modifyContextOpts(memo) {
+ return { ...memo, basename: window.routerBase };
+};
+`,
+ });
+ });
+};
diff --git a/packages/app/client/public/browser-checker.js b/packages/app/client/public/browser-checker.js
new file mode 100644
index 000000000..168706a37
--- /dev/null
+++ b/packages/app/client/public/browser-checker.js
@@ -0,0 +1 @@
+showLog = true; function log(m) { if (window.console && showLog) { console.log(m) } } function css_browser_selector(u) { var uaInfo = {}, screens = [320, 480, 640, 768, 1024, 1152, 1280, 1440, 1680, 1920, 2560], allScreens = screens.length, ua = u.toLowerCase(), is = function (t) { return RegExp(t, 'i').test(ua) }, version = function (p, n) { n = n.replace('.', '_'); var i = n.indexOf('_'), ver = ''; while (i > 0) { ver += ' ' + p + n.substring(0, i); i = n.indexOf('_', i + 1) } ver += ' ' + p + n; return ver }, g = 'gecko', w = 'webkit', c = 'chrome', f = 'firefox', s = 'safari', o = 'opera', m = 'mobile', a = 'android', bb = 'blackberry', lang = 'lang_', dv = 'device_', html = document.documentElement, b = [(!/opera|webtv/i.test(ua) && /msie\s(\d+)/.test(ua)) || /trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.test(ua) ? 'ie ie' + (/trident\/4\.0/.test(ua) ? '8' : RegExp.$1 == '11.0' ? '11' : RegExp.$1) : is('firefox/') ? g + ' ' + f + (/firefox\/((\d+)(\.(\d+))(\.\d+)*)/.test(ua) ? ' ' + f + RegExp.$2 + ' ' + f + RegExp.$2 + '_' + RegExp.$4 : '') : is('gecko/') ? g : is('opera') ? o + (/version\/((\d+)(\.(\d+))(\.\d+)*)/.test(ua) ? ' ' + o + RegExp.$2 + ' ' + o + RegExp.$2 + '_' + RegExp.$4 : /opera(\s|\/)(\d+)\.(\d+)/.test(ua) ? ' ' + o + RegExp.$2 + ' ' + o + RegExp.$2 + '_' + RegExp.$3 : '') : is('konqueror') ? 'konqueror' : is('blackberry') ? bb + (/Version\/(\d+)(\.(\d+)+)/i.test(ua) ? ' ' + bb + RegExp.$1 + ' ' + bb + RegExp.$1 + RegExp.$2.replace('.', '_') : /Blackberry ?(([0-9]+)([a-z]?))[\/|;]/gi.test(ua) ? ' ' + bb + RegExp.$2 + (RegExp.$3 ? ' ' + bb + RegExp.$2 + RegExp.$3 : '') : '') : is('android') ? a + (/Version\/(\d+)(\.(\d+))+/i.test(ua) ? ' ' + a + RegExp.$1 + ' ' + a + RegExp.$1 + RegExp.$2.replace('.', '_') : '') + (/Android (.+); (.+) Build/i.test(ua) ? ' ' + dv + RegExp.$2.replace(/ /g, '_').replace(/-/g, '_') : '') : is('chrome') ? w + ' ' + c + (/chrome\/((\d+)(\.(\d+))(\.\d+)*)/.test(ua) ? ' ' + c + RegExp.$2 + (RegExp.$4 > 0 ? ' ' + c + RegExp.$2 + '_' + RegExp.$4 : '') : '') : is('iron') ? w + ' iron' : is('applewebkit/') ? w + ' ' + s + (/version\/((\d+)(\.(\d+))(\.\d+)*)/.test(ua) ? ' ' + s + RegExp.$2 + ' ' + s + RegExp.$2 + RegExp.$3.replace('.', '_') : / Safari\/(\d+)/i.test(ua) ? RegExp.$1 == '419' || RegExp.$1 == '417' || RegExp.$1 == '416' || RegExp.$1 == '412' ? ' ' + s + '2_0' : RegExp.$1 == '312' ? ' ' + s + '1_3' : RegExp.$1 == '125' ? ' ' + s + '1_2' : RegExp.$1 == '85' ? ' ' + s + '1_0' : '' : '') : is('mozilla/') ? g : '', is('android|mobi|mobile|j2me|iphone|ipod|ipad|blackberry|playbook|kindle|silk',) ? m : '', is('j2me') ? 'j2me' : is('ipad|ipod|iphone') ? (/CPU( iPhone)? OS (\d+[_|\.]\d+([_|\.]\d+)*)/i.test(ua) ? 'ios' + version('ios', RegExp.$2) : '') + ' ' + (/(ip(ad|od|hone))/gi.test(ua) ? RegExp.$1 : '') : is('playbook') ? 'playbook' : is('kindle|silk') ? 'kindle' : is('playbook') ? 'playbook' : is('mac') ? 'mac' + (/mac os x ((\d+)[.|_](\d+))/.test(ua) ? ' mac' + RegExp.$2 + ' mac' + RegExp.$1.replace('.', '_') : '') : is('win') ? 'win' + (is('windows nt 6.2') ? ' win8' : is('windows nt 6.1') ? ' win7' : is('windows nt 6.0') ? ' vista' : is('windows nt 5.2') || is('windows nt 5.1') ? ' win_xp' : is('windows nt 5.0') ? ' win_2k' : is('windows nt 4.0') || is('WinNT4.0') ? ' win_nt' : '') : is('freebsd') ? 'freebsd' : is('x11|linux') ? 'linux' : '', /[; |\[](([a-z]{2})(\-[a-z]{2})?)[)|;|\]]/i.test(ua) ? (lang + RegExp.$2).replace('-', '_') + (RegExp.$3 != '' ? (' ' + lang + RegExp.$1).replace('-', '_') : '') : '', is('ipad|iphone|ipod') && !is('safari') ? 'ipad_app' : '',]; console.debug(ua); function screenSize() { var w = window.outerWidth || html.clientWidth; var h = window.outerHeight || html.clientHeight; uaInfo.orientation = w < h ? 'portrait' : 'landscape'; html.className = html.className.replace(/ ?orientation_\w+/g, '').replace(/ [min|max|cl]+[w|h]_\d+/g, ''); for (var i = allScreens - 1; i >= 0; i--) { if (w >= screens[i]) { uaInfo.maxw = screens[i]; break } } widthClasses = ''; for (var info in uaInfo) { widthClasses += ' ' + info + '_' + uaInfo[info] } html.className = html.className + widthClasses; return widthClasses } window.onresize = screenSize; screenSize(); function retina() { var r = window.devicePixelRatio > 1; if (r) { html.className += ' retina' } else { html.className += ' non-retina' } } retina(); var cssbs = b.join(' ') + ' js '; html.className = (cssbs + html.className.replace(/\b(no[-|_]?)?js\b/g, '')).replace(/^ /, '').replace(/ +/g, ' '); return cssbs } css_browser_selector(navigator.userAgent);
\ No newline at end of file
diff --git a/packages/app/client/public/global.css b/packages/app/client/public/global.css
new file mode 100644
index 000000000..395e9435b
--- /dev/null
+++ b/packages/app/client/public/global.css
@@ -0,0 +1,29 @@
+#root {
+ width: 100%;
+ height: 100%;
+}
+/* width */
+.win ::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+/* Track */
+.win ::-webkit-scrollbar-track {
+ background: #f1f1f1;
+}
+
+/* Handle */
+.win ::-webkit-scrollbar-thumb {
+ background: #c0c0c0;
+ border-radius: 4px;
+}
+
+/* Handle on hover */
+.win ::-webkit-scrollbar-thumb:hover {
+ background: #a8a8a8;
+}
+
+.win .rc-virtual-list-scrollbar-thumb {
+ background: #c0c0c0 !important;
+}
diff --git a/packages/app/client/public/set-router.js b/packages/app/client/public/set-router.js
new file mode 100644
index 000000000..6e9c95382
--- /dev/null
+++ b/packages/app/client/public/set-router.js
@@ -0,0 +1,2 @@
+var match = location.pathname.match(/^\/apps\/([^/]*)\//);
+window.routerBase = match ? match[0] : "/";
\ No newline at end of file
diff --git a/packages/app/client/src/pages/document.ejs b/packages/app/client/src/pages/document.ejs
deleted file mode 100644
index 8747521a6..000000000
--- a/packages/app/client/src/pages/document.ejs
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
- Loading...
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/app/client/tsconfig.json b/packages/app/client/tsconfig.json
index cce36a3b9..010fbcb06 100644
--- a/packages/app/client/tsconfig.json
+++ b/packages/app/client/tsconfig.json
@@ -22,5 +22,16 @@
"allowSyntheticDefaultImports": true
},
"include": ["mock/**/*", "src/**/*", "config/**/*", ".umirc.ts", "typings.d.ts"],
- "exclude": ["node_modules", "lib", "es", "dist", "typings", "**/__test__", "test", "docs", "tests"]
+ "exclude": [
+ "node_modules",
+ "lib",
+ "es",
+ "public",
+ "dist",
+ "typings",
+ "**/__test__",
+ "test",
+ "docs",
+ "tests"
+ ]
}
diff --git a/packages/app/server/package.json b/packages/app/server/package.json
index 09b1d218b..383b130ae 100644
--- a/packages/app/server/package.json
+++ b/packages/app/server/package.json
@@ -1,12 +1,12 @@
{
"name": "@nocobase/app-server",
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
- "@nocobase/preset-nocobase": "0.9.4-alpha.2"
+ "@nocobase/preset-nocobase": "0.10.0-alpha.1"
},
"repository": {
"type": "git",
diff --git a/packages/core/acl/package.json b/packages/core/acl/package.json
index 95eab6f94..9b015dde1 100644
--- a/packages/core/acl/package.json
+++ b/packages/core/acl/package.json
@@ -1,13 +1,13 @@
{
"name": "@nocobase/acl",
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"description": "",
"license": "Apache-2.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
- "@nocobase/resourcer": "0.9.4-alpha.2",
- "@nocobase/utils": "0.9.4-alpha.2",
+ "@nocobase/resourcer": "0.10.0-alpha.1",
+ "@nocobase/utils": "0.10.0-alpha.1",
"minimatch": "^5.1.1"
},
"repository": {
diff --git a/packages/core/actions/package.json b/packages/core/actions/package.json
index cdc8016bf..8e0234360 100644
--- a/packages/core/actions/package.json
+++ b/packages/core/actions/package.json
@@ -1,14 +1,14 @@
{
"name": "@nocobase/actions",
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"description": "",
"license": "Apache-2.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
- "@nocobase/cache": "0.9.4-alpha.2",
- "@nocobase/database": "0.9.4-alpha.2",
- "@nocobase/resourcer": "0.9.4-alpha.2"
+ "@nocobase/cache": "0.10.0-alpha.1",
+ "@nocobase/database": "0.10.0-alpha.1",
+ "@nocobase/resourcer": "0.10.0-alpha.1"
},
"repository": {
"type": "git",
diff --git a/packages/core/auth/package.json b/packages/core/auth/package.json
index a5648a3bc..33fb85e81 100644
--- a/packages/core/auth/package.json
+++ b/packages/core/auth/package.json
@@ -1,19 +1,19 @@
{
"name": "@nocobase/auth",
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"description": "",
"license": "Apache-2.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
- "@nocobase/database": "0.9.4-alpha.2",
- "@nocobase/resourcer": "0.9.4-alpha.2",
- "@nocobase/utils": "0.9.4-alpha.2",
- "@nocobase/actions": "0.9.4-alpha.2"
+ "@nocobase/actions": "0.10.0-alpha.1",
+ "@nocobase/database": "0.10.0-alpha.1",
+ "@nocobase/resourcer": "0.10.0-alpha.1",
+ "@nocobase/utils": "0.10.0-alpha.1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nocobase/nocobase.git",
"directory": "packages/auth"
}
-}
\ No newline at end of file
+}
diff --git a/packages/core/build/package.json b/packages/core/build/package.json
index 9223f9336..a7e148c59 100755
--- a/packages/core/build/package.json
+++ b/packages/core/build/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/build",
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"description": "Library build tool based on rollup.",
"main": "lib/index.js",
"bin": {
diff --git a/packages/core/build/src/fixtures/build/babel-syntax/package.json b/packages/core/build/src/fixtures/build/babel-syntax/package.json
index 1368588bd..f86651c7b 100755
--- a/packages/core/build/src/fixtures/build/babel-syntax/package.json
+++ b/packages/core/build/src/fixtures/build/babel-syntax/package.json
@@ -1,5 +1,5 @@
{
"dependencies": {
- "react": "^16.8.4"
+ "react": "^18.0.0"
}
}
diff --git a/packages/core/build/src/fixtures/build/rollup-resolve-jsx/package.json b/packages/core/build/src/fixtures/build/rollup-resolve-jsx/package.json
index 55ef290f1..e7b40d62e 100755
--- a/packages/core/build/src/fixtures/build/rollup-resolve-jsx/package.json
+++ b/packages/core/build/src/fixtures/build/rollup-resolve-jsx/package.json
@@ -1,5 +1,5 @@
{
"dependencies": {
- "react": "16"
+ "react": "18.0.0"
}
}
diff --git a/packages/core/build/src/fixtures/build/rollup-svgr/package.json b/packages/core/build/src/fixtures/build/rollup-svgr/package.json
index 55ef290f1..e7b40d62e 100755
--- a/packages/core/build/src/fixtures/build/rollup-svgr/package.json
+++ b/packages/core/build/src/fixtures/build/rollup-svgr/package.json
@@ -1,5 +1,5 @@
{
"dependencies": {
- "react": "16"
+ "react": "18.0.0"
}
}
diff --git a/packages/core/cache/package.json b/packages/core/cache/package.json
index 19684a9d2..559666bf3 100644
--- a/packages/core/cache/package.json
+++ b/packages/core/cache/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/cache",
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"description": "",
"license": "Apache-2.0",
"main": "./lib/index.js",
diff --git a/packages/core/cli/package.json b/packages/core/cli/package.json
index 63eb9aad5..8854c5484 100644
--- a/packages/core/cli/package.json
+++ b/packages/core/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/cli",
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"description": "",
"license": "Apache-2.0",
"main": "./src/index.js",
@@ -8,16 +8,19 @@
"nocobase": "./bin/index.js"
},
"dependencies": {
+ "@types/fs-extra": "^11.0.1",
"chalk": "^4.1.1",
"commander": "^9.2.0",
"dotenv": "^10.0.0",
"execa": "^5.1.1",
+ "fast-glob": "^3.2.12",
+ "fs-extra": "^11.1.1",
"pm2": "^5.2.0",
"portfinder": "^1.0.28",
"serve": "^13.0.2"
},
"devDependencies": {
- "@nocobase/devtools": "0.9.4-alpha.2"
+ "@nocobase/devtools": "0.10.0-alpha.1"
},
"repository": {
"type": "git",
diff --git a/packages/core/cli/src/commands/clean.js b/packages/core/cli/src/commands/clean.js
index 9d0de95d5..34906e6f2 100644
--- a/packages/core/cli/src/commands/clean.js
+++ b/packages/core/cli/src/commands/clean.js
@@ -14,6 +14,7 @@ module.exports = (cli) => {
if (!isDev()) {
return;
}
- run('rimraf', ['-rf', 'packages/*/*/{lib,esm,es,dist}']);
+ run('rimraf', ['-rf', './**/{.umi,.umi-production}']);
+ run('rimraf', ['-rf', 'packages/*/*/{lib,esm,es,dist,node_modules}']);
});
};
diff --git a/packages/core/cli/src/commands/doc.js b/packages/core/cli/src/commands/doc.js
index 581f889a3..98efc065c 100644
--- a/packages/core/cli/src/commands/doc.js
+++ b/packages/core/cli/src/commands/doc.js
@@ -1,6 +1,8 @@
const { Command } = require('commander');
const { resolve, isAbsolute } = require('path');
const { run, isDev } = require('../util');
+const fs = require('fs-extra');
+const glob = require('fast-glob');
/**
*
@@ -9,30 +11,57 @@ const { run, isDev } = require('../util');
module.exports = (cli) => {
cli
.command('doc')
+ .argument('[dev|build|serve]')
+ .argument('[packages]')
.option('--lang [lang]')
.allowUnknownOption()
- .action((options) => {
+ .action((command, pkg, options) => {
if (!isDev()) {
return;
}
+
+ // 指定项目目录
+ let cwd = process.cwd();
+ if (command === undefined && pkg === undefined) {
+ command = 'dev';
+ } else if (!['dev', 'build', 'serve'].includes(command) && pkg === undefined) {
+ pkg = command;
+ command = 'dev';
+ cwd = resolve(process.cwd(), 'packages', pkg);
+ }
+ if (pkg !== undefined) {
+ process.env.APP_ROOT = `packages/${pkg}`;
+ }
+
+ // 删除 tmp 目录
+ const tmpDir = glob.sync(
+ ['.dumi/tmp', '.dumi/tmp-production', 'packages/*/*/.dumi/tmp', 'packages/*/*.dumi/tmp-production'],
+ {
+ cwd: process.cwd(),
+ onlyDirectories: true,
+ },
+ );
+ tmpDir.forEach((dir) => {
+ fs.removeSync(dir);
+ });
+
+ // 设置语言
process.env.DOC_LANG = options.lang || 'en-US';
- const docThemePath = process.env.DOC_THEME_PATH;
- if (!docThemePath) {
- process.env.DUMI_THEME = resolve(process.cwd(), 'packages/core/dumi-theme-nocobase/src');
+
+ if (command === 'serve') {
+ // 如果是多语言,则需要进入内部,如果不是多语言,则直接进入 docs/dist
+ let dir = resolve(cwd, 'docs/dist', process.env.DOC_LANG);
+ if (!existsSync(dir)) {
+ dir = resolve(cwd, 'docs/dist');
+ }
+ run('serve', [dir]);
} else {
- process.env.DUMI_THEME = isAbsolute(docThemePath) ? docThemePath : resolve(process.cwd(), docThemePath);
- }
- // TODO(bug): space replace = not work
- const index = process.argv.indexOf(`--lang=${options.lang}`);
- if (index > 0) {
- process.argv.splice(index, 1);
- }
- const argv = process.argv.slice(3);
- const argument = argv.shift() || 'dev';
- if (argument === 'serve') {
- run('serve', [`${resolve(process.cwd(), 'docs/dist', process.env.DOC_LANG)}`, ...argv]);
- } else {
- run('dumi', [argument, ...argv]);
+ run('dumi', [command], {
+ env: {
+ APP_ROOT: process.env.APP_ROOT,
+ DOC_LANG: process.env.DOC_LANG,
+ },
+ });
}
});
};
diff --git a/packages/core/client/.dumi/global.less b/packages/core/client/.dumi/global.less
new file mode 100644
index 000000000..75f241ae0
--- /dev/null
+++ b/packages/core/client/.dumi/global.less
@@ -0,0 +1 @@
+@import 'antd/dist/antd.css';
diff --git a/packages/core/client/.dumi/theme/builtins/LangSwitch/index.tsx b/packages/core/client/.dumi/theme/builtins/LangSwitch/index.tsx
new file mode 100644
index 000000000..4111f9eb6
--- /dev/null
+++ b/packages/core/client/.dumi/theme/builtins/LangSwitch/index.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+
+const LangSwitch = () => {
+ const { hostname } = window.location
+ if (hostname === 'localhost') return null;
+ const en = window.location.href.replace(hostname, 'docs.nocobase.com')
+ const cn = window.location.href.replace(hostname, 'docs-cn.nocobase.com')
+ return EN | 中文
+}
+
+export default LangSwitch;
diff --git a/packages/core/client/.dumi/theme/builtins/Previewer/index.tsx b/packages/core/client/.dumi/theme/builtins/Previewer/index.tsx
new file mode 100644
index 000000000..9ec63f2cc
--- /dev/null
+++ b/packages/core/client/.dumi/theme/builtins/Previewer/index.tsx
@@ -0,0 +1,23 @@
+import React, { FC, useRef, useEffect, Suspense } from 'react';
+import { Root, createRoot } from 'react-dom/client';
+import { IPreviewerProps } from 'dumi';
+import DefaultPreviewer from 'dumi/theme-default/builtins/Previewer';
+
+const Previewer: FC = ({ children, ...props }) => {
+ const ref = useRef(null);
+ useEffect(() => {
+ let root: Root
+ if (ref.current) {
+ root = createRoot(ref.current)
+ root.render(loading...}>{children} )
+ }
+ return () => {
+ if (root) {
+ root.unmount()
+ }
+ }
+ }, []);
+ return
;
+};
+
+export default Previewer;
diff --git a/packages/core/client/.dumirc.ts b/packages/core/client/.dumirc.ts
new file mode 100644
index 000000000..18cf515ab
--- /dev/null
+++ b/packages/core/client/.dumirc.ts
@@ -0,0 +1,56 @@
+import { defineConfig } from 'dumi';
+import { defineThemeConfig } from 'dumi-theme-nocobase'
+import { getUmiConfig } from '@nocobase/devtools/umiConfig';
+import fs from 'fs';
+import path from 'path';
+
+const contributingPath = path.resolve(__dirname, './contributing.md');
+const docsContributingPath = path.resolve(__dirname, './docs/contributing.md');
+
+// check if the target path already exists, and remove it if it does
+if (fs.existsSync(docsContributingPath)) {
+ fs.unlinkSync(docsContributingPath);
+}
+
+fs.copyFileSync(contributingPath, docsContributingPath);
+
+const umiConfig = getUmiConfig();
+
+export default defineConfig({
+ hash: true,
+ alias: {
+ ...umiConfig.alias,
+ },
+ resolve: {
+ atomDirs: [
+ { type: 'component', dir: 'src' },
+ { type: 'component', dir: 'src/schema-component/antd' },
+ { type: 'component', dir: 'src/route-switch/antd' },
+ ]
+ },
+ themeConfig: defineThemeConfig({
+ title: 'NocoBase',
+ logo: 'https://www.nocobase.com/images/logo.png',
+ github: 'https://github.com/nocobase/nocobase',
+ footer: 'nocobase | Copyright © 2022',
+ sidebarGroupModePath: ['/components'],
+ nav: [
+ {
+ title: 'Intro',
+ link: '/intro',
+ },
+ {
+ title: 'Client',
+ link: '/components/acl',
+ },
+ {
+ title: 'Develop',
+ link: '/develop',
+ },
+ {
+ title: 'Contributing',
+ link: '/contributing',
+ }
+ ]
+ })
+});
diff --git a/packages/core/client/.umirc.ts b/packages/core/client/.umirc.ts
deleted file mode 100644
index 1d959e7a8..000000000
--- a/packages/core/client/.umirc.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { resolveNocobasePackagesAlias } from '@nocobase/devtools/umiConfig';
-import { defineConfig } from 'dumi';
-
-export default defineConfig({
- title: 'NocoBase',
- // outputPath: `./docs/dist/${lang}`,
- mode: 'site',
- resolve: {
- includes: ['./'],
- },
- // locales: [[lang, lang]],
- hash: true,
- logo: 'https://www.nocobase.com/images/logo.png',
- navs: [
- null,
- {
- title: 'GitHub',
- path: 'https://github.com/nocobase/nocobase',
- },
- ],
- chainWebpack(memo) {
- resolveNocobasePackagesAlias(memo);
- },
-});
diff --git a/packages/core/client/contributing.md b/packages/core/client/contributing.md
index 515749f6e..1f4f1b8a1 100644
--- a/packages/core/client/contributing.md
+++ b/packages/core/client/contributing.md
@@ -57,5 +57,5 @@ export default () => Hello NocoBase! ;
也可以引用 demo 文件
```markdown
-
+
```
diff --git a/packages/core/client/develop.md b/packages/core/client/develop.md
deleted file mode 100644
index fb934422a..000000000
--- a/packages/core/client/develop.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-sidemenu: false
----
-
-
diff --git a/packages/core/client/docs/develop.md b/packages/core/client/docs/develop.md
new file mode 100644
index 000000000..78c8cf4f7
--- /dev/null
+++ b/packages/core/client/docs/develop.md
@@ -0,0 +1,5 @@
+---
+sidebar: false
+---
+
+
diff --git a/packages/core/client/intro.md b/packages/core/client/docs/intro.md
similarity index 98%
rename from packages/core/client/intro.md
rename to packages/core/client/docs/intro.md
index 106ff364d..1951d0768 100644
--- a/packages/core/client/intro.md
+++ b/packages/core/client/docs/intro.md
@@ -34,7 +34,7 @@ export default app.compose();
* title: Router
*/
import React from 'react';
-import { Route, Switch, Link, MemoryRouter as Router } from 'react-router-dom';
+import { Route, Routes, Link, MemoryRouter as Router } from 'react-router-dom';
const Home = () => Home ;
const About = () => About ;
@@ -42,14 +42,10 @@ const About = () => About ;
const App = () => (
Home, About
-
-
-
-
-
-
-
-
+
+ }>
+ }>
+
);
@@ -74,7 +70,6 @@ const routes: RouteRedirectProps[] = [
{
type: 'route',
path: '/',
- exact: true,
component: 'Home',
},
{
@@ -515,7 +510,6 @@ const routes: RouteRedirectProps[] = [
{
type: 'route',
path: '/',
- exact: true,
component: 'RouteSchemaComponent',
schema: {
name: 'home',
@@ -642,7 +636,7 @@ dn.insertAfterEnd(schema);
相关例子如下:
-
+
insertAdjacent 操作不仅可以用于新增节点,也可以用于现有节点的位置移动,如以下拖拽排序的例子:
@@ -815,7 +809,7 @@ const { data, loading } = useRequest();
但是这样的方式不利于 Providers 的管理和扩展,为此提炼了 `compose()` 函数用于配置多个 providers,如下:
-
+
## Application
diff --git a/packages/core/client/package.json b/packages/core/client/package.json
index 796e1f1b2..0f1816534 100644
--- a/packages/core/client/package.json
+++ b/packages/core/client/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/client",
- "version": "0.9.4-alpha.2",
+ "version": "0.10.0-alpha.1",
"license": "Apache-2.0",
"main": "lib",
"module": "es/index.js",
@@ -13,9 +13,9 @@
"@formily/antd": "2.2.24",
"@formily/core": "2.2.24",
"@formily/react": "2.2.24",
- "@nocobase/evaluators": "0.9.4-alpha.2",
- "@nocobase/sdk": "0.9.4-alpha.2",
- "@nocobase/utils": "0.9.4-alpha.2",
+ "@nocobase/evaluators": "0.10.0-alpha.1",
+ "@nocobase/sdk": "0.10.0-alpha.1",
+ "@nocobase/utils": "0.10.0-alpha.1",
"ahooks": "^3.7.2",
"antd": "^4.24.8",
"axios": "^0.26.1",
@@ -40,24 +40,24 @@
"react-image-lightbox": "^5.1.4",
"react-js-cron": "^3.1.0",
"react-quill": "^1.3.5",
- "react-router-dom": "^5.2.0",
+ "react-router-dom": "^6.11.2",
"react-to-print": "^2.14.7",
"sanitize-html": "2.10.0",
"solarlunar-es": "^1.0.9",
"use-deep-compare-effect": "^1.8.1"
},
"peerDependencies": {
- "@types/react": ">=16.8.0 || >=17.0.0",
- "@types/react-dom": ">=16.8.0 || >=17.0.0",
- "react": ">=16.8.0 || >=17.0.0",
- "react-dom": ">=16.8.0",
- "react-is": ">=16.8.0 || >=17.0.0"
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0",
+ "react-is": ">=18.0.0"
},
"devDependencies": {
"@testing-library/react": "^12.1.2",
"@types/markdown-it": "12.2.3",
"@types/markdown-it-highlightjs": "3.3.1",
- "axios-mock-adapter": "^1.20.0"
+ "axios-mock-adapter": "^1.20.0",
+ "dumi": "^2.2.0",
+ "dumi-theme-nocobase": "^0.2.9"
},
"gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644"
}
diff --git a/packages/core/client/src/acl/ACLProvider.tsx b/packages/core/client/src/acl/ACLProvider.tsx
index e263809e2..d5e2e3d26 100644
--- a/packages/core/client/src/acl/ACLProvider.tsx
+++ b/packages/core/client/src/acl/ACLProvider.tsx
@@ -2,7 +2,7 @@ import { Field } from '@formily/core';
import { Schema, useField, useFieldSchema } from '@formily/react';
import { Spin } from 'antd';
import React, { createContext, useContext, useEffect } from 'react';
-import { Redirect } from 'react-router-dom';
+import { Navigate } from 'react-router-dom';
import { useAPIClient, useRequest } from '../api-client';
import { useBlockRequestContext } from '../block-provider/BlockProvider';
import { useCollection } from '../collection-manager';
@@ -52,7 +52,7 @@ export const ACLRolesCheckProvider = (props) => {
return ;
}
if (result.error) {
- return ;
+ return ;
}
return {props.children} ;
};
diff --git a/packages/core/client/src/acl/index.md b/packages/core/client/src/acl/index.md
index fa0ae96ff..4de99929a 100644
--- a/packages/core/client/src/acl/index.md
+++ b/packages/core/client/src/acl/index.md
@@ -1,10 +1,7 @@
---
-nav:
- title: Client
- path: /client
group:
title: Client
- path: /client
+ order: 1
---
# ACL
diff --git a/packages/core/client/src/antd-config-provider/index.md b/packages/core/client/src/antd-config-provider/index.md
index c066a8f68..f773165e6 100644
--- a/packages/core/client/src/antd-config-provider/index.md
+++ b/packages/core/client/src/antd-config-provider/index.md
@@ -1,12 +1,11 @@
---
-nav:
- path: /client
group:
- path: /client
+ title: Client
+ order: 1
---
# AntdConfigProvider
为 antd 组件提供统一的全局化配置
-
+
diff --git a/packages/core/client/src/api-client/APIClientProvider.tsx b/packages/core/client/src/api-client/APIClientProvider.tsx
index 29bd5c465..043b5236d 100644
--- a/packages/core/client/src/api-client/APIClientProvider.tsx
+++ b/packages/core/client/src/api-client/APIClientProvider.tsx
@@ -1,9 +1,10 @@
-import React from 'react';
+import React, { ReactNode } from 'react';
import { APIClient } from './APIClient';
import { APIClientContext } from './context';
export interface APIClientProviderProps {
apiClient: APIClient;
+ children?: ReactNode;
}
export const APIClientProvider: React.FC = (props) => {
diff --git a/packages/core/client/src/api-client/index.md b/packages/core/client/src/api-client/index.md
index 244296d57..e4c67d46d 100644
--- a/packages/core/client/src/api-client/index.md
+++ b/packages/core/client/src/api-client/index.md
@@ -1,8 +1,7 @@
---
-nav:
- path: /client
group:
- path: /client
+ title: Client
+ order: 1
---
# APIClient
@@ -55,7 +54,7 @@ const { data, loading, run } = apiClient.service('uid');
`api.service(uid)` 的例子,ComponentB 里刷新 ComponentA 的请求数据
-
+
## APIClientProvider
@@ -99,7 +98,7 @@ run({
例子如下:
-
+
或者是 NocoBase 的 resource & action 请求:
@@ -120,7 +119,7 @@ run({
例子如下:
-
+
也可以是自定义的异步函数:
diff --git a/packages/core/client/src/application-v2/Application.tsx b/packages/core/client/src/application-v2/Application.tsx
new file mode 100644
index 000000000..6dbfaafd3
--- /dev/null
+++ b/packages/core/client/src/application-v2/Application.tsx
@@ -0,0 +1,91 @@
+import { i18n } from 'i18next';
+import { merge } from 'lodash';
+import get from 'lodash/get';
+import set from 'lodash/set';
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import { I18nextProvider } from 'react-i18next';
+import { APIClient, APIClientProvider } from '../api-client';
+import { Plugin } from './Plugin';
+import { PluginManager } from './PluginManager';
+import { Router } from './Router';
+import { AppComponent, defaultAppComponents } from './components';
+import { ApplicationOptions } from './types';
+
+export class Application {
+ providers: any[];
+ router: Router;
+ plugins: Map;
+ scopes: Record;
+ i18n: i18n;
+ apiClient: APIClient;
+ components: any;
+ pm: PluginManager;
+
+ constructor(protected _options: ApplicationOptions) {
+ this.providers = [];
+ this.plugins = new Map();
+ this.scopes = merge(this.scopes, _options.scopes || {});
+ this.components = merge(defaultAppComponents, _options.components || {});
+ this.apiClient = new APIClient(_options.apiClient);
+ this.router = new Router(_options.router, { app: this });
+ this.pm = new PluginManager(this);
+ this.useDefaultProviders();
+ }
+
+ get options() {
+ return this._options;
+ }
+
+ useDefaultProviders() {
+ this.use([APIClientProvider, { apiClient: this.apiClient }]);
+ this.use(I18nextProvider, { i18n: this.i18n });
+ }
+
+ getPlugin(name: string) {
+ return this.plugins.get(name);
+ }
+
+ getComponent(name: string) {
+ return get(this.components, name);
+ }
+
+ renderComponent(name: string, props = {}) {
+ return React.createElement(this.getComponent(name), props);
+ }
+
+ registerComponent(name: string, component: any) {
+ set(this.components, name, component);
+ }
+
+ registerComponents(components: any) {
+ Object.keys(components).forEach((name) => {
+ this.registerComponent(name, components[name]);
+ });
+ }
+
+ registerScopes(scopes: Record) {
+ this.scopes = merge(this.scopes, scopes);
+ }
+
+ use(component: any, props?: any) {
+ this.providers.push(props ? [component, props] : component);
+ }
+
+ async load() {
+ return this.pm.load();
+ }
+
+ getRootComponent() {
+ return () => ;
+ }
+
+ mount(selector: string) {
+ const container = typeof selector === 'string' ? document.querySelector(selector) : selector;
+ if (container) {
+ const App = this.getRootComponent();
+ const root = createRoot(container);
+ root.render( );
+ }
+ }
+}
diff --git a/packages/core/client/src/application-v2/Plugin.ts b/packages/core/client/src/application-v2/Plugin.ts
new file mode 100644
index 000000000..9abf92693
--- /dev/null
+++ b/packages/core/client/src/application-v2/Plugin.ts
@@ -0,0 +1,30 @@
+import { Application } from './Application';
+import { PluginOptions } from './types';
+
+export class Plugin {
+ constructor(protected _options: PluginOptions, protected app: Application) {
+ this.app = app;
+ }
+
+ get options() {
+ return this._options;
+ }
+
+ get name() {
+ return this._options.name;
+ }
+
+ get pm() {
+ return this.app.pm;
+ }
+
+ get router() {
+ return this.app.router;
+ }
+
+ async afterAdd() {}
+
+ async beforeLoad() {}
+
+ async load() {}
+}
diff --git a/packages/core/client/src/application-v2/PluginManager.ts b/packages/core/client/src/application-v2/PluginManager.ts
new file mode 100644
index 000000000..32f6221d3
--- /dev/null
+++ b/packages/core/client/src/application-v2/PluginManager.ts
@@ -0,0 +1,83 @@
+import { Application } from './Application';
+import { Plugin } from './Plugin';
+import { type PluginOptions } from './types';
+
+export interface PluginManagerOptions {
+ plugins: string[];
+}
+
+type PluginNameOrClass = string | typeof Plugin;
+
+export class PluginManager {
+ protected pluginInstances: Map;
+ protected pluginPrepares: Map;
+
+ constructor(protected app: Application) {
+ this.pluginInstances = new Map();
+ this.pluginPrepares = new Map();
+ this.addPresetPlugins();
+ }
+
+ protected addPresetPlugins() {
+ const { plugins } = this.app.options;
+ for (const plugin of plugins) {
+ if (typeof plugin === 'string') {
+ this.prepare(plugin);
+ } else {
+ this.prepare(...plugin);
+ }
+ }
+ }
+
+ prepare(nameOrClass: PluginNameOrClass, options?: PluginOptions) {
+ let opts: any = {};
+ if (typeof nameOrClass === 'string') {
+ opts['name'] = nameOrClass;
+ } else {
+ opts = { ...options, Plugin: nameOrClass };
+ }
+ return this.pluginPrepares.set(opts.name, opts);
+ }
+
+ async add(nameOrClass: PluginNameOrClass, options?: PluginOptions) {
+ let opts: any = {};
+ if (typeof nameOrClass === 'string') {
+ opts['name'] = nameOrClass;
+ } else {
+ opts = { ...options, Plugin: nameOrClass };
+ }
+ const plugin = await this.makePlugin(opts);
+ this.pluginInstances.set(plugin.name, plugin);
+ await plugin.afterAdd();
+ return plugin;
+ }
+
+ async makePlugin(opts) {
+ const { importPlugins } = this.app.options;
+ let P: typeof Plugin = opts.Plugin;
+ if (!P) {
+ P = await importPlugins(opts.name);
+ }
+ if (!P) {
+ throw new Error(`Plugin "${opts.name} " not found`);
+ }
+ console.log(opts, P);
+ return new P(opts, this.app);
+ }
+
+ async load() {
+ for (const opts of this.pluginPrepares.values()) {
+ const plugin = await this.makePlugin(opts);
+ this.pluginInstances.set(plugin.name, plugin);
+ await plugin.afterAdd();
+ }
+
+ for (const plugin of this.pluginInstances.values()) {
+ await plugin.beforeLoad();
+ }
+
+ for (const plugin of this.pluginInstances.values()) {
+ await plugin.load();
+ }
+ }
+}
diff --git a/packages/core/client/src/application-v2/Router.ts b/packages/core/client/src/application-v2/Router.ts
new file mode 100644
index 000000000..5886bd542
--- /dev/null
+++ b/packages/core/client/src/application-v2/Router.ts
@@ -0,0 +1,67 @@
+import set from 'lodash/set';
+import { createBrowserRouter, createHashRouter, createMemoryRouter } from 'react-router-dom';
+import { Application } from './Application';
+import { RouterOptions } from './types';
+
+export class Router {
+ protected app: Application;
+ protected routes: Map;
+
+ constructor(protected options?: RouterOptions, protected context?: any) {
+ this.routes = new Map();
+ this.app = context.app;
+ }
+
+ getRoutes() {
+ const routes = {};
+ for (const [name, route] of this.routes) {
+ set(routes, name.split('.').join('.children.'), route);
+ }
+
+ const transform = (item) => {
+ if (item.component) {
+ item.Component = this.app.getComponent(item.component);
+ }
+ return item;
+ };
+
+ const toArr = (items: any) => {
+ return Object.values(items || {}).map((item) => {
+ if (item.children) {
+ item.children = toArr(item.children);
+ }
+ return transform(item);
+ });
+ };
+ return toArr(routes);
+ }
+
+ createRouter() {
+ const { type, ...opts } = this.options;
+ const routes = this.getRoutes();
+ if (routes.length === 0) {
+ return null;
+ }
+ switch (type) {
+ case 'hash':
+ return createHashRouter(routes, opts) as any;
+ case 'browser':
+ return createBrowserRouter(routes, opts) as any;
+ case 'memory':
+ return createMemoryRouter(routes, opts) as any;
+ default:
+ return createMemoryRouter(routes, opts) as any;
+ }
+ }
+
+ add(name: string, route: any) {
+ this.routes.set(name, route);
+ Object.keys(route.children || {}).forEach((key) => {
+ this.routes.set(`${name}.${key}`, route.children[key]);
+ });
+ }
+
+ remove(name: string) {
+ this.routes.delete(name);
+ }
+}
diff --git a/packages/core/client/src/application-v2/components/AppComponent.tsx b/packages/core/client/src/application-v2/components/AppComponent.tsx
new file mode 100644
index 000000000..7b7de37aa
--- /dev/null
+++ b/packages/core/client/src/application-v2/components/AppComponent.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { ApplicationContext } from '../context';
+import { useApp, useLoad } from '../hooks';
+
+const Internal = React.memo(() => {
+ const app = useApp();
+ const loading = useLoad();
+ if (loading) {
+ return app.renderComponent('App.Spin');
+ }
+ return app.renderComponent('App.Main', {
+ app,
+ providers: app.providers,
+ });
+});
+
+export const AppComponent = (props) => {
+ const { app } = props;
+ return (
+
+
+
+ );
+};
diff --git a/packages/core/client/src/application-v2/components/MainComponent.tsx b/packages/core/client/src/application-v2/components/MainComponent.tsx
new file mode 100644
index 000000000..ae9a046f0
--- /dev/null
+++ b/packages/core/client/src/application-v2/components/MainComponent.tsx
@@ -0,0 +1,9 @@
+import React, { useMemo } from 'react';
+import { Application } from '../Application';
+import { RouterProvider } from './RouterProvider';
+
+export const MainComponent = React.memo((props: { app: Application; providers: any[] }) => {
+ const { app, providers } = props;
+ const router = useMemo(() => app.router.createRouter(), []);
+ return ;
+});
diff --git a/packages/core/client/src/application-v2/components/RouterProvider.tsx b/packages/core/client/src/application-v2/components/RouterProvider.tsx
new file mode 100644
index 000000000..06131985e
--- /dev/null
+++ b/packages/core/client/src/application-v2/components/RouterProvider.tsx
@@ -0,0 +1,119 @@
+// @ts-nocheck
+import type { RouterState } from '@remix-run/router';
+import React from 'react';
+import {
+ UNSAFE_DataRouterContext as DataRouterContext,
+ UNSAFE_DataRouterStateContext as DataRouterStateContext,
+ UNSAFE_LocationContext as LocationContext,
+ UNSAFE_RouteContext as RouteContext,
+ Router,
+ UNSAFE_useRoutesImpl as useRoutesImpl,
+ type DataRouteObject,
+ type RouterProviderProps,
+} from 'react-router';
+import { compose } from '../compose';
+
+const START_TRANSITION = 'startTransition';
+
+/**
+ * Given a Remix Router instance, render the appropriate UI
+ */
+export function RouterProvider({
+ fallbackElement,
+ router,
+ providers,
+}: RouterProviderProps & { providers?: any }): React.ReactElement {
+ // Need to use a layout effect here so we are subscribed early enough to
+ // pick up on any render-driven redirects/navigations (useEffect/)
+ const [state, setStateImpl] = React.useState(router.state);
+ const setState = React.useCallback(
+ (newState: RouterState) => {
+ START_TRANSITION in React ? React[START_TRANSITION](() => setStateImpl(newState)) : setStateImpl(newState);
+ },
+ [setStateImpl],
+ );
+ React.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
+
+ const navigator = React.useMemo((): Navigator => {
+ return {
+ createHref: router.createHref,
+ encodeLocation: router.encodeLocation,
+ go: (n) => router.navigate(n),
+ push: (to, state, opts) =>
+ router.navigate(to, {
+ state,
+ preventScrollReset: opts?.preventScrollReset,
+ }),
+ replace: (to, state, opts) =>
+ router.navigate(to, {
+ replace: true,
+ state,
+ preventScrollReset: opts?.preventScrollReset,
+ }),
+ };
+ }, [router]);
+
+ const basename = router.basename || '/';
+
+ const dataRouterContext = React.useMemo(
+ () => ({
+ router,
+ navigator,
+ static: false,
+ basename,
+ }),
+ [router, navigator, basename],
+ );
+ const Providers = compose(...providers)((props) => <>{props.children}>);
+ // The fragment and {null} here are important! We need them to keep React 18's
+ // useId happy when we are server-rendering since we may have a