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/cli',
'core/create-nocobase-app', 'core/create-nocobase-app',
'core/devtools', 'core/devtools',
'core/dumi-theme-nocobase',
'app/client', '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/*/*/client.js
packages/*/*/server.js packages/*/*/server.js
packages/core/build packages/core/build
packages/core/dumi-theme-nocobase
packages/core/devtools 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 - name: Test with Sqlite
run: yarn nocobase install -f && yarn test run: yarn nocobase install -f && yarn test
env: env:
NODE_OPTIONS: "--max_old_space_size=4096"
DB_DIALECT: sqlite DB_DIALECT: sqlite
DB_STORAGE: /tmp/db.sqlite DB_STORAGE: /tmp/db.sqlite
DB_UNDERSCORED: ${{ matrix.underscored }} DB_UNDERSCORED: ${{ matrix.underscored }}
@ -81,6 +82,7 @@ jobs:
- name: Test with postgres - name: Test with postgres
run: yarn nocobase install -f && yarn test run: yarn nocobase install -f && yarn test
env: env:
NODE_OPTIONS: "--max_old_space_size=4096"
DB_DIALECT: postgres DB_DIALECT: postgres
DB_HOST: postgres DB_HOST: postgres
DB_PORT: 5432 DB_PORT: 5432
@ -118,6 +120,7 @@ jobs:
- name: Test with MySQL - name: Test with MySQL
run: yarn nocobase install -f && yarn test run: yarn nocobase install -f && yarn test
env: env:
NODE_OPTIONS: "--max_old_space_size=4096"
DB_DIALECT: mysql DB_DIALECT: mysql
DB_HOST: mysql DB_HOST: mysql
DB_PORT: 3306 DB_PORT: 3306

6
.gitignore vendored
View File

@ -20,4 +20,8 @@ dist/
docker/**/storage docker/**/storage
cache/diskstore-* cache/diskstore-*
*.nbdump *.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/**/esm/**
packages/**/node_modules/** packages/**/node_modules/**
packages/core/client/src/locale/* 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 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 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. 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) ![](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, 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 ## Ç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. 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 正处在早期开发阶段,可能变动频繁,请谨慎用于生产环境。
NocoBase v0.10 已发布,详情查看 [v0.10 更新说明](http://docs-cn.nocobase.com/welcome/release/v10-changelog)。
## 我们在招聘 ## 我们在招聘
我们在寻找全职、远程的产品设计、开发、测试的新同事加入团队,如果你对 NocoBase 有强烈的兴趣欢迎给我们发邮件hello@nocobase.com 我们在寻找全职、远程的产品设计、开发、测试的新同事加入团队,如果你对 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': [ '/welcome': [
{ {
title: 'Welcome', title: 'Welcome',
@ -6,7 +33,7 @@ export default {
'title.tr-TR': 'Hoşgeldiniz', 'title.tr-TR': 'Hoşgeldiniz',
type: 'group', type: 'group',
children: [ children: [
'/welcome/introduction/index', '/welcome/introduction',
'/welcome/introduction/features', '/welcome/introduction/features',
'/welcome/introduction/when', '/welcome/introduction/when',
// '/welcome/introduction/learning-guide', // '/welcome/introduction/learning-guide',
@ -22,9 +49,8 @@ export default {
title: 'Installation', title: 'Installation',
'title.zh-CN': '安装', 'title.zh-CN': '安装',
'title.TR-TR': 'Kurulum', 'title.TR-TR': 'Kurulum',
type: 'subMenu',
children: [ children: [
'/welcome/getting-started/installation/index', '/welcome/getting-started/installation',
'/welcome/getting-started/installation/docker-compose', '/welcome/getting-started/installation/docker-compose',
'/welcome/getting-started/installation/create-nocobase-app', '/welcome/getting-started/installation/create-nocobase-app',
'/welcome/getting-started/installation/git-clone', '/welcome/getting-started/installation/git-clone',
@ -34,9 +60,8 @@ export default {
title: 'Upgrading', title: 'Upgrading',
'title.zh-CN': '升级', 'title.zh-CN': '升级',
'title.TR-TR': 'Güncelleme', 'title.TR-TR': 'Güncelleme',
type: 'subMenu',
children: [ children: [
'/welcome/getting-started/upgrading/index', '/welcome/getting-started/upgrading',
'/welcome/getting-started/upgrading/docker-compose', '/welcome/getting-started/upgrading/docker-compose',
'/welcome/getting-started/upgrading/create-nocobase-app', '/welcome/getting-started/upgrading/create-nocobase-app',
'/welcome/getting-started/upgrading/git-clone', '/welcome/getting-started/upgrading/git-clone',
@ -58,7 +83,8 @@ export default {
link: 'https://github.com/nocobase/nocobase/blob/main/CHANGELOG.md', link: 'https://github.com/nocobase/nocobase/blob/main/CHANGELOG.md',
}, },
// '/welcome/release/index', // '/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', 'title.TR-TR': 'Başlarken',
type: 'group', type: 'group',
children: [ children: [
'/development/index', '/development',
'/development/your-fisrt-plugin', '/development/your-fisrt-plugin',
'/development/app-ds', '/development/app-ds',
'/development/plugin-ds', '/development/plugin-ds',
@ -131,14 +157,13 @@ export default {
'title.TR-TR': 'Sunucu', 'title.TR-TR': 'Sunucu',
type: 'group', type: 'group',
children: [ children: [
'/development/server/index', '/development/server',
{ {
title: 'Collections & Fields', title: 'Collections & Fields',
'title.zh-CN': '数据表和字段', 'title.zh-CN': '数据表和字段',
'title.TR-TR': 'Koleksiyonlar & Alanlar', 'title.TR-TR': 'Koleksiyonlar & Alanlar',
type: 'subMenu',
children: [ children: [
'/development/server/collections/index', '/development/server/collections',
'/development/server/collections/options', '/development/server/collections/options',
'/development/server/collections/configure', '/development/server/collections/configure',
'/development/server/collections/association-fields', '/development/server/collections/association-fields',
@ -162,14 +187,13 @@ export default {
'title.TR-TR': 'Ziyaretçi(Client)', 'title.TR-TR': 'Ziyaretçi(Client)',
type: 'group', type: 'group',
children: [ children: [
'/development/client/index', '/development/client',
{ {
title: 'UI 设计器', title: 'UI 设计器',
'title.zh-CN': 'UI 设计器', 'title.zh-CN': 'UI 设计器',
'title.TR-TR': 'Kullanıcı Arayüz Tasarımcısı', 'title.TR-TR': 'Kullanıcı Arayüz Tasarımcısı',
type: 'subMenu',
children: [ 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/what-is-ui-schema',
'/development/client/ui-schema-designer/extending-schema-components', '/development/client/ui-schema-designer/extending-schema-components',
// '/development/client/ui-schema-designer/insert-adjacent', // '/development/client/ui-schema-designer/insert-adjacent',
@ -189,12 +213,12 @@ export default {
}, },
], ],
'/api': [ '/api': [
'/api/index', '/api',
'/api/env', '/api/env',
{ {
title: 'HTTP API', title: 'HTTP API',
type: 'subMenu', type: 'subMenu',
children: ['/api/http/index', '/api/http/rest-api'], children: ['/api/http', '/api/http/rest-api'],
}, },
{ {
title: '@nocobase/server', title: '@nocobase/server',
@ -209,7 +233,7 @@ export default {
title: '@nocobase/database', title: '@nocobase/database',
type: 'subMenu', type: 'subMenu',
children: [ children: [
'/api/database/index', '/api/database',
'/api/database/collection', '/api/database/collection',
'/api/database/field', '/api/database/field',
'/api/database/repository', '/api/database/repository',
@ -223,31 +247,18 @@ export default {
{ {
title: '@nocobase/resourcer', title: '@nocobase/resourcer',
type: 'subMenu', type: 'subMenu',
children: [ children: ['/api/resourcer', '/api/resourcer/resource', '/api/resourcer/action', '/api/resourcer/middleware'],
'/api/resourcer/index',
'/api/resourcer/resource',
'/api/resourcer/action',
'/api/resourcer/middleware',
],
}, },
{ {
title: '@nocobase/acl', title: '@nocobase/acl',
type: 'subMenu', type: 'subMenu',
children: [ children: ['/api/acl/acl', '/api/acl/acl-role', '/api/acl/acl-resource'],
'/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',
],
}, },
{ {
title: '@nocobase/client', title: '@nocobase/client',
type: 'subMenu', type: 'subMenu',
children: [ children: [
// '/api/client/index', // '/api/client',
'/api/client/application', '/api/client/application',
'/api/client/route-switch', '/api/client/route-switch',
{ {
@ -255,7 +266,6 @@ export default {
'title.zh-CN': 'SchemaDesigner', 'title.zh-CN': 'SchemaDesigner',
'title.TR-TR': 'Şema Tasarımcısı', 'title.TR-TR': 'Şema Tasarımcısı',
type: 'subMenu',
children: [ children: [
'/api/client/schema-designer/schema-component', '/api/client/schema-designer/schema-component',
'/api/client/schema-designer/schema-initializer', '/api/client/schema-designer/schema-initializer',
@ -266,7 +276,6 @@ export default {
title: 'Extensions', title: 'Extensions',
'title.zh-CN': 'Extensions', 'title.zh-CN': 'Extensions',
'title.TR-TR': 'Eklentiler', 'title.TR-TR': 'Eklentiler',
type: 'subMenu',
children: [ children: [
// '/api/client/extensions/schema-component', // '/api/client/extensions/schema-component',
'/api/client/extensions/collection-manager', '/api/client/extensions/collection-manager',
@ -276,17 +285,24 @@ export default {
}, },
], ],
}, },
'/api/cli',
'/api/actions',
'/api/sdk',
{ {
title: '@nocobase/cli', title: '@nocobase/cli',
path: '/api/cli', path: '/api/cli',
type: 'item',
}, },
{ {
title: '@nocobase/actions', title: '@nocobase/actions',
path: '/api/actions', path: '/api/actions',
type: 'item',
}, },
{ {
title: '@nocobase/sdk', title: '@nocobase/sdk',
path: '/api/sdk', path: '/api/sdk',
type: 'item',
}, },
], ],
}; };
export { nav, sidebar };

View File

@ -23,8 +23,6 @@ interface RedirectProps {
type: 'redirect'; type: 'redirect';
to: any; to: any;
path?: string; path?: string;
exact?: boolean;
strict?: boolean;
push?: boolean; push?: boolean;
from?: string; from?: string;
[key: string]: any; [key: string]: any;
@ -33,8 +31,6 @@ interface RedirectProps {
interface RouteProps { interface RouteProps {
type: 'route'; type: 'route';
path?: string | string[]; path?: string | string[];
exact?: boolean;
strict?: boolean;
sensitive?: boolean; sensitive?: boolean;
component?: any; component?: any;
routes?: RouteProps[]; routes?: RouteProps[];
@ -56,7 +52,6 @@ const routes: RouteRedirectProps[] = [
{ {
type: 'route', type: 'route',
path: '/', path: '/',
exact: true,
component: 'Home', 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', type: 'route',
path: '/', path: '/',
exact: true,
component: 'Home', component: 'Home',
}, },
]} ]}

View File

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

View File

@ -1,3 +1,3 @@
# Overview # 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 - Submit pull request
- Sign the CLA - Sign the CLA
## Download ## Download
```bash ```bash
# Replace the following git address with your own repo # 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 ## 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 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 ```bash
yarn install yarn install
``` ```
## 4. Execute the update command ## 5. Execute the update command
```bash ```bash
yarn nocobase upgrade yarn nocobase upgrade
``` ```
## 5. Start NocoBase ## 6. Start NocoBase
development environment development environment
@ -41,4 +54,3 @@ yarn build
# Start # Start
yarn start # Not supported on Windows platforms yet 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. NocoBase supports three types of installation, with slight differences in upgrades.
- [Upgrading for Docker compose](./upgrading/docker-compose.md) - [Upgrading for Docker compose](./docker-compose.md)
- [Upgrading for create-nocobase-app](./upgrading/create-nocobase-app.md) - [Upgrading for create-nocobase-app](./create-nocobase-app.md)
- [Upgrading for Git source code](./upgrading/git-clone.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'; type: 'redirect';
to: any; to: any;
path?: string; path?: string;
exact?: boolean;
strict?: boolean;
push?: boolean; push?: boolean;
from?: string; from?: string;
[key: string]: any; [key: string]: any;
@ -33,8 +31,6 @@ interface RedirectProps {
interface RouteProps { interface RouteProps {
type: 'route'; type: 'route';
path?: string | string[]; path?: string | string[];
exact?: boolean;
strict?: boolean;
sensitive?: boolean; sensitive?: boolean;
component?: any; component?: any;
routes?: RouteProps[]; routes?: RouteProps[];
@ -56,7 +52,6 @@ const routes: RouteRedirectProps[] = [
{ {
type: 'route', type: 'route',
path: '/', path: '/',
exact: true,
component: 'Home', component: 'Home',
}, },
{ {
@ -76,4 +71,4 @@ export default () => {
</RouteSwitchProvider> </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', type: 'route',
path: '/', path: '/',
exact: true,
component: 'Home', component: 'Home',
}, },
]} ]}

View File

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

View File

@ -1,3 +1,3 @@
# Overview # 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 - Çekme isteği(pull request) gönderin
- CLA'yı imzalayın - CLA'yı imzalayın
## İndir ## İndir
```bash ```bash
# Aşağıdaki git adresini kendi deponuzla değiştirin # 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 ## 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. NocoBase, yükseltmelerde küçük farklılıklarla üç tür yüklemeyi destekler.
- [Docker için Güncelleme](./upgrading/docker-compose.md) - [Docker için Güncelleme](./docker-compose.md)
- [create-nocobase-app için Güncelleme](./upgrading/create-nocobase-app.md) - [create-nocobase-app için Güncelleme](./create-nocobase-app.md)
- [Git Kaynak koduyla Güncelleme](./upgrading/git-clone.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 应用。 NocoBase CLI 旨在帮助你开发、构建和部署 NocoBase 应用。

View File

@ -23,8 +23,6 @@ interface RedirectProps {
type: 'redirect'; type: 'redirect';
to: any; to: any;
path?: string; path?: string;
exact?: boolean;
strict?: boolean;
push?: boolean; push?: boolean;
from?: string; from?: string;
[key: string]: any; [key: string]: any;
@ -33,8 +31,6 @@ interface RedirectProps {
interface RouteProps { interface RouteProps {
type: 'route'; type: 'route';
path?: string | string[]; path?: string | string[];
exact?: boolean;
strict?: boolean;
sensitive?: boolean; sensitive?: boolean;
component?: any; component?: any;
routes?: RouteProps[]; routes?: RouteProps[];
@ -56,7 +52,6 @@ const routes: RouteRedirectProps[] = [
{ {
type: 'route', type: 'route',
path: '/', path: '/',
exact: true,
component: 'Home', component: 'Home',
}, },
{ {
@ -76,4 +71,4 @@ export default () => {
</RouteSwitchProvider> </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', type: 'route',
path: '/', path: '/',
exact: true,
component: 'Home', component: 'Home',
}, },
]} ]}
@ -38,4 +37,4 @@ describe('route-switch', () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
}); });
``` ```

View File

@ -17,7 +17,6 @@ const routes: RouteRedirectProps[] = [
{ {
type: 'route', type: 'route',
path: '/', path: '/',
exact: true,
component: 'Home', 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 # 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 支持三种安装方式: NocoBase 支持三种安装方式:
- [Docker 安装(推荐)](./installation/docker-compose.md) - [Docker 安装(推荐)](./docker-compose.md)
- [create-nocobase-app 安装](./installation/create-nocobase-app.md) - [create-nocobase-app 安装](./create-nocobase-app.md)
- [Git 源码安装](./installation/git-clone.md) - [Git 源码安装](./git-clone.md)
## 如何选择 ## 如何选择

View File

@ -12,19 +12,32 @@ cd my-nocobase-app
git pull 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 yarn install
``` ```
## 4. 执行更新命令 ## 5. 执行更新命令
```bash ```bash
yarn nocobase upgrade yarn nocobase upgrade
``` ```
## 5. 启动 NocoBase ## 6. 启动 NocoBase
开发环境 开发环境

View File

@ -2,6 +2,6 @@
NocoBase 支持三种安装方式,升级时略有不同。 NocoBase 支持三种安装方式,升级时略有不同。
- [Docker 安装的升级](./upgrading/docker-compose.md) - [Docker 安装的升级](./docker-compose.md)
- [create-nocobase-app 安装的升级](./upgrading/create-nocobase-app.md) - [create-nocobase-app 安装的升级](./create-nocobase-app.md)
- [Git 源码安装的升级](./upgrading/git-clone.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", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"npmClientArgs": [ "npmClientArgs": [

View File

@ -32,7 +32,11 @@
}, },
"resolutions": { "resolutions": {
"@types/react": "^17.0.0", "@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": { "config": {
"ghooks": { "ghooks": {
@ -40,6 +44,10 @@
} }
}, },
"devDependencies": { "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/cli": "^16.1.0",
"@commitlint/config-conventional": "^16.0.0", "@commitlint/config-conventional": "^16.0.0",
"@commitlint/prompt-cli": "^16.1.0", "@commitlint/prompt-cli": "^16.1.0",
@ -48,6 +56,8 @@
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",
"@vitejs/plugin-react": "^4.0.0", "@vitejs/plugin-react": "^4.0.0",
"auto-changelog": "^2.4.0", "auto-changelog": "^2.4.0",
"dumi": "^2.2.0",
"dumi-theme-nocobase": "^0.2.11",
"ghooks": "^2.0.4", "ghooks": "^2.0.4",
"jsdom-worker": "^0.3.0", "jsdom-worker": "^0.3.0",
"prettier": "^2.2.1", "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'; import { defineConfig } from 'umi';
const umiConfig = getUmiConfig(); const umiConfig = getUmiConfig();
process.env.MFSU_AD = 'none'; process.env.MFSU_AD = 'none';
process.env.DID_YOU_KNOW = 'none';
export default defineConfig({ 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, hash: true,
alias: {
...umiConfig.alias,
},
define: { define: {
...umiConfig.define, ...umiConfig.define,
}, },
dynamicImportSyntax: {},
// only proxy when using `umi dev`
// if the assets are built, will not proxy
proxy: { proxy: {
...umiConfig.proxy, ...umiConfig.proxy,
}, },
nodeModulesTransform: { fastRefresh: false, // 热更新会导致 Context 丢失,不开启
type: 'none', mfsu: false,
}, esbuildMinifyIIFE: true,
routes: [{ path: '/', exact: false, component: '@/pages/index' }], // srcTranspiler: 'esbuild', // 不行,各种报错
// fastRefresh: {}, // mfsu: {
chainWebpack(memo) { // esbuild: true // 不行,各种报错
resolveNocobasePackagesAlias(memo); // },
// 浏览器兼容性,兼容到 2018 年的浏览器
// 在引入 mermaid 之后,运行 yarn dev 的时候会报错,添加下面的代码可以解决。 targets: {
memo.module chrome: 69,
.rule('js-in-node_modules') edge: 79,
.test(/(react-error-boundary.*\.js|htmlparser2|(.*mermaid.*\.js$))/) safari: 12,
.include.clear();
return memo;
}, },
routes: [{ path: '/*', component: 'index' }],
}); });

View File

@ -1,9 +1,9 @@
{ {
"name": "@nocobase/app-client", "name": "@nocobase/app-client",
"version": "0.9.4-alpha.2", "version": "0.10.0-alpha.1",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"devDependencies": { "devDependencies": {
"@nocobase/client": "0.9.4-alpha.2" "@nocobase/client": "0.10.0-alpha.1"
}, },
"repository": { "repository": {
"type": "git", "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 "allowSyntheticDefaultImports": true
}, },
"include": ["mock/**/*", "src/**/*", "config/**/*", ".umirc.ts", "typings.d.ts"], "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", "name": "@nocobase/app-server",
"version": "0.9.4-alpha.2", "version": "0.10.0-alpha.1",
"description": "", "description": "",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/preset-nocobase": "0.9.4-alpha.2" "@nocobase/preset-nocobase": "0.10.0-alpha.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,13 +1,13 @@
{ {
"name": "@nocobase/acl", "name": "@nocobase/acl",
"version": "0.9.4-alpha.2", "version": "0.10.0-alpha.1",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/resourcer": "0.9.4-alpha.2", "@nocobase/resourcer": "0.10.0-alpha.1",
"@nocobase/utils": "0.9.4-alpha.2", "@nocobase/utils": "0.10.0-alpha.1",
"minimatch": "^5.1.1" "minimatch": "^5.1.1"
}, },
"repository": { "repository": {

View File

@ -1,14 +1,14 @@
{ {
"name": "@nocobase/actions", "name": "@nocobase/actions",
"version": "0.9.4-alpha.2", "version": "0.10.0-alpha.1",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/cache": "0.9.4-alpha.2", "@nocobase/cache": "0.10.0-alpha.1",
"@nocobase/database": "0.9.4-alpha.2", "@nocobase/database": "0.10.0-alpha.1",
"@nocobase/resourcer": "0.9.4-alpha.2" "@nocobase/resourcer": "0.10.0-alpha.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,19 +1,19 @@
{ {
"name": "@nocobase/auth", "name": "@nocobase/auth",
"version": "0.9.4-alpha.2", "version": "0.10.0-alpha.1",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/database": "0.9.4-alpha.2", "@nocobase/actions": "0.10.0-alpha.1",
"@nocobase/resourcer": "0.9.4-alpha.2", "@nocobase/database": "0.10.0-alpha.1",
"@nocobase/utils": "0.9.4-alpha.2", "@nocobase/resourcer": "0.10.0-alpha.1",
"@nocobase/actions": "0.9.4-alpha.2" "@nocobase/utils": "0.10.0-alpha.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/nocobase/nocobase.git", "url": "git+https://github.com/nocobase/nocobase.git",
"directory": "packages/auth" "directory": "packages/auth"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/build", "name": "@nocobase/build",
"version": "0.9.4-alpha.2", "version": "0.10.0-alpha.1",
"description": "Library build tool based on rollup.", "description": "Library build tool based on rollup.",
"main": "lib/index.js", "main": "lib/index.js",
"bin": { "bin": {

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/cache", "name": "@nocobase/cache",
"version": "0.9.4-alpha.2", "version": "0.10.0-alpha.1",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/cli", "name": "@nocobase/cli",
"version": "0.9.4-alpha.2", "version": "0.10.0-alpha.1",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./src/index.js", "main": "./src/index.js",
@ -8,16 +8,19 @@
"nocobase": "./bin/index.js" "nocobase": "./bin/index.js"
}, },
"dependencies": { "dependencies": {
"@types/fs-extra": "^11.0.1",
"chalk": "^4.1.1", "chalk": "^4.1.1",
"commander": "^9.2.0", "commander": "^9.2.0",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"execa": "^5.1.1", "execa": "^5.1.1",
"fast-glob": "^3.2.12",
"fs-extra": "^11.1.1",
"pm2": "^5.2.0", "pm2": "^5.2.0",
"portfinder": "^1.0.28", "portfinder": "^1.0.28",
"serve": "^13.0.2" "serve": "^13.0.2"
}, },
"devDependencies": { "devDependencies": {
"@nocobase/devtools": "0.9.4-alpha.2" "@nocobase/devtools": "0.10.0-alpha.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -14,6 +14,7 @@ module.exports = (cli) => {
if (!isDev()) { if (!isDev()) {
return; 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 { Command } = require('commander');
const { resolve, isAbsolute } = require('path'); const { resolve, isAbsolute } = require('path');
const { run, isDev } = require('../util'); 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) => { module.exports = (cli) => {
cli cli
.command('doc') .command('doc')
.argument('[dev|build|serve]')
.argument('[packages]')
.option('--lang [lang]') .option('--lang [lang]')
.allowUnknownOption() .allowUnknownOption()
.action((options) => { .action((command, pkg, options) => {
if (!isDev()) { if (!isDev()) {
return; 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'; process.env.DOC_LANG = options.lang || 'en-US';
const docThemePath = process.env.DOC_THEME_PATH;
if (!docThemePath) { if (command === 'serve') {
process.env.DUMI_THEME = resolve(process.cwd(), 'packages/core/dumi-theme-nocobase/src'); // 如果是多语言,则需要进入内部,如果不是多语言,则直接进入 docs/dist
let dir = resolve(cwd, 'docs/dist', process.env.DOC_LANG);
if (!existsSync(dir)) {
dir = resolve(cwd, 'docs/dist');
}
run('serve', [dir]);
} else { } else {
process.env.DUMI_THEME = isAbsolute(docThemePath) ? docThemePath : resolve(process.cwd(), docThemePath); run('dumi', [command], {
} env: {
// TODO(bug): space replace = not work APP_ROOT: process.env.APP_ROOT,
const index = process.argv.indexOf(`--lang=${options.lang}`); DOC_LANG: process.env.DOC_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]);
} }
}); });
}; };

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 文件 也可以引用 demo 文件
```markdown ```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 * title: Router
*/ */
import React from 'react'; 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 Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>; const About = () => <h1>About</h1>;
@ -42,14 +42,10 @@ const About = () => <h1>About</h1>;
const App = () => ( const App = () => (
<Router initialEntries={['/']}> <Router initialEntries={['/']}>
<Link to={'/'}>Home</Link>, <Link to={'/about'}>About</Link> <Link to={'/'}>Home</Link>, <Link to={'/about'}>About</Link>
<Switch> <Routes>
<Route exact path="/"> <Route path="/" element={<Home />}></Route>
<Home /> <Route path="/about" element={<About />}></Route>
</Route> </Routes>
<Route path="/about">
<About />
</Route>
</Switch>
</Router> </Router>
); );
@ -74,7 +70,6 @@ const routes: RouteRedirectProps[] = [
{ {
type: 'route', type: 'route',
path: '/', path: '/',
exact: true,
component: 'Home', component: 'Home',
}, },
{ {
@ -515,7 +510,6 @@ const routes: RouteRedirectProps[] = [
{ {
type: 'route', type: 'route',
path: '/', path: '/',
exact: true,
component: 'RouteSchemaComponent', component: 'RouteSchemaComponent',
schema: { schema: {
name: 'home', 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 操作不仅可以用于新增节点,也可以用于现有节点的位置移动,如以下拖拽排序的例子: insertAdjacent 操作不仅可以用于新增节点,也可以用于现有节点的位置移动,如以下拖拽排序的例子:
@ -815,7 +809,7 @@ const { data, loading } = useRequest();
但是这样的方式不利于 Providers 的管理和扩展,为此提炼了 `compose()` 函数用于配置多个 providers如下 但是这样的方式不利于 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 ## Application

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/client", "name": "@nocobase/client",
"version": "0.9.4-alpha.2", "version": "0.10.0-alpha.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "lib", "main": "lib",
"module": "es/index.js", "module": "es/index.js",
@ -13,9 +13,9 @@
"@formily/antd": "2.2.24", "@formily/antd": "2.2.24",
"@formily/core": "2.2.24", "@formily/core": "2.2.24",
"@formily/react": "2.2.24", "@formily/react": "2.2.24",
"@nocobase/evaluators": "0.9.4-alpha.2", "@nocobase/evaluators": "0.10.0-alpha.1",
"@nocobase/sdk": "0.9.4-alpha.2", "@nocobase/sdk": "0.10.0-alpha.1",
"@nocobase/utils": "0.9.4-alpha.2", "@nocobase/utils": "0.10.0-alpha.1",
"ahooks": "^3.7.2", "ahooks": "^3.7.2",
"antd": "^4.24.8", "antd": "^4.24.8",
"axios": "^0.26.1", "axios": "^0.26.1",
@ -40,24 +40,24 @@
"react-image-lightbox": "^5.1.4", "react-image-lightbox": "^5.1.4",
"react-js-cron": "^3.1.0", "react-js-cron": "^3.1.0",
"react-quill": "^1.3.5", "react-quill": "^1.3.5",
"react-router-dom": "^5.2.0", "react-router-dom": "^6.11.2",
"react-to-print": "^2.14.7", "react-to-print": "^2.14.7",
"sanitize-html": "2.10.0", "sanitize-html": "2.10.0",
"solarlunar-es": "^1.0.9", "solarlunar-es": "^1.0.9",
"use-deep-compare-effect": "^1.8.1" "use-deep-compare-effect": "^1.8.1"
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": ">=16.8.0 || >=17.0.0", "react": ">=18.0.0",
"@types/react-dom": ">=16.8.0 || >=17.0.0", "react-dom": ">=18.0.0",
"react": ">=16.8.0 || >=17.0.0", "react-is": ">=18.0.0"
"react-dom": ">=16.8.0",
"react-is": ">=16.8.0 || >=17.0.0"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/react": "^12.1.2", "@testing-library/react": "^12.1.2",
"@types/markdown-it": "12.2.3", "@types/markdown-it": "12.2.3",
"@types/markdown-it-highlightjs": "3.3.1", "@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" "gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644"
} }

View File

@ -2,7 +2,7 @@ import { Field } from '@formily/core';
import { Schema, useField, useFieldSchema } from '@formily/react'; import { Schema, useField, useFieldSchema } from '@formily/react';
import { Spin } from 'antd'; import { Spin } from 'antd';
import React, { createContext, useContext, useEffect } from 'react'; 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 { useAPIClient, useRequest } from '../api-client';
import { useBlockRequestContext } from '../block-provider/BlockProvider'; import { useBlockRequestContext } from '../block-provider/BlockProvider';
import { useCollection } from '../collection-manager'; import { useCollection } from '../collection-manager';
@ -52,7 +52,7 @@ export const ACLRolesCheckProvider = (props) => {
return <Spin />; return <Spin />;
} }
if (result.error) { if (result.error) {
return <Redirect to={'/signin'} />; return <Navigate replace to={'/signin'} />;
} }
return <ACLContext.Provider value={result}>{props.children}</ACLContext.Provider>; return <ACLContext.Provider value={result}>{props.children}</ACLContext.Provider>;
}; };

View File

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

View File

@ -1,12 +1,11 @@
--- ---
nav:
path: /client
group: group:
path: /client title: Client
order: 1
--- ---
# AntdConfigProvider # AntdConfigProvider
为 antd 组件提供统一的全局化配置 为 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 { APIClient } from './APIClient';
import { APIClientContext } from './context'; import { APIClientContext } from './context';
export interface APIClientProviderProps { export interface APIClientProviderProps {
apiClient: APIClient; apiClient: APIClient;
children?: ReactNode;
} }
export const APIClientProvider: React.FC<APIClientProviderProps> = (props) => { export const APIClientProvider: React.FC<APIClientProviderProps> = (props) => {

View File

@ -1,8 +1,7 @@
--- ---
nav:
path: /client
group: group:
path: /client title: Client
order: 1
--- ---
# APIClient # APIClient
@ -55,7 +54,7 @@ const { data, loading, run } = apiClient.service('uid');
`api.service(uid)` 的例子ComponentB 里刷新 ComponentA 的请求数据 `api.service(uid)` 的例子ComponentB 里刷新 ComponentA 的请求数据
<code src="./demos/demo3.tsx" /> <code src="./demos/demo3.tsx"></code>
## APIClientProvider ## APIClientProvider
@ -99,7 +98,7 @@ run({
例子如下: 例子如下:
<code src="./demos/demo2.tsx" /> <code src="./demos/demo2.tsx"></code>
或者是 NocoBase 的 resource & action 请求: 或者是 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