refactor: upgrade umi, react and react-router-dom (#1921)

* refactor: update umi version 3.x to version 4.x

* refactor: update react-router-dom version to 6.x

* refactor(react-router-dom): change Layout Component `props.children` to `<Outlet />`

* refactor(react-router-dom): change <Route /> props and <RouteSwitch /> correct

* refactor(react-router-dom): replace `<Redirect />` to `<Navigate replace />`

* refactor(react-router-dom): replace `useHistory` to `useNavigate`

* refactor(react-router-dom): replace `useRouteMatch` to `useParams`

* refactor(react-router-dom & dumi): fix <RouteSwitch /> & umi document bug

* refactor(react-router-dom): `useRoutes` Optimize `<RouteSwitch />` code

* refactor(react-router-dom): update `Route` types and docs

* refactor(react-router-dom): optimize RouteSwitch code

* refactor(react-router-dom): `useLocation` no generics type

* refactor(react-router-dom): add `less v3.9.0` to `resolutions` to solve the error of `gulp-less`

* refactor(react-router-dom): fix `<RouteSwitch />`  `props.routes` as an array is not handled

* chore: upgrade `dumi` and refactor docs

* fix: completed code review, add `targets` to solve browser compatibility & removed `chainWebpack`

* refactor(dumi): upgraded dumi under `packages/core/client`

* refactor(dumi): delete `packages/core/dumi-theme-nocobase`

* refactor(dumi): degrade `react`  & replace `dumi-theme-antd` to `dumi-theme-nocobase`

* refactor(dumi): solve conflicts between multiple dumi applications

* fix: login page error in react 17

* refactor(dumi): remove less resolutions

* refactor(dumi): umi add `msfu: true` config

* fix: merge bug

* fix: self code review

* fix: code reivew and test bug

* refactor: upgrade react to 18

* refactor: degrade react types to 17

* chore: fix ci error

* fix: support routerBase & fix workflow page params

* fix(doc): menu externel link

* fix: build error

* fix: delete

* fix: vitest error

* fix: react-router new code replace

* fix: vitest markdown error

* fix: title is none when refresh

* fix: merge error

* fix: sidebar width is wrong

* fix: useProps error

* fix: side-menu-width

* fix: menu selectId is wrong & useProps is string

* fix: menu selected first default & side menu hide when change

* fix: test error & v0.10 change log

* fix: new compnent doc modify

* fix: set umi `fastRefresh=false`

* refactor: application v2

* fix: improve code

* fix: bug

* fix: page = 0 error

* fix: workflow navigate error

* feat: plugin manager

* fix: afterAdd

* feat: update docs

* feat: update docs

* fix: page tab change not work

* fix: login redirect query param doesn't work

* fix: bug and doc

* feat: update docs

* fix: ci error

* fix: merge main

* feat: update docs

* feat: update docs

* feat: update docs

* chore(versions): 😊 publish v0.10.0-alpha.1

* fix: translations

* chore: backend node test max old space size

* docs: add useSearchParams

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
Co-authored-by: ChengLei Shao <chareice@live.com>
This commit is contained in:
jack zhang 2023-06-20 11:48:02 +08:00 committed by GitHub
parent d8ad98e2f7
commit d76e8fb87f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
344 changed files with 8087 additions and 7881 deletions

View File

@ -6,7 +6,6 @@ export default {
'core/cli',
'core/create-nocobase-app',
'core/devtools',
'core/dumi-theme-nocobase',
'app/client',
],
};

1
.dumi/global.less Normal file
View File

@ -0,0 +1 @@
@import 'antd/dist/antd.css';

View File

@ -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 <span><a href={en}>EN</a> | <a href={cn}></a></span>
}
export default LangSwitch;

View File

@ -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<IPreviewerProps> = ({ children, ...props }) => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let root: Root
if (ref.current) {
root = createRoot(ref.current)
root.render(<Suspense fallback={<div>loading...</div>}>{children}</Suspense>)
}
return () => {
if (root) {
root.unmount()
}
}
}, []);
return <DefaultPreviewer {...props}><div ref={ref} /></DefaultPreviewer>;
};
export default Previewer;

54
.dumirc.ts Normal file
View File

@ -0,0 +1,54 @@
import { getUmiConfig } from '@nocobase/devtools/umiConfig';
import { defineConfig } from 'dumi';
import { defineThemeConfig } from 'dumi-theme-nocobase';
import { nav, sidebar } from './docs/config';
const umiConfig = getUmiConfig();
const lang = process.env.DOC_LANG || 'en-US';
console.log('process.env.DOC_LANG', process.env.DOC_LANG);
// 设置多语言的 title
function setTitle(menuChildren) {
if (!menuChildren) return;
menuChildren.forEach((item) => {
if (typeof item === 'object') {
item.title = item[`title.${lang}`] || item.title;
if (item.children) {
setTitle(item.children);
}
}
});
}
if (lang !== 'en-US') {
Object.values(sidebar).forEach(setTitle);
}
export default defineConfig({
hash: true,
alias: {
...umiConfig.alias,
},
outputPath: `./docs/dist/${lang}`,
resolve: {
docDirs: [`./docs/${lang}`]
},
locales: [
{ id: 'en-US', name: 'English' },
{ id: 'zh-CN', name: '中文' },
],
themeConfig: defineThemeConfig({
title: 'NocoBase',
logo: 'https://www.nocobase.com/images/logo.png',
nav: nav.map((item) => ({ ...item, title: (item[`title.${lang}`] || item.title) })),
sidebarEnhance: sidebar as any,
github: 'https://github.com/nocobase/nocobase',
footer: 'nocobase | Copyright © 2022',
localesEnhance: [
{ id: 'zh-CN', switchPrefix: '中' },
{ id: 'en-US', switchPrefix: 'en' }
],
}),
// mfsu: true, // 报错
});

View File

@ -18,6 +18,8 @@ packages/*/*/server.d.ts
packages/*/*/client.js
packages/*/*/server.js
packages/core/build
packages/core/dumi-theme-nocobase
packages/core/devtools
packages/core/database/src/sql-parser/index.js
packages/core/database/src/sql-parser/index.js
**/.dumi/tmp
**/.dumi/tmp-test
**/.dumi/tmp-production

View File

@ -40,6 +40,7 @@ jobs:
- name: Test with Sqlite
run: yarn nocobase install -f && yarn test
env:
NODE_OPTIONS: "--max_old_space_size=4096"
DB_DIALECT: sqlite
DB_STORAGE: /tmp/db.sqlite
DB_UNDERSCORED: ${{ matrix.underscored }}
@ -81,6 +82,7 @@ jobs:
- name: Test with postgres
run: yarn nocobase install -f && yarn test
env:
NODE_OPTIONS: "--max_old_space_size=4096"
DB_DIALECT: postgres
DB_HOST: postgres
DB_PORT: 5432
@ -118,6 +120,7 @@ jobs:
- name: Test with MySQL
run: yarn nocobase install -f && yarn test
env:
NODE_OPTIONS: "--max_old_space_size=4096"
DB_DIALECT: mysql
DB_HOST: mysql
DB_PORT: 3306

6
.gitignore vendored
View File

@ -20,4 +20,8 @@ dist/
docker/**/storage
cache/diskstore-*
*.nbdump
storage/duplicator/*
storage/duplicator/*
**/.dumi/tmp
**/.dumi/tmp-test
**/.dumi/tmp-production
packages/core/client/docs/contributing.md

View File

@ -10,3 +10,6 @@ packages/**/lib/**
packages/**/esm/**
packages/**/node_modules/**
packages/core/client/src/locale/*
**/.dumi/tmp
**/.dumi/tmp-test
**/.dumi/tmp-production

130
.umirc.ts
View File

@ -1,130 +0,0 @@
import transformer from '@umijs/preset-dumi/lib/transformer';
import { defineConfig } from 'dumi';
import fs from 'fs';
import path from 'path';
import menus from './docs/menus';
const lang = process.env.DOC_LANG || 'en-US';
console.log('process.env.DOC_LANG', process.env.DOC_LANG);
const findFilePath = (filename, lang) => {
const filePath = path.resolve(__dirname, `docs/${lang}/${filename}`);
if (fs.existsSync(`${filePath}.md`)) {
return `${filePath}.md`;
}
return;
};
const markdown = (filename, lang) => {
const filePath = findFilePath(filename, lang);
if (!filePath) {
return;
}
return transformer.markdown(fs.readFileSync(filePath, 'utf8').toString(), filePath);
};
const getPath = (value: string) => {
if (!value) {
return '';
}
const keys = value.split('/');
if (keys.pop() === 'index') {
return keys.join('/') || '/';
}
return value;
};
const getTitle = (item, lang) => {
if (lang) {
return item[`title.${lang}`] || item.title;
}
return item.title;
};
const parseMenuItems = (items: any[], lang: string) => {
const menuItems: any[] = [];
for (const item of items) {
if (typeof item === 'string') {
const result = markdown(item, lang);
if (result) {
menuItems.push({
title: result.meta.title,
disabled: result.meta.disabled,
path: getPath(item),
});
}
} else if (item.children) {
menuItems.push({
...item,
title: getTitle(item, lang),
children: parseMenuItems(item.children, lang),
});
} else if (item.path) {
menuItems.push({
...item,
title: getTitle(item, lang),
path: getPath(item.path),
});
} else {
menuItems.push({
...item,
title: getTitle(item, lang),
});
}
}
return menuItems;
};
const navs = [
{
title: 'Welcome',
'title.zh-CN': '欢迎',
path: '/welcome',
},
{
title: 'User manual',
'title.zh-CN': '使用手册',
path: '/manual',
},
{
title: 'Plugin Development',
'title.zh-CN': '插件开发',
path: '/development',
},
{
title: 'API reference',
'title.zh-CN': 'API 参考',
path: '/api',
},
{
title: 'Schema components',
'title.zh-CN': 'Schema 组件库',
path: '/components',
},
{
title: 'GitHub',
path: 'https://github.com/nocobase/nocobase',
},
];
export default defineConfig({
title: 'NocoBase',
outputPath: `./docs/dist/${lang}`,
mode: 'site',
resolve: {
includes: [`./docs/${lang}`],
},
locales: [[lang, lang]],
hash: true,
logo: 'https://www.nocobase.com/images/logo.png',
navs: {
'en-US': navs,
'zh-CN': navs.map((item) => ({ ...item, title: item['title.zh-CN'] || item.title })),
},
menus: Object.keys(menus).reduce((result, key) => {
const items = menus[key];
result[key] = parseMenuItems(items, lang);
return result;
}, {}),
});

View File

@ -6,6 +6,8 @@ English | [中文](./README.zh-CN.md) | [Türkçe](./README.tr-TR.md)
NocoBase is in early stage of development and is subject to frequent changes, please use caution in production environments.
NocoBase v0.10 has been released, see [v0.10: update instructions](http://docs.nocobase.com/welcome/release/v10-changelog) for details.
## We are hiring
We are looking for full-time, remote product design and development colleagues to join the team. If you have a strong interest in NocoBase, please feel free to email us at hello@nocobase.com.

View File

@ -2,10 +2,12 @@
![](https://nocobase.oss-cn-beijing.aliyuncs.com/bbcedd403d31cd1ccc4e9709581f5c2f.png)
**Not:** 📌
**Note:** 📌
NocoBase, geliştirmenin ilk aşamasındadır ve sık sık değişiklik yaparak geliştiriyoruz. Lütfen üretim ortamlarında dikkatli olun.
NocoBase v0.10 has been released, see [v0.10: update instructions](http://docs.nocobase.com/welcome/release/v10-changelog) for details.
## Çalışma arkadaşımız olabilirsiniz
Takıma katılacak tam zamanlı, uzaktan ürün tasarımı ve geliştirme arkadaşları arıyoruz. NocoBase'e güçlü bir ilginiz varsa, lütfen bize hello@nocobase.com adresinden e-posta göndermekten çekinmeyin.

View File

@ -6,6 +6,8 @@
NocoBase 正处在早期开发阶段,可能变动频繁,请谨慎用于生产环境。
NocoBase v0.10 已发布,详情查看 [v0.10 更新说明](http://docs-cn.nocobase.com/welcome/release/v10-changelog)。
## 我们在招聘
我们在寻找全职、远程的产品设计、开发、测试的新同事加入团队,如果你对 NocoBase 有强烈的兴趣欢迎给我们发邮件hello@nocobase.com

View File

@ -1,4 +1,31 @@
export default {
const nav = [
{
title: 'Welcome',
'title.zh-CN': '欢迎',
link: '/welcome/introduction',
},
{
title: 'User manual',
'title.zh-CN': '使用手册',
link: '/manual/quick-start/the-first-app',
},
{
title: 'Plugin Development',
'title.zh-CN': '插件开发',
link: '/development',
},
{
title: 'API reference',
'title.zh-CN': 'API 参考',
link: '/api',
},
{
title: 'Schema components',
'title.zh-CN': 'Schema 组件库',
link: '/components',
},
];
const sidebar = {
'/welcome': [
{
title: 'Welcome',
@ -6,7 +33,7 @@ export default {
'title.tr-TR': 'Hoşgeldiniz',
type: 'group',
children: [
'/welcome/introduction/index',
'/welcome/introduction',
'/welcome/introduction/features',
'/welcome/introduction/when',
// '/welcome/introduction/learning-guide',
@ -22,9 +49,8 @@ export default {
title: 'Installation',
'title.zh-CN': '安装',
'title.TR-TR': 'Kurulum',
type: 'subMenu',
children: [
'/welcome/getting-started/installation/index',
'/welcome/getting-started/installation',
'/welcome/getting-started/installation/docker-compose',
'/welcome/getting-started/installation/create-nocobase-app',
'/welcome/getting-started/installation/git-clone',
@ -34,9 +60,8 @@ export default {
title: 'Upgrading',
'title.zh-CN': '升级',
'title.TR-TR': 'Güncelleme',
type: 'subMenu',
children: [
'/welcome/getting-started/upgrading/index',
'/welcome/getting-started/upgrading',
'/welcome/getting-started/upgrading/docker-compose',
'/welcome/getting-started/upgrading/create-nocobase-app',
'/welcome/getting-started/upgrading/git-clone',
@ -58,7 +83,8 @@ export default {
link: 'https://github.com/nocobase/nocobase/blob/main/CHANGELOG.md',
},
// '/welcome/release/index',
'/welcome/release/v08-changelog',
// '/welcome/release/v08-changelog',
'/welcome/release/v10-changelog',
],
},
{
@ -117,7 +143,7 @@ export default {
'title.TR-TR': 'Başlarken',
type: 'group',
children: [
'/development/index',
'/development',
'/development/your-fisrt-plugin',
'/development/app-ds',
'/development/plugin-ds',
@ -131,14 +157,13 @@ export default {
'title.TR-TR': 'Sunucu',
type: 'group',
children: [
'/development/server/index',
'/development/server',
{
title: 'Collections & Fields',
'title.zh-CN': '数据表和字段',
'title.TR-TR': 'Koleksiyonlar & Alanlar',
type: 'subMenu',
children: [
'/development/server/collections/index',
'/development/server/collections',
'/development/server/collections/options',
'/development/server/collections/configure',
'/development/server/collections/association-fields',
@ -162,14 +187,13 @@ export default {
'title.TR-TR': 'Ziyaretçi(Client)',
type: 'group',
children: [
'/development/client/index',
'/development/client',
{
title: 'UI 设计器',
'title.zh-CN': 'UI 设计器',
'title.TR-TR': 'Kullanıcı Arayüz Tasarımcısı',
type: 'subMenu',
children: [
// '/development/client/ui-schema-designer/index',
// '/development/client/ui-schema-designer',
'/development/client/ui-schema-designer/what-is-ui-schema',
'/development/client/ui-schema-designer/extending-schema-components',
// '/development/client/ui-schema-designer/insert-adjacent',
@ -189,12 +213,12 @@ export default {
},
],
'/api': [
'/api/index',
'/api',
'/api/env',
{
title: 'HTTP API',
type: 'subMenu',
children: ['/api/http/index', '/api/http/rest-api'],
children: ['/api/http', '/api/http/rest-api'],
},
{
title: '@nocobase/server',
@ -209,7 +233,7 @@ export default {
title: '@nocobase/database',
type: 'subMenu',
children: [
'/api/database/index',
'/api/database',
'/api/database/collection',
'/api/database/field',
'/api/database/repository',
@ -223,31 +247,18 @@ export default {
{
title: '@nocobase/resourcer',
type: 'subMenu',
children: [
'/api/resourcer/index',
'/api/resourcer/resource',
'/api/resourcer/action',
'/api/resourcer/middleware',
],
children: ['/api/resourcer', '/api/resourcer/resource', '/api/resourcer/action', '/api/resourcer/middleware'],
},
{
title: '@nocobase/acl',
type: 'subMenu',
children: [
'/api/acl/index',
'/api/acl/acl',
'/api/acl/acl-role',
'/api/acl/acl-resource',
'/api/acl/acl-available-action',
'/api/acl/acl-available-strategy',
'/api/acl/allow-manager',
],
children: ['/api/acl/acl', '/api/acl/acl-role', '/api/acl/acl-resource'],
},
{
title: '@nocobase/client',
type: 'subMenu',
children: [
// '/api/client/index',
// '/api/client',
'/api/client/application',
'/api/client/route-switch',
{
@ -255,7 +266,6 @@ export default {
'title.zh-CN': 'SchemaDesigner',
'title.TR-TR': 'Şema Tasarımcısı',
type: 'subMenu',
children: [
'/api/client/schema-designer/schema-component',
'/api/client/schema-designer/schema-initializer',
@ -266,7 +276,6 @@ export default {
title: 'Extensions',
'title.zh-CN': 'Extensions',
'title.TR-TR': 'Eklentiler',
type: 'subMenu',
children: [
// '/api/client/extensions/schema-component',
'/api/client/extensions/collection-manager',
@ -276,17 +285,24 @@ export default {
},
],
},
'/api/cli',
'/api/actions',
'/api/sdk',
{
title: '@nocobase/cli',
path: '/api/cli',
type: 'item',
},
{
title: '@nocobase/actions',
path: '/api/actions',
type: 'item',
},
{
title: '@nocobase/sdk',
path: '/api/sdk',
type: 'item',
},
],
};
export { nav, sidebar };

View File

@ -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',
},
{

1
docs/en-US/components.md Normal file
View File

@ -0,0 +1 @@
# Components

View File

@ -25,7 +25,6 @@ describe('route-switch', () => {
{
type: 'route',
path: '/',
exact: true,
component: 'Home',
},
]}

View File

@ -17,7 +17,6 @@ const routes: RouteRedirectProps[] = [
{
type: 'route',
path: '/',
exact: true,
component: 'Home',
},
{

View File

@ -1,3 +1,3 @@
# Overview
<code src="./demo1.tsx" />
<code src="./demo1.tsx"></code>

View File

@ -1 +1,3 @@
<Redirect to="/welcome/introduction" />
# Index
<Navigate replace to="/welcome/introduction"></Navigate>

View File

@ -5,7 +5,7 @@
- Submit pull request
- Sign the CLA
## Download
## Download
```bash
# Replace the following git address with your own repo
@ -45,4 +45,4 @@ The documentation is in the docs directory and follows Markdown syntax
## Others
For more CLI instructions, please [refer to the NocoBase CLI chapter](./development/nocobase-cli.md)
For more CLI instructions, please [refer to the NocoBase CLI chapter](https://docs-cn.nocobase.com/api/cli).

View File

@ -12,19 +12,32 @@ cd my-nocobase-app
git pull
```
## 3. Update dependencies
## 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. Update dependencies
```bash
yarn install
```
## 4. Execute the update command
## 5. Execute the update command
```bash
yarn nocobase upgrade
```
## 5. Start NocoBase
## 6. Start NocoBase
development environment
@ -41,4 +54,3 @@ yarn build
# Start
yarn start # Not supported on Windows platforms yet
```

View File

@ -2,6 +2,6 @@
NocoBase supports three types of installation, with slight differences in upgrades.
- [Upgrading for Docker compose](./upgrading/docker-compose.md)
- [Upgrading for create-nocobase-app](./upgrading/create-nocobase-app.md)
- [Upgrading for Git source code](./upgrading/git-clone.md)
- [Upgrading for Docker compose](./docker-compose.md)
- [Upgrading for create-nocobase-app](./create-nocobase-app.md)
- [Upgrading for Git source code](./git-clone.md)

View File

@ -0,0 +1,233 @@
# v0.10: Update instructions
## New features in the second quarter
- Association field component improvements, support for multiple component switching
- Select
- Record picker
- Sub-form/Sub-details
- Sub-table
- File manager
- Title(read only)
- Quick creation of relational data, supports two quick creation modes
- Add in drop-down menu to quickly create a new record based on the title field
- Add in pop-up window to configure complex add forms
- Duplicate action, supports two modes
- Direct duplicate
- Copy into the form and continue to fill in
- Form data templates
- Filter data scope support variables
- List block
- Grid card block
- Mobile client plugin
- User authentication plugin, support for different authentication methods
- Email/Password
- SMS
- OIDC
- SAML
- Workflow nodes
- Manual node upgrade, support for adding and editing from existing collections
- Loop node
- Aggregate node
- File manager
- Provide file collection template
- Provide file manager component
## Upgrading applications
### Upgrading for Docker compose
No change, upgrade reference [Upgrading for Docker compose](/welcome/getting-started/upgrading/docker-compose)
### Upgrading for Git source code
v0.10 has a major upgrade of dependencies, so to prevent errors when upgrading the source code, you need to delete the following directories before upgrading
```bash
### Remove .umi-related cache
yarn rimraf -rf "./**/{.umi,.umi-production}"
# Delete compiled files
yarn rimraf -rf "./packages/*/*/{lib,esm,es,dist,node_modules}"
# Remove dependencies
yarn rimraf -rf node_modules
```
See [Upgrading for Git source code](/welcome/getting-started/upgrading/git-clone) for more details
### Upgrading for create-nocobase-app
It is recommended that `yarn create` re-download the new version and modify the `.env` configuration, for more details refer to [major version upgrade guide](/welcome/getting-started/upgrading/create-nocobase-app#major-upgrade)
## Upcoming deprecated and potentially incompatible changes
### Sub-table field component
Not compatible with new version, block fields need to be removed and reassigned (UI reassignment only)
### Attachment upload api changes
In addition to the built-in attachments table, users can also custom file collection, the upload api for attachments has been changed from `/api/attachments:upload` to `/api/<file-collection>: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 `<Outlet />` instead of `props.children`.
```diff
import React from 'react';
+ import { Outlet } from 'react-router-dom';
export default function Layout(props) {
return (
<div>
- { props.children }
+ <Outlet />
</div>
);
}
```
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 (
<div>
- { React.cloneElement(props.children, { someProp: 'p1' }) }
+ <Outlet context={{ someProp: 'p1' }} />
</div>
);
}
```
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
`<Redirect>` is changed to `<Navigate replace />`.
```diff
- <Redirect to="about" />
+ <Navigate to="about" replace />
```
### 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<type>()` is changed to `useLocation`.
```diff
- const location= useLocation<type>();
+ 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)。

View File

@ -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 () => {
</RouteSwitchProvider>
);
};
```
```

1
docs/tr-TR/components.md Normal file
View File

@ -0,0 +1 @@
# Components

View File

@ -25,7 +25,6 @@ describe('route-switch', () => {
{
type: 'route',
path: '/',
exact: true,
component: 'Home',
},
]}

View File

@ -17,7 +17,6 @@ const routes: RouteRedirectProps[] = [
{
type: 'route',
path: '/',
exact: true,
component: 'Home',
},
{

View File

@ -1,3 +1,3 @@
# Overview
<code src="./demo1.tsx" />
<code src="./demo1.tsx"></code>

View File

@ -1 +1,3 @@
<Redirect to="/welcome/introduction" />
# Index
<Navigate replace to="/welcome/introduction"></Navigate>

View File

@ -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).

View File

@ -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)

View File

@ -0,0 +1,156 @@
# v0.10update 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 `<Outlet />` instead of `props.children`.
```diff
import React from 'react';
+ import { Outlet } from 'react-router-dom';
export default function Layout(props) {
return (
<div>
- { props.children }
+ <Outlet />
</div>
);
}
```
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 (
<div>
- { React.cloneElement(props.children, { someProp: 'p1' }) }
+ <Outlet context={{ someProp: 'p1' }} />
</div>
);
}
```
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
`<Redirect>` is changed to `<Navigate replace />`.
```diff
- <Redirect to="about" />
+ <Navigate to="about" replace />
```
### 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<type>()` is changed to `useLocation`.
```diff
- const location= useLocation<type>();
+ 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
- <code src="./demo.tsx" />
+ <code src="demo.tsx"></code>
```

View File

@ -1,4 +1,4 @@
# 内置常用资源操作
# @nocobase/actions
## 概览

View File

@ -1,4 +1,4 @@
# NocoBase CLI
# @nocobase/cli
NocoBase CLI 旨在帮助你开发、构建和部署 NocoBase 应用。

View File

@ -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 () => {
</RouteSwitchProvider>
);
};
```
```

1
docs/zh-CN/components.md Normal file
View File

@ -0,0 +1 @@
# Components

View File

@ -25,7 +25,6 @@ describe('route-switch', () => {
{
type: 'route',
path: '/',
exact: true,
component: 'Home',
},
]}
@ -38,4 +37,4 @@ describe('route-switch', () => {
expect(container).toMatchSnapshot();
});
});
```
```

View File

@ -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)
完整示例查看 [packages/samples/custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page)

View File

@ -1,3 +1,3 @@
# Overview
<code src="./demo1.tsx" />
<code src="./demo1.tsx"></code>

View File

@ -1 +1,3 @@
<Redirect to="/welcome/introduction" />
# Index
<Navigate replace to="/welcome/introduction"></Navigate>

View File

@ -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)。

View File

@ -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)
## 如何选择

View File

@ -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
开发环境

View File

@ -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)

View File

@ -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/<file-collection>: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 布局组件需要使用 `<Outlet />` 代替 `props.children`
```diff
import React from 'react';
+ import { Outlet } from 'react-router-dom';
export default function Layout(props) {
return (
<div>
- { props.children }
+ <Outlet />
</div>
);
}
```
使用了 `React.cloneElement` 方式渲染的路由组件改造,示例:
```diff
import React from 'react';
+ import { Outlet } from 'react-router-dom';
export default function RouteComponent(props) {
return (
<div>
- { React.cloneElement(props.children, { someProp: 'p1' }) }
+ <Outlet context={{ someProp: 'p1' }} />
</div>
);
}
```
组件改成从 `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
`<Redirect>` 转为 `<Navigate replace />`
```diff
- <Redirect to="about" />
+ <Navigate to="about" replace />
```
#### 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<type>()` 改为 `useLocation`
```diff
- const location= useLocation<type>();
+ 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)。

View File

@ -1,5 +1,5 @@
{
"version": "0.9.4-alpha.2",
"version": "0.10.0-alpha.1",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": [

View File

@ -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",

View File

@ -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' }],
});

View File

@ -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",

View File

@ -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 };
};
`,
});
});
};

View File

@ -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);

View File

@ -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;
}

View File

@ -0,0 +1,2 @@
var match = location.pathname.match(/^\/apps\/([^/]*)\//);
window.routerBase = match ? match[0] : "/";

View File

@ -1,49 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=0.1">
<title>Loading...</title>
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/favicon/site.webmanifest">
<style>
#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;
}
</style>
<script>
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);
</script>
</head>
<body>
<div id="root"></div>
<script>
var match = location.pathname.match(/^\/apps\/([^/]*)\//);
window.routerBase = match ? match[0] : "/";
</script>
</body>
</html>

View File

@ -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"
]
}

View File

@ -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",

View File

@ -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": {

View File

@ -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",

View File

@ -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"
}
}
}

View File

@ -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": {

View File

@ -1,5 +1,5 @@
{
"dependencies": {
"react": "^16.8.4"
"react": "^18.0.0"
}
}

View File

@ -1,5 +1,5 @@
{
"dependencies": {
"react": "16"
"react": "18.0.0"
}
}

View File

@ -1,5 +1,5 @@
{
"dependencies": {
"react": "16"
"react": "18.0.0"
}
}

View File

@ -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",

View File

@ -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",

View File

@ -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}']);
});
};

View File

@ -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,
},
});
}
});
};

View File

@ -0,0 +1 @@
@import 'antd/dist/antd.css';

View File

@ -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 <span><a href={en}>EN</a> | <a href={cn}></a></span>
}
export default LangSwitch;

View File

@ -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<IPreviewerProps> = ({ children, ...props }) => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let root: Root
if (ref.current) {
root = createRoot(ref.current)
root.render(<Suspense fallback={<div>loading...</div>}>{children}</Suspense>)
}
return () => {
if (root) {
root.unmount()
}
}
}, []);
return <DefaultPreviewer {...props}><div ref={ref} /></DefaultPreviewer>;
};
export default Previewer;

View File

@ -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',
}
]
})
});

View File

@ -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);
},
});

View File

@ -57,5 +57,5 @@ export default () => <h1>Hello NocoBase!</h1>;
也可以引用 demo 文件
```markdown
<code src="./demos/dmeo1.tsx"/>
<code src="./demos/dmeo1.tsx"></code>
```

View File

@ -1,5 +0,0 @@
---
sidemenu: false
---
<code src="./src/application/demos/demo2/index.tsx"/>

View File

@ -0,0 +1,5 @@
---
sidebar: false
---
<code src="../src/application/demos/demo2/index.tsx"></code>

View File

@ -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 = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
@ -42,14 +42,10 @@ const About = () => <h1>About</h1>;
const App = () => (
<Router initialEntries={['/']}>
<Link to={'/'}>Home</Link>, <Link to={'/about'}>About</Link>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
</Switch>
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
</Routes>
</Router>
);
@ -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);
相关例子如下:
<code src="./src/schema-component/demos/demo1.tsx" />
<code id='intro-demo1' src="../src/schema-component/demos/demo1.tsx"></code>
insertAdjacent 操作不仅可以用于新增节点,也可以用于现有节点的位置移动,如以下拖拽排序的例子:
@ -815,7 +809,7 @@ const { data, loading } = useRequest();
但是这样的方式不利于 Providers 的管理和扩展,为此提炼了 `compose()` 函数用于配置多个 providers如下
<code defaultShowCode="true" titile="compose" src="./src/application/demos/demo1/index.tsx"/>
<code id='intro-demo2' defaultShowCode="true" titile="compose" src="../src/application/demos/demo1/index.tsx"></code>
## Application

View File

@ -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"
}

View File

@ -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 <Spin />;
}
if (result.error) {
return <Redirect to={'/signin'} />;
return <Navigate replace to={'/signin'} />;
}
return <ACLContext.Provider value={result}>{props.children}</ACLContext.Provider>;
};

View File

@ -1,10 +1,7 @@
---
nav:
title: Client
path: /client
group:
title: Client
path: /client
order: 1
---
# ACL

View File

@ -1,12 +1,11 @@
---
nav:
path: /client
group:
path: /client
title: Client
order: 1
---
# AntdConfigProvider
为 antd 组件提供统一的全局化配置
<code src="./demos/demo1.tsx" />
<code src="./demos/demo1.tsx"></code>

View File

@ -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<APIClientProviderProps> = (props) => {

View File

@ -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 的请求数据
<code src="./demos/demo3.tsx" />
<code src="./demos/demo3.tsx"></code>
## APIClientProvider
@ -99,7 +98,7 @@ run({
例子如下:
<code src="./demos/demo2.tsx" />
<code src="./demos/demo2.tsx"></code>
或者是 NocoBase 的 resource & action 请求:
@ -120,7 +119,7 @@ run({
例子如下:
<code src="./demos/demo1.tsx" />
<code src="./demos/demo1.tsx"></code>
也可以是自定义的异步函数:

View File

@ -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<string, Plugin>;
scopes: Record<string, any>;
i18n: i18n;
apiClient: APIClient;
components: any;
pm: PluginManager;
constructor(protected _options: ApplicationOptions) {
this.providers = [];
this.plugins = new Map<string, Plugin>();
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<string, any>) {
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 () => <AppComponent app={this} />;
}
mount(selector: string) {
const container = typeof selector === 'string' ? document.querySelector(selector) : selector;
if (container) {
const App = this.getRootComponent();
const root = createRoot(container);
root.render(<App />);
}
}
}

View File

@ -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() {}
}

View File

@ -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<string, Plugin>;
protected pluginPrepares: Map<string, any>;
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();
}
}
}

View File

@ -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<string, any>;
constructor(protected options?: RouterOptions, protected context?: any) {
this.routes = new Map<string, any>();
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<any>(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);
}
}

View File

@ -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 (
<ApplicationContext.Provider value={app}>
<Internal />
</ApplicationContext.Provider>
);
};

View File

@ -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 <RouterProvider router={router} providers={providers} />;
});

View File

@ -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/<Navigate>)
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 <script> here
// containing the hydrated server-side staticContext (from StaticRouterProvider).
// useId relies on the component tree structure to generate deterministic id's
// so we need to ensure it remains the same on the client even though
// we don't need the <script> tag
return (
<>
<RouterContextCleaner>
<DataRouterContext.Provider value={dataRouterContext}>
<DataRouterStateContext.Provider value={state}>
<Router
basename={basename}
location={state.location}
navigationType={state.historyAction}
navigator={navigator}
>
<Providers>
{state.initialized ? <DataRoutes routes={router.routes} state={state} /> : fallbackElement}
</Providers>
</Router>
</DataRouterStateContext.Provider>
</DataRouterContext.Provider>
</RouterContextCleaner>
{null}
</>
);
}
export function DataRoutes({
routes,
state,
}: {
routes: DataRouteObject[];
state: RouterState;
}): React.ReactElement | null {
return useRoutesImpl(routes, undefined, state);
}
export const RouterContextCleaner = (props) => {
return (
<RouteContext.Provider
value={{
outlet: null,
matches: [],
isDataRoute: false,
}}
>
<LocationContext.Provider value={null as any}>{props.children}</LocationContext.Provider>
</RouteContext.Provider>
);
};

View File

@ -0,0 +1,13 @@
import React from 'react';
import { MainComponent } from './MainComponent';
export * from './AppComponent';
export * from './MainComponent';
export * from './RouterProvider';
export const defaultAppComponents = {
App: {
Main: MainComponent,
Spin: () => React.createElement('div', 'loading'),
},
};

View File

@ -0,0 +1,18 @@
import React from 'react';
const Blank = ({ children }) => children || null;
export const compose = (...components: any[]) => {
const Root = [...components, Blank].reduce((parent, child) => {
const [Parent, parentProps] = Array.isArray(parent) ? parent : [parent];
const [Child, childProps] = Array.isArray(child) ? child : [child];
return ({ children }) => (
<Parent {...parentProps}>
<Child {...childProps}>{children}</Child>
</Parent>
);
});
return (LastChild?: any) => (props?: any) => {
return <Root>{LastChild && <LastChild {...props} />}</Root>;
};
};

View File

@ -0,0 +1,4 @@
import { createContext } from 'react';
import { Application } from './Application';
export const ApplicationContext = createContext<Application>(null as any);

View File

@ -0,0 +1,90 @@
import React from 'react';
import { Link, Outlet, useLocation } from 'react-router-dom';
import { Application } from '../Application';
import { Plugin } from '../Plugin';
const Root = () => {
return (
<div>
<Link to={'/'}>Home</Link>
<Link to={'/hello'}>Hello</Link>
<Link to={'/team'}>Team</Link>
<Link to={'/about'}>About</Link>
<Outlet />
</div>
);
};
const Team = () => {
return (
<div>
Team <Link to={'/about'}>About</Link>
</div>
);
};
const About = () => {
return (
<div>
<Link to={'/team'}>Team</Link> About
</div>
);
};
class Test1Plugin extends Plugin {
async load() {
this.router.add('root.team', {
path: 'team',
component: 'Team',
});
}
}
class Test2Plugin extends Plugin {
async load() {
this.router.add('root.about', {
path: 'about',
component: 'About',
});
}
}
class NocobasePresetPlugin extends Plugin {
async afterAdd() {
await this.pm.add('test1');
await this.pm.add(Test2Plugin, { name: 'test2' });
}
async load() {
this.router.add('root', {
path: '/',
component: 'Root',
});
}
}
const app = new Application({
apiClient: {
baseURL: process.env.API_BASE_URL,
},
router: {
type: 'hash',
},
plugins: [[NocobasePresetPlugin, { name: 'nocobase' }]],
importPlugins: async (name) => {
return {
test1: Test1Plugin,
}[name];
},
components: { Root, Team, About },
});
app.use((props) => {
const location = useLocation();
if (location.pathname === '/hello') {
return <div>Hello</div>;
}
return props.children;
});
export default app.getRootComponent();

View File

@ -0,0 +1,3 @@
export * from './useApp';
export * from './useLoad';
export * from './useRouter';

View File

@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ApplicationContext } from '../context';
export const useApp = () => {
return useContext(ApplicationContext);
};

View File

@ -0,0 +1,16 @@
import { useEffect, useState } from 'react';
import { useApp } from './useApp';
export const useLoad = () => {
const app = useApp();
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
(async () => {
await app.load();
setLoading(false);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return loading;
};

View File

@ -0,0 +1,6 @@
import { useApp } from './useApp';
export const useRouter = () => {
const app = useApp();
return app.router;
};

View File

@ -0,0 +1,3 @@
# Application V2
<code src="./demos/demo1.tsx">Demo</code>

View File

@ -0,0 +1,3 @@
export * from './Application';
export * from './Plugin';
export * from './compose';

Some files were not shown because too many files have changed in this diff Show More