mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 03:56:16 +00:00
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:
parent
d8ad98e2f7
commit
d76e8fb87f
@ -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
1
.dumi/global.less
Normal file
@ -0,0 +1 @@
|
||||
@import 'antd/dist/antd.css';
|
11
.dumi/theme/slots/LangSwitch/index.tsx
Normal file
11
.dumi/theme/slots/LangSwitch/index.tsx
Normal 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;
|
23
.dumi/theme/slots/Previewer/index.tsx
Normal file
23
.dumi/theme/slots/Previewer/index.tsx
Normal 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
54
.dumirc.ts
Normal 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, // 报错
|
||||
});
|
@ -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
|
||||
**/.dumi/tmp
|
||||
**/.dumi/tmp-test
|
||||
**/.dumi/tmp-production
|
||||
|
3
.github/workflows/nocobase-test-backend.yml
vendored
3
.github/workflows/nocobase-test-backend.yml
vendored
@ -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
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -21,3 +21,7 @@ docker/**/storage
|
||||
cache/diskstore-*
|
||||
*.nbdump
|
||||
storage/duplicator/*
|
||||
**/.dumi/tmp
|
||||
**/.dumi/tmp-test
|
||||
**/.dumi/tmp-production
|
||||
packages/core/client/docs/contributing.md
|
||||
|
@ -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
130
.umirc.ts
@ -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;
|
||||
}, {}),
|
||||
});
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
NocoBase 正处在早期开发阶段,可能变动频繁,请谨慎用于生产环境。
|
||||
|
||||
NocoBase v0.10 已发布,详情查看 [v0.10 更新说明](http://docs-cn.nocobase.com/welcome/release/v10-changelog)。
|
||||
|
||||
## 我们在招聘
|
||||
|
||||
我们在寻找全职、远程的产品设计、开发、测试的新同事加入团队,如果你对 NocoBase 有强烈的兴趣,欢迎给我们发邮件:hello@nocobase.com
|
||||
|
@ -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 };
|
@ -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
1
docs/en-US/components.md
Normal file
@ -0,0 +1 @@
|
||||
# Components
|
@ -25,7 +25,6 @@ describe('route-switch', () => {
|
||||
{
|
||||
type: 'route',
|
||||
path: '/',
|
||||
exact: true,
|
||||
component: 'Home',
|
||||
},
|
||||
]}
|
||||
|
@ -17,7 +17,6 @@ const routes: RouteRedirectProps[] = [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/',
|
||||
exact: true,
|
||||
component: 'Home',
|
||||
},
|
||||
{
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Overview
|
||||
|
||||
<code src="./demo1.tsx" />
|
||||
<code src="./demo1.tsx"></code>
|
||||
|
@ -1 +1,3 @@
|
||||
<Redirect to="/welcome/introduction" />
|
||||
# Index
|
||||
|
||||
<Navigate replace to="/welcome/introduction"></Navigate>
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
@ -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)
|
||||
|
233
docs/en-US/welcome/release/v10-changelog.md
Normal file
233
docs/en-US/welcome/release/v10-changelog.md
Normal 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)。
|
@ -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/tr-TR/components.md
Normal file
1
docs/tr-TR/components.md
Normal file
@ -0,0 +1 @@
|
||||
# Components
|
@ -25,7 +25,6 @@ describe('route-switch', () => {
|
||||
{
|
||||
type: 'route',
|
||||
path: '/',
|
||||
exact: true,
|
||||
component: 'Home',
|
||||
},
|
||||
]}
|
||||
|
@ -17,7 +17,6 @@ const routes: RouteRedirectProps[] = [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/',
|
||||
exact: true,
|
||||
component: 'Home',
|
||||
},
|
||||
{
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Overview
|
||||
|
||||
<code src="./demo1.tsx" />
|
||||
<code src="./demo1.tsx"></code>
|
||||
|
@ -1 +1,3 @@
|
||||
<Redirect to="/welcome/introduction" />
|
||||
# Index
|
||||
|
||||
<Navigate replace to="/welcome/introduction"></Navigate>
|
||||
|
@ -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).
|
||||
|
@ -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)
|
||||
|
156
docs/tr-TR/welcome/release/v10-changelog.md
Normal file
156
docs/tr-TR/welcome/release/v10-changelog.md
Normal file
@ -0,0 +1,156 @@
|
||||
# v0.10:update instructions
|
||||
|
||||
v0.10 has made major upgrades to dependencies, including `react`, `react-dom`, `react-router`, `umi`, and `dumi`.
|
||||
|
||||
## 依赖升级
|
||||
|
||||
- Upgrade `react@^17`, `react-dom@^17` to `react@^18`, `react-dom@^18` version
|
||||
|
||||
```diff
|
||||
{
|
||||
"devDependencies": {
|
||||
+ "react": "^18",
|
||||
+ "react-dom": "^18",
|
||||
- "react": "^17",
|
||||
- "react-dom": "^17",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Upgrade `react-router@5` to `react-router@6`
|
||||
|
||||
```diff
|
||||
{
|
||||
"devDependencies": {
|
||||
+ "react-router-dom": "^6.11.2",
|
||||
- "react-router-dom": "^5",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `react-router` change description
|
||||
|
||||
because the upgrade of `react-router` requires some code changes.
|
||||
|
||||
### Layout Component
|
||||
|
||||
Layout component needs to use `<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>
|
||||
```
|
@ -1,4 +1,4 @@
|
||||
# 内置常用资源操作
|
||||
# @nocobase/actions
|
||||
|
||||
## 概览
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# NocoBase CLI
|
||||
# @nocobase/cli
|
||||
|
||||
NocoBase CLI 旨在帮助你开发、构建和部署 NocoBase 应用。
|
||||
|
||||
|
@ -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/zh-CN/components.md
Normal file
1
docs/zh-CN/components.md
Normal file
@ -0,0 +1 @@
|
||||
# Components
|
@ -25,7 +25,6 @@ describe('route-switch', () => {
|
||||
{
|
||||
type: 'route',
|
||||
path: '/',
|
||||
exact: true,
|
||||
component: 'Home',
|
||||
},
|
||||
]}
|
||||
|
@ -17,7 +17,6 @@ const routes: RouteRedirectProps[] = [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/',
|
||||
exact: true,
|
||||
component: 'Home',
|
||||
},
|
||||
{
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Overview
|
||||
|
||||
<code src="./demo1.tsx" />
|
||||
<code src="./demo1.tsx"></code>
|
||||
|
@ -1 +1,3 @@
|
||||
<Redirect to="/welcome/introduction" />
|
||||
# Index
|
||||
|
||||
<Navigate replace to="/welcome/introduction"></Navigate>
|
||||
|
@ -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)。
|
||||
|
@ -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)
|
||||
|
||||
## 如何选择
|
||||
|
||||
|
@ -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
|
||||
|
||||
开发环境
|
||||
|
||||
|
@ -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)
|
||||
|
232
docs/zh-CN/welcome/release/v10-changelog.md
Normal file
232
docs/zh-CN/welcome/release/v10-changelog.md
Normal 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)。
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.9.4-alpha.2",
|
||||
"version": "0.10.0-alpha.1",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"npmClientArgs": [
|
||||
|
12
package.json
12
package.json
@ -32,7 +32,11 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0"
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-router": "^6.11.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"config": {
|
||||
"ghooks": {
|
||||
@ -40,6 +44,10 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"@commitlint/cli": "^16.1.0",
|
||||
"@commitlint/config-conventional": "^16.0.0",
|
||||
"@commitlint/prompt-cli": "^16.1.0",
|
||||
@ -48,6 +56,8 @@
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"auto-changelog": "^2.4.0",
|
||||
"dumi": "^2.2.0",
|
||||
"dumi-theme-nocobase": "^0.2.11",
|
||||
"ghooks": "^2.0.4",
|
||||
"jsdom-worker": "^0.3.0",
|
||||
"prettier": "^2.2.1",
|
||||
|
@ -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' }],
|
||||
});
|
||||
|
@ -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",
|
||||
|
17
packages/app/client/plugin.ts
Normal file
17
packages/app/client/plugin.ts
Normal 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 };
|
||||
};
|
||||
`,
|
||||
});
|
||||
});
|
||||
};
|
1
packages/app/client/public/browser-checker.js
Normal file
1
packages/app/client/public/browser-checker.js
Normal 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);
|
29
packages/app/client/public/global.css
Normal file
29
packages/app/client/public/global.css
Normal 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;
|
||||
}
|
2
packages/app/client/public/set-router.js
Normal file
2
packages/app/client/public/set-router.js
Normal file
@ -0,0 +1,2 @@
|
||||
var match = location.pathname.match(/^\/apps\/([^/]*)\//);
|
||||
window.routerBase = match ? match[0] : "/";
|
@ -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>
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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": {
|
||||
|
@ -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",
|
||||
|
@ -1,15 +1,15 @@
|
||||
{
|
||||
"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",
|
||||
|
@ -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": {
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "^16.8.4"
|
||||
"react": "^18.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "16"
|
||||
"react": "18.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "16"
|
||||
"react": "18.0.0"
|
||||
}
|
||||
}
|
||||
|
2
packages/core/cache/package.json
vendored
2
packages/core/cache/package.json
vendored
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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}']);
|
||||
});
|
||||
};
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
1
packages/core/client/.dumi/global.less
Normal file
1
packages/core/client/.dumi/global.less
Normal file
@ -0,0 +1 @@
|
||||
@import 'antd/dist/antd.css';
|
@ -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;
|
@ -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;
|
56
packages/core/client/.dumirc.ts
Normal file
56
packages/core/client/.dumirc.ts
Normal 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',
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
@ -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);
|
||||
},
|
||||
});
|
@ -57,5 +57,5 @@ export default () => <h1>Hello NocoBase!</h1>;
|
||||
也可以引用 demo 文件
|
||||
|
||||
```markdown
|
||||
<code src="./demos/dmeo1.tsx"/>
|
||||
<code src="./demos/dmeo1.tsx"></code>
|
||||
```
|
||||
|
@ -1,5 +0,0 @@
|
||||
---
|
||||
sidemenu: false
|
||||
---
|
||||
|
||||
<code src="./src/application/demos/demo2/index.tsx"/>
|
5
packages/core/client/docs/develop.md
Normal file
5
packages/core/client/docs/develop.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
sidebar: false
|
||||
---
|
||||
|
||||
<code src="../src/application/demos/demo2/index.tsx"></code>
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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>;
|
||||
};
|
||||
|
@ -1,10 +1,7 @@
|
||||
---
|
||||
nav:
|
||||
title: Client
|
||||
path: /client
|
||||
group:
|
||||
title: Client
|
||||
path: /client
|
||||
order: 1
|
||||
---
|
||||
|
||||
# ACL
|
||||
|
@ -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>
|
||||
|
@ -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) => {
|
||||
|
@ -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>
|
||||
|
||||
也可以是自定义的异步函数:
|
||||
|
||||
|
91
packages/core/client/src/application-v2/Application.tsx
Normal file
91
packages/core/client/src/application-v2/Application.tsx
Normal 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 />);
|
||||
}
|
||||
}
|
||||
}
|
30
packages/core/client/src/application-v2/Plugin.ts
Normal file
30
packages/core/client/src/application-v2/Plugin.ts
Normal 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() {}
|
||||
}
|
83
packages/core/client/src/application-v2/PluginManager.ts
Normal file
83
packages/core/client/src/application-v2/PluginManager.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
67
packages/core/client/src/application-v2/Router.ts
Normal file
67
packages/core/client/src/application-v2/Router.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
@ -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} />;
|
||||
});
|
@ -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>
|
||||
);
|
||||
};
|
13
packages/core/client/src/application-v2/components/index.ts
Normal file
13
packages/core/client/src/application-v2/components/index.ts
Normal 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'),
|
||||
},
|
||||
};
|
18
packages/core/client/src/application-v2/compose.tsx
Normal file
18
packages/core/client/src/application-v2/compose.tsx
Normal 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>;
|
||||
};
|
||||
};
|
4
packages/core/client/src/application-v2/context.ts
Normal file
4
packages/core/client/src/application-v2/context.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { createContext } from 'react';
|
||||
import { Application } from './Application';
|
||||
|
||||
export const ApplicationContext = createContext<Application>(null as any);
|
90
packages/core/client/src/application-v2/demos/demo1.tsx
Normal file
90
packages/core/client/src/application-v2/demos/demo1.tsx
Normal 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();
|
3
packages/core/client/src/application-v2/hooks/index.ts
Normal file
3
packages/core/client/src/application-v2/hooks/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './useApp';
|
||||
export * from './useLoad';
|
||||
export * from './useRouter';
|
6
packages/core/client/src/application-v2/hooks/useApp.ts
Normal file
6
packages/core/client/src/application-v2/hooks/useApp.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
import { ApplicationContext } from '../context';
|
||||
|
||||
export const useApp = () => {
|
||||
return useContext(ApplicationContext);
|
||||
};
|
16
packages/core/client/src/application-v2/hooks/useLoad.ts
Normal file
16
packages/core/client/src/application-v2/hooks/useLoad.ts
Normal 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;
|
||||
};
|
@ -0,0 +1,6 @@
|
||||
import { useApp } from './useApp';
|
||||
|
||||
export const useRouter = () => {
|
||||
const app = useApp();
|
||||
return app.router;
|
||||
};
|
3
packages/core/client/src/application-v2/index.md
Normal file
3
packages/core/client/src/application-v2/index.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Application V2
|
||||
|
||||
<code src="./demos/demo1.tsx">Demo</code>
|
3
packages/core/client/src/application-v2/index.ts
Executable file
3
packages/core/client/src/application-v2/index.ts
Executable 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
Loading…
Reference in New Issue
Block a user