Feat/collection templates (#1124)

* feat: add collection templates

* feat:  collection templates

* feat:  collection templates

* feat:  collection templates

* feat:  collection templates

* feat: collection templates

* feat: calendar collection template

* feat: no id does not support sorting

* feat: collection template edit

* feat: collectiom templates code improve

* feat: collection template supports configurable field interface

* feat: collection template supports configurable field interface

* feat: establish relation field when there is no id

* feat: collection templates

* feat: collection templates locale

* feat: calendar collection

* feat: calendar collection default fields

* feat: code optimization

* feat: code optimization

* feat: code optimization

* feat: collectin template fix

* feat: add sample-custom-collection-template

* feat: code optimization

* feat: code optimization

* feat: code optimization

* feat: code optimization

* feat: import code

* feat: collection template local

* feat: code opimization

* feat: code opimization

* feat: code opimization

* feat: code opimization

* feat: code opimization

* feat: collection template local

* feat: collection template local

* fix(collection-manager): exclude reverse parameters when overriding

* feat: update docs

* feat: disabled

* feat: improve code

* feat: update submodule

* feat: submodules: true

* fix: token

* fix: with ssh-key

* fix: update dockerfile

* fix: ci

* fix: ci

* fix: ci

* fix: ci

* fix: --no-verify-access

* fix: ci

* fix: ci

* fix: ci

* fix: missing env

* fix: env

Co-authored-by: chenos <chenlinxh@gmail.com>
Co-authored-by: chareice <chareice@live.com>
This commit is contained in:
katherinehhh 2022-11-29 11:31:30 +08:00 committed by GitHub
parent f7f58bfe99
commit 4085ed0db7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1149 additions and 663 deletions

View File

@ -24,6 +24,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }}
submodules: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
@ -51,7 +54,7 @@ jobs:
uses: docker/build-push-action@v3
with:
context: .
file: Dockerfile
file: Dockerfile.acr
build-args: |
VERDACCIO_URL=http://localhost:4873/
COMMIT_HASH=${GITHUB_SHA}

64
Dockerfile.acr Normal file
View File

@ -0,0 +1,64 @@
FROM node:16 as builder
ARG VERDACCIO_URL=http://host.docker.internal:10104/
ARG COMIT_HASH
RUN apt-get update && apt-get install -y jq
WORKDIR /tmp
COPY . /tmp
RUN npx npm-cli-adduser --username test --password test -e test@nocobase.com -r $VERDACCIO_URL
RUN cd /tmp && \
NEWVERSION="$(cat lerna.json | jq '.version' | tr -d '"').$(date +%s)" \
&& tmp=$(mktemp) \
&& jq ".version = \"${NEWVERSION}\"" lerna.json > "$tmp" && mv "$tmp" lerna.json
RUN yarn install && yarn nocobase install && yarn pm add graph-collection-manager && yarn build
RUN git checkout -b release \
&& yarn version:alpha -y
RUN cd packages/pro-plugins && git checkout -b release \
&& git config user.email "test@mail.com" \
&& git config user.name "test" && git add . \
&& git commit -m "chore(versions): test publish packages xxx"
RUN git config user.email "test@mail.com" \
&& git config user.name "test" && git add . \
&& git commit -m "chore(versions): test publish packages xxx" \
&& git submodule update --init --recursive -- packages/pro-plugins
RUN yarn release:force --no-verify-access --no-git-reset --registry $VERDACCIO_URL
RUN yarn config set registry $VERDACCIO_URL
WORKDIR /app
RUN cd /app \
&& yarn config set network-timeout 600000 -g \
&& yarn create nocobase-app my-nocobase-app -a \
-e APP_ENV=production \
-e PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase/preset-,@nocobase/plugin-pro- \
-e PRESET_NOCOBASE_PLUGINS=error-handler,collection-manager,ui-schema-storage,ui-routes-storage,file-manager,system-settings,verification,users,acl,china-region,workflow,client,export,import,audit-logs,graph-collection-manager \
&& cd /app/my-nocobase-app \
&& yarn install --production \
&& yarn add @nocobase/plugin-pro-graph-collection-manager -W
RUN cd /app \
&& rm -rf my-nocobase-app/packages/app/client/src/.umi \
&& rm -rf nocobase.tar.gz \
&& find ./my-nocobase-app/node_modules/china-division/dist -name '*.csv' -delete \
&& find ./my-nocobase-app/node_modules/china-division/dist -name '*.sqlite' -delete \
&& tar -zcf ./nocobase.tar.gz -C /app/my-nocobase-app .
FROM node:16-stretch-slim
RUN apt-get update && apt-get install -y nginx
RUN rm -rf /etc/nginx/sites-enabled/default
COPY ./docker/nocobase/nocobase.conf /etc/nginx/sites-enabled/nocobase.conf
COPY --from=builder /app/nocobase.tar.gz /app/nocobase.tar.gz
WORKDIR /app/nocobase
RUN mkdir -p /app/nocobase/storage/uploads/ && echo "$COMIT_HASH" >> /app/nocobase/storage/uploads/COMIT_HASH
COPY ./docker/nocobase/docker-entrypoint.sh /app/
CMD ["/app/docker-entrypoint.sh"]

View File

@ -1,47 +0,0 @@
FROM node:16 as builder
ARG VERDACCIO_URL=https://pkgs.nocobase.com/
RUN apt-get update && apt-get install -y jq
WORKDIR /tmp
COPY . /tmp
RUN npx npm-cli-adduser --username test --password test -e test@nocobase.com -r $VERDACCIO_URL
RUN cd /tmp && \
NEWVERSION="$(cat lerna.json | jq '.version' | tr -d '"').$(date +%s)" \
&& tmp=$(mktemp) \
&& jq ".version = \"${NEWVERSION}\"" lerna.json > "$tmp" && mv "$tmp" lerna.json
RUN yarn install && yarn build
RUN git checkout -b release \
&& yarn version:alpha -y \
&& git config user.email "test@mail.com" \
&& git config user.name "test" && git add . \
&& git commit -m "chore(versions): test publish packages xxx" \
&& yarn release:force --registry $VERDACCIO_URL
RUN yarn config set registry $VERDACCIO_URL
WORKDIR /app
RUN cd /app \
&& yarn create nocobase-app my-nocobase-app -a -e APP_ENV=production \
&& cd /app/my-nocobase-app \
&& yarn install --production
RUN cd /app \
&& rm -rf my-nocobase-app/packages/app/client/src/.umi \
&& rm -rf nocobase.tar.gz \
&& find ./my-nocobase-app/node_modules/china-division/dist -name '*.csv' -delete \
&& find ./my-nocobase-app/node_modules/china-division/dist -name '*.sqlite' -delete \
&& tar -zcf ./nocobase.tar.gz -C /app/my-nocobase-app .
FROM node:16-stretch-slim
RUN apt-get update && apt-get install -y nginx
RUN rm -rf /etc/nginx/sites-enabled/default
COPY ./docker/nocobase/nocobase.conf /etc/nginx/sites-enabled/nocobase.conf
COPY --from=builder /app/nocobase.tar.gz /app/nocobase.tar.gz
WORKDIR /app/nocobase
COPY ./docker/nocobase/docker-entrypoint.sh /app/
CMD ["/app/docker-entrypoint.sh"]

View File

@ -0,0 +1,79 @@
# v0.8.1: Collection 模板
<img src="./v08-1-collection-templates/v08-1-collection-templates.jpg">
## 为什么需要 Collection 模板?
待补充
## 配置参数说明
```ts
interface ICollectionTemplate {
name: string;
title?: string;
/** 排序 */
order?: number;
/** 默认配置 */
default?: CollectionOptions;
/** UI 可配置的 CollectionOptions 参数(添加或编辑的 Collection 表单的字段) */
configurableProperties?: Record<string, ISchema>;
/** 当前模板可用的字段类型 */
availableFieldInterfaces?: AvailableFieldInterfacesInclude | AvailableFieldInterfacesExclude;
}
interface AvailableFieldInterfacesInclude {
include?: any[];
}
interface AvailableFieldInterfacesExclude {
exclude?: any[];
}
interface CollectionOptions {
/**
* 自动生成 id
* @default true
* */
autoGenId?: boolean;
/** 创建人 */
createdBy?: boolean;
/** 最后更新人 */
updatedBy?: boolean;
/** 创建日期 */
createdAt?: boolean;
/** 更新日期 */
updatedAt?: boolean;
/** 可排序 */
sortable?: boolean;
/* 树结构 */
tree?: string;
/* 日志 */
logging?: boolean;
/** 继承 */
inherits: string | string[];
/* 字段列表 */
fields?: FieldOptions[];
}
```
## 示例
创建时都不需要 autoGenId并且只提供 title 和 name 配置项
```ts
import { collectionConfigurableProperties } from '@nocobase/client';
{
default: {
autoGenId: false,
fields: [],
},
configurableProperties: {
...collectionConfigurableProperties('name', 'title'),
},
}
```
完整插件示例参考:[samples/custom-collection-template](https://github.com/nocobase/nocobase/tree/feat/collection-templates/packages/samples/custom-collection-template)

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@ -1,20 +1,24 @@
import { Spin } from 'antd';
import React, { useContext, useState } from 'react';
import { keyBy } from 'lodash';
import { useAPIClient, useRequest } from '../api-client';
import { CollectionManagerSchemaComponentProvider } from './CollectionManagerSchemaComponentProvider';
import { CollectionManagerContext } from './context';
import * as defaultInterfaces from './interfaces';
import { CollectionManagerOptions } from './types';
import { templateOptions } from '../collection-manager/Configuration/templates';
export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (props) => {
const { service, interfaces, collections = [], refreshCM } = props;
const { service, interfaces, collections = [], refreshCM, templates } = props;
const defaultTemplates = keyBy(templateOptions(), (item) => item.name);
const ctx = useContext(CollectionManagerContext);
return (
<CollectionManagerContext.Provider
value={{
...ctx,
service,
interfaces: { ...defaultInterfaces, ...interfaces, ...ctx.interfaces },
interfaces: { ...defaultInterfaces, ...interfaces },
templates: { ...defaultTemplates, ...templates },
collections,
refreshCM,
}}

View File

@ -17,6 +17,10 @@ import {
OverridingCollectionField,
ViewCollectionField,
ViewFieldAction,
AddCollection,
AddCollectionAction,
EditCollection,
EditCollectionAction,
} from './Configuration';
const schema: ISchema = {
@ -53,6 +57,10 @@ export const CollectionManagerPane = () => {
ConfigurationTable,
AddFieldAction,
AddCollectionField,
AddCollection,
AddCollectionAction,
EditCollection,
EditCollectionAction,
EditFieldAction,
EditCollectionField,
OverridingCollectionField,
@ -99,6 +107,8 @@ export const CollectionManagerShortcut2 = () => {
EditFieldAction,
OverridingFieldAction,
ViewFieldAction,
AddCollectionAction,
EditCollectionAction,
}}
/>
</ActionContext.Provider>

View File

@ -0,0 +1,282 @@
import { DownOutlined, PlusOutlined } from '@ant-design/icons';
import { ArrayTable } from '@formily/antd';
import { ISchema, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import { Button, Dropdown, Menu } from 'antd';
import { cloneDeep } from 'lodash';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useRequest } from '../../api-client';
import { RecordProvider, useRecord } from '../../record-provider';
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
import { useCancelAction } from '../action-hooks';
import { useCollectionManager } from '../hooks';
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
import * as components from './components';
import { templateOptions } from './templates';
const getSchema = (schema, record: any, compile): ISchema => {
if (!schema) {
return;
}
const properties = cloneDeep(schema.configurableProperties) as any;
if (schema.hasDefaultValue === true) {
properties['defaultValue'] = cloneDeep(schema.default.uiSchema);
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
properties['defaultValue']['x-decorator'] = 'FormItem';
}
const initialValue: any = {
name: `f_${uid()}`,
template: schema.name,
...cloneDeep(schema.default),
};
if (initialValue.reverseField) {
initialValue.reverseField.name = `f_${uid()}`;
}
return {
type: 'object',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Action.Drawer',
'x-component-props': {
getContainer: '{{ getContainer }}',
},
'x-decorator': 'Form',
'x-decorator-props': {
useValues(options) {
return useRequest(
() =>
Promise.resolve({
data: initialValue,
}),
options,
);
},
},
title: '{{ t("Create collection") }}',
properties: {
summary: {
type: 'void',
'x-component': 'FieldSummary',
'x-component-props': {
schemaKey: schema.name,
},
},
// @ts-ignore
...properties,
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: '{{ t("Cancel") }}',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: '{{ t("Submit") }}',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: () => useCreateCollection(),
},
},
},
},
},
},
},
};
};
const useDefaultCollectionFields = (values) => {
let defaults = values.fields ? [...values.fields] : [];
const { autoGenId = true, createdAt = true, createdBy = true, updatedAt = true, updatedBy = true } = values;
if (autoGenId) {
const pk = values.fields.find((f) => f.primaryKey);
if (!pk) {
defaults.push({
name: 'id',
type: 'bigInt',
autoIncrement: true,
primaryKey: true,
allowNull: false,
uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true },
interface: 'id',
});
}
}
if (createdAt) {
defaults.push({
name: 'createdAt',
interface: 'createdAt',
type: 'date',
field: 'createdAt',
uiSchema: {
type: 'datetime',
title: '{{t("Created at")}}',
'x-component': 'DatePicker',
'x-component-props': {},
'x-read-pretty': true,
},
});
}
if (createdBy) {
defaults.push({
name: 'createdBy',
interface: 'createdBy',
type: 'belongsTo',
target: 'users',
foreignKey: 'createdById',
uiSchema: {
type: 'object',
title: '{{t("Created by")}}',
'x-component': 'RecordPicker',
'x-component-props': {
fieldNames: {
value: 'id',
label: 'nickname',
},
},
'x-read-pretty': true,
},
});
}
if (updatedAt) {
defaults.push({
type: 'date',
field: 'updatedAt',
name: 'updatedAt',
interface: 'updatedAt',
uiSchema: {
type: 'string',
title: '{{t("Last updated at")}}',
'x-component': 'DatePicker',
'x-component-props': {},
'x-read-pretty': true,
},
});
}
if (updatedBy) {
defaults.push({
type: 'belongsTo',
target: 'users',
foreignKey: 'updatedById',
name: 'updatedBy',
interface: 'updatedBy',
uiSchema: {
type: 'object',
title: '{{t("Last updated by")}}',
'x-component': 'RecordPicker',
'x-component-props': {
fieldNames: {
value: 'id',
label: 'nickname',
},
},
'x-read-pretty': true,
},
});
}
// 其他
return defaults;
};
const useCreateCollection = () => {
const form = useForm();
const { refreshCM } = useCollectionManager();
const ctx = useActionContext();
const { refresh } = useResourceActionContext();
const { resource } = useResourceContext();
return {
async run() {
await form.submit();
const values = cloneDeep(form.values);
const fields = useDefaultCollectionFields(values);
console.log(fields);
if (values.autoCreateReverseField) {
} else {
delete values.reverseField;
}
delete values.id;
delete values.autoCreateReverseField;
await resource.create({
values: {
logging: true,
...values,
fields,
},
});
ctx.setVisible(false);
await form.reset();
refresh();
await refreshCM();
},
};
};
export const AddCollection = (props) => {
const record = useRecord();
return <AddCollectionAction item={record} {...props} />;
};
export const AddCollectionAction = (props) => {
const { scope, getContainer, item: record, children, trigger, align } = props;
const { getTemplate } = useCollectionManager();
const [visible, setVisible] = useState(false);
const [schema, setSchema] = useState({});
const compile = useCompile();
const { t } = useTranslation();
return (
<RecordProvider record={record}>
<ActionContext.Provider value={{ visible, setVisible }}>
<Dropdown
getPopupContainer={getContainer}
trigger={trigger}
align={align}
overlay={
<Menu
style={{
maxHeight: '60vh',
overflow: 'auto',
}}
onClick={(info) => {
const schema = getSchema(getTemplate(info.key), record, compile);
setSchema(schema);
setVisible(true);
}}
>
{templateOptions().map((option) => {
return <Menu.Item key={option.name}>{compile(option.title)}</Menu.Item>;
})}
</Menu>
}
>
{children || (
<Button icon={<PlusOutlined />} type={'primary'}>
{t('Create collection')} <DownOutlined />
</Button>
)}
</Dropdown>
<SchemaComponent
schema={schema}
components={{ ...components, ArrayTable }}
scope={{
getContainer,
useCancelAction,
createOnly: true,
useCreateCollection,
record,
showReverseFieldConfig: true,
...scope,
}}
/>
</ActionContext.Provider>
</RecordProvider>
);
};

View File

@ -9,12 +9,12 @@ import { useTranslation } from 'react-i18next';
import { useRequest } from '../../api-client';
import { RecordProvider, useRecord } from '../../record-provider';
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
import { useCancelAction, useCreateAction } from '../action-hooks';
import { useCancelAction } from '../action-hooks';
import { useCollectionManager } from '../hooks';
import { IField } from '../interfaces/types';
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
import * as components from './components';
import { getOptions } from './interfaces';
import { options } from './interfaces';
const getSchema = (schema: IField, record: any, compile) => {
if (!schema) {
@ -114,7 +114,6 @@ export const useCollectionFieldFormValues = () => {
const useCreateCollectionField = () => {
const form = useForm();
const { run } = useCreateAction();
const { refreshCM } = useCollectionManager();
const ctx = useActionContext();
const { refresh } = useResourceActionContext();
@ -144,11 +143,44 @@ export const AddCollectionField = (props) => {
export const AddFieldAction = (props) => {
const { scope, getContainer, item: record, children, trigger, align } = props;
const { getInterface } = useCollectionManager();
const { getInterface, getTemplate } = useCollectionManager();
const [visible, setVisible] = useState(false);
const [schema, setSchema] = useState({});
const compile = useCompile();
const { t } = useTranslation();
const getFieldOptions = () => {
const { availableFieldInterfaces } = getTemplate(record.template) || {};
const { exclude, include } = availableFieldInterfaces || {};
const optionArr = [];
options.forEach((v) => {
if (v.key === 'systemInfo') {
optionArr.push({
...v,
children: v.children.filter((v) => {
if (v.value === 'id') {
return typeof record['autoGenId'] === 'boolean' ? record['autoGenId'] : true;
} else {
return typeof record[v.value] === 'boolean' ? record[v.value] : true;
}
}),
});
} else {
const children = v.children.filter((v) => {
if (include?.length) {
return include.includes(v.value);
} else if (exclude?.length) {
return !exclude.includes(v.value);
}
return true;
});
optionArr.push({
...v,
children,
});
}
});
return optionArr;
};
return (
<RecordProvider record={record}>
<ActionContext.Provider value={{ visible, setVisible }}>
@ -170,7 +202,7 @@ export const AddFieldAction = (props) => {
}
}}
>
{getOptions().map((option) => {
{getFieldOptions().map((option) => {
return (
option.children.length > 0 && (
<Menu.ItemGroup key={option.label} title={compile(option.label)}>

View File

@ -1,13 +1,12 @@
import { useForm } from '@formily/react';
import { action } from '@formily/reactive';
import { uid } from '@formily/shared';
import React, { useContext, useEffect, useRef, useState } from 'react';
import React, { useContext, useRef, useState } from 'react';
import { CollectionFieldsTable } from '.';
import { useRequest } from '../../api-client';
import { useCurrentAppInfo } from '../../appInfo';
import { useRecord } from '../../record-provider';
import { SchemaComponent, SchemaComponentContext, useActionContext, useCompile } from '../../schema-component';
import { useCancelAction, useUpdateCollectionActionAndRefreshCM } from '../action-hooks';
import { SchemaComponent, SchemaComponentContext, useCompile } from '../../schema-component';
import { useCancelAction } from '../action-hooks';
import { useCollectionManager } from '../hooks/useCollectionManager';
import { DataSourceContext } from '../sub-table';
import { AddSubFieldAction } from './AddSubFieldAction';
@ -25,109 +24,6 @@ const useAsyncDataSource = (service: any) => (field: any) => {
);
};
const useCollectionValues = (options) => {
const { visible } = useActionContext();
const result = useRequest(
() =>
Promise.resolve({
data: {
name: `t_${uid()}`,
createdBy: true,
updatedBy: true,
sortable: true,
logging: true,
fields: [
{
name: 'id',
type: 'bigInt',
autoIncrement: true,
primaryKey: true,
allowNull: false,
uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true },
interface: 'id',
},
{
interface: 'createdAt',
type: 'date',
field: 'createdAt',
name: 'createdAt',
uiSchema: {
type: 'datetime',
title: '{{t("Created at")}}',
'x-component': 'DatePicker',
'x-component-props': {},
'x-read-pretty': true,
},
},
{
interface: 'createdBy',
type: 'belongsTo',
target: 'users',
foreignKey: 'createdById',
name: 'createdBy',
uiSchema: {
type: 'object',
title: '{{t("Created by")}}',
'x-component': 'RecordPicker',
'x-component-props': {
fieldNames: {
value: 'id',
label: 'nickname',
},
},
'x-read-pretty': true,
},
},
{
type: 'date',
field: 'updatedAt',
name: 'updatedAt',
interface: 'updatedAt',
uiSchema: {
type: 'string',
title: '{{t("Last updated at")}}',
'x-component': 'DatePicker',
'x-component-props': {},
'x-read-pretty': true,
},
},
{
type: 'belongsTo',
target: 'users',
foreignKey: 'updatedById',
name: 'updatedBy',
interface: 'updatedBy',
uiSchema: {
type: 'object',
title: '{{t("Last updated by")}}',
'x-component': 'RecordPicker',
'x-component-props': {
fieldNames: {
value: 'id',
label: 'nickname',
},
},
'x-read-pretty': true,
},
},
],
},
}),
{
...options,
manual: true,
},
);
useEffect(() => {
if (visible) {
result.run();
}
}, [visible]);
return result;
};
const useSelectedRowKeys = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
return [selectedRowKeys, setSelectedRowKeys];
@ -205,13 +101,11 @@ export const ConfigurationTable = () => {
useDestroySubField,
useBulkDestroySubField,
useSelectedRowKeys,
useCollectionValues,
useAsyncDataSource,
loadCollections,
useCurrentFields,
useNewId,
useCancelAction,
useUpdateCollectionActionAndRefreshCM,
enableInherits: database?.dialect === 'postgres',
}}
/>

View File

@ -0,0 +1,161 @@
import { ArrayTable } from '@formily/antd';
import { ISchema, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import { omit } from 'lodash';
import cloneDeep from 'lodash/cloneDeep';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useRequest } from '../../api-client';
import { RecordProvider, useRecord } from '../../record-provider';
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
import { useCancelAction } from '../action-hooks';
import { useCollectionManager } from '../hooks';
import { IField } from '../interfaces/types';
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
import * as components from './components';
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
if (!schema) {
return;
}
const properties = (cloneDeep(schema.configurableProperties) as any) || {
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-disabled': true,
},
};
properties.name['x-disabled'] = true;
if (schema.hasDefaultValue === true) {
properties['defaultValue'] = cloneDeep(schema.default.uiSchema);
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
properties['defaultValue']['x-decorator'] = 'FormItem';
}
return {
type: 'object',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ useValuesFromRecord }}',
},
title: '{{ t("Edit collection") }}',
properties: {
...properties,
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: '{{ t("Cancel") }}',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: '{{ t("Submit") }}',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useUpdateCollectionActionAndRefreshCM }}',
},
},
},
},
},
},
},
};
};
export const useValuesFromRecord = (options) => {
const record = useRecord();
const result = useRequest(() => Promise.resolve({ data: { autoGenId: true, ...record } }), {
...options,
manual: true,
});
const ctx = useActionContext();
useEffect(() => {
if (ctx.visible) {
result.run();
}
}, [ctx.visible]);
return result;
};
export const useUpdateCollectionActionAndRefreshCM = (options) => {
const { refreshCM } = useCollectionManager();
const form = useForm();
const ctx = useActionContext();
const { refresh } = useResourceActionContext();
const { resource, targetKey } = useResourceContext();
const { [targetKey]: filterByTk } = useRecord();
return {
async run() {
await form.submit();
await resource.update({ filterByTk, values: omit(form.values, ['fields']) });
ctx.setVisible(false);
await form.reset();
refresh();
await refreshCM();
},
};
};
export const EditCollection = (props) => {
const record = useRecord();
return <EditCollectionAction item={record} {...props} />;
};
export const EditCollectionAction = (props) => {
const { scope, getContainer, item: record, children } = props;
const { getTemplate } = useCollectionManager();
const [visible, setVisible] = useState(false);
const [schema, setSchema] = useState({});
const { t } = useTranslation();
const compile = useCompile();
return (
<RecordProvider record={record}>
<ActionContext.Provider value={{ visible, setVisible }}>
<a
onClick={async () => {
const templateConf = getTemplate(record.template);
const schema = getSchema(
{
...templateConf,
},
record,
compile,
getContainer,
);
setSchema(schema);
setVisible(true);
}}
>
{children || t('Edit')}
</a>
<SchemaComponent
schema={schema}
components={{ ...components, ArrayTable }}
scope={{
getContainer,
useValuesFromRecord,
useUpdateCollectionActionAndRefreshCM,
useCancelAction,
createOnly: false,
...scope,
}}
/>
</ActionContext.Provider>
</RecordProvider>
);
};

View File

@ -0,0 +1,14 @@
import { observer } from '@formily/react';
import { Tag } from 'antd';
import React from 'react';
import { useCompile } from '../../../schema-component';
import { useCollectionManager } from '../../hooks';
export const CollectionTemplate = observer((props: any) => {
const { value } = props;
const { getTemplate } = useCollectionManager();
const compile = useCompile();
const schema = getTemplate(value);
return <Tag>{compile(schema?.title||'{{t("General collection")}}')}</Tag>;
});

View File

@ -1,15 +1,17 @@
import { registerValidateFormats } from '@formily/core';
import { exp } from 'mathjs';
export * from './AddFieldAction';
export * from './ConfigurationTable';
export * from './EditFieldAction';
export * from './interfaces';
export * from './components';
export * from './templates';
export * from './CollectionFieldsTable';
export * from './schemas/collections'
export * from './OverridingCollectionField'
export * from './ViewInheritedField'
export * from './schemas/collections';
export * from './OverridingCollectionField';
export * from './ViewInheritedField';
export * from './AddCollectionAction';
export * from './EditCollectionAction';
registerValidateFormats({
uid: /^[A-Za-z0-9][A-Za-z0-9_-]*$/,

View File

@ -35,6 +35,7 @@ export const getOptions = () => {
return Object.keys(groupLabels).map((groupName) => {
return {
label: groupLabels[groupName],
key: groupName,
children: Object.keys(fields[groupName] || {})
.map((type) => {
const field = fields[groupName][type];

View File

@ -1,6 +1,7 @@
import { ISchema, Schema } from '@formily/react';
import { i18n } from '../../../i18n';
import { CollectionOptions } from '../../types';
import { CollectionTemplate } from '../components/CollectionTemplate';
import { collectionFieldSchema } from './collectionFields';
const compile = (source) => {
@ -35,6 +36,16 @@ export const collection: CollectionOptions = {
'{{t("Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.")}}',
},
},
{
type: 'string',
name: 'template',
interface: 'input',
uiSchema: {
title: '{{ t("Collection Template") }}',
type: 'string',
'x-component': 'Input',
},
},
{
type: 'hasMany',
name: 'fields',
@ -60,85 +71,6 @@ export const collection: CollectionOptions = {
],
};
export const createCollectionProperties = {
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-validator': 'uid',
},
inherits: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-visible': '{{ enableInherits}}',
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: '{{ t("Cancel") }}',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ cm.useCancelAction }}',
},
},
action2: {
title: '{{ t("Submit") }}',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ cm.useCreateActionAndRefreshCM }}',
},
},
},
},
};
export const editCollectionProperties = {
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-disabled': true,
},
inherits: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-disabled': true,
'x-visible': '{{ enableInherits}}',
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: '{{ t("Cancel") }}',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: '{{ t("Submit") }}',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useUpdateCollectionActionAndRefreshCM }}',
},
},
},
},
};
export const collectionSchema: ISchema = {
type: 'object',
properties: {
@ -190,6 +122,7 @@ export const collectionSchema: ISchema = {
title: '{{ t("Delete") }}',
'x-component': 'Action',
'x-component-props': {
icon: 'DeleteOutlined',
useAction: '{{ cm.useBulkDestroyActionAndRefreshCM }}',
confirm: {
title: "{{t('Delete record')}}",
@ -200,22 +133,10 @@ export const collectionSchema: ISchema = {
create: {
type: 'void',
title: '{{ t("Create collection") }}',
'x-component': 'Action',
'x-component': 'AddCollection',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
title: '{{ t("Create collection") }}',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ useCollectionValues }}',
},
properties: createCollectionProperties,
},
},
},
},
},
@ -255,6 +176,18 @@ export const collectionSchema: ISchema = {
},
},
column3: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
title: '{{t("Collection template")}}',
properties: {
template: {
'x-component': CollectionTemplate,
'x-read-pretty': true,
},
},
},
column4: {
type: 'void',
title: '{{ t("Actions") }}',
'x-component': 'Table.Column',
@ -294,22 +227,10 @@ export const collectionSchema: ISchema = {
update: {
type: 'void',
title: '{{ t("Edit") }}',
'x-component': 'Action.Link',
'x-component': 'EditCollection',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ cm.useValuesFromRecord }}',
},
title: '{{ t("Edit collection") }}',
properties: editCollectionProperties,
},
},
},
delete: {
type: 'void',

View File

@ -0,0 +1,23 @@
import { ISchema } from '@formily/react';
import * as types from '../templates';
export const templates = new Map<string, ISchema>();
const collectionTemplates: any = {};
export function registerTemplate(key: string, schema: any) {
collectionTemplates[key] = schema;
}
Object.keys(types).forEach((type) => {
const schema = types[type];
registerTemplate(schema.name || 'others', { order: 0, ...schema });
});
export const templateOptions = () =>
Object.keys(collectionTemplates)
.sort((a, b) => {
return collectionTemplates[a].order - collectionTemplates[b].order;
})
.map((templace) => {
return collectionTemplates[templace];
});

View File

@ -259,39 +259,6 @@ export const useBulkDestroyAction = () => {
};
};
export const useUpdateCollectionActionAndRefreshCM = (options) => {
const { refreshCM } = useCollectionManager();
const form = useForm();
const ctx = useActionContext();
const { refresh } = useResourceActionContext();
const { resource, targetKey } = useResourceContext();
const { [targetKey]: filterByTk } = useRecord();
return {
async run() {
await form.submit();
await resource.update({ filterByTk, values: omit(form.values, ['fields']) });
ctx.setVisible(false);
await form.reset();
refresh();
await refreshCM();
},
};
};
export const useValuesFromRecord = (options) => {
const record = useRecord();
const result = useRequest(() => Promise.resolve({ data: record }), {
...options,
manual: true,
});
const ctx = useActionContext();
useEffect(() => {
if (ctx.visible) {
result.run();
}
}, [ctx.visible]);
return result;
};
export const useValuesFromRA = (options) => {
const ctx = useResourceActionContext();

View File

@ -1,11 +1,11 @@
import { clone } from '@formily/shared';
import { reduce, unionBy, uniq } from 'lodash';
import { useContext } from 'react';
import { reduce, unionBy,uniq } from 'lodash';
import { CollectionManagerContext } from '../context';
import { CollectionFieldOptions } from '../types';
export const useCollectionManager = () => {
const { refreshCM, service, interfaces, collections } = useContext(CollectionManagerContext);
const { refreshCM, service, interfaces, collections, templates } = useContext(CollectionManagerContext);
const getInheritedFields = (name) => {
const inheritKeys = getInheritCollections(name);
const inheritedFields = reduce(
@ -22,7 +22,7 @@ export const useCollectionManager = () => {
const getCollectionFields = (name: string): CollectionFieldOptions[] => {
const currentFields = collections?.find((collection) => collection.name === name)?.fields;
const inheritedFields = getInheritedFields(name);
const totalFields = unionBy(currentFields?.concat(inheritedFields) || [], 'name').filter((v:any) => {
const totalFields = unionBy(currentFields?.concat(inheritedFields) || [], 'name').filter((v: any) => {
return !v.isForeignKey;
});
return totalFields;
@ -122,6 +122,9 @@ export const useCollectionManager = () => {
getInterface(name: string) {
return interfaces[name] ? clone(interfaces[name]) : null;
},
getTemplate(name: string = 'general') {
return templates[name] ? clone(templates[name] || templates['general']) : null;
},
getParentCollectionFields: (parentCollection, currentCollection) => {
const currentFields = collections?.find((collection) => collection.name === currentCollection)?.fields;
const parentFields = collections?.find((collection) => collection.name === parentCollection)?.fields;

View File

@ -12,5 +12,7 @@ export * from './hooks';
export * as interfacesProperties from './interfaces/properties';
export * from './interfaces/types';
export * from './ResourceActionProvider';
export { getConfigurableProperties } from './templates/properties';
export * from './templates/types';
export * from './types';

View File

@ -60,6 +60,7 @@ export const constraintsProps = {
title: '{{t("ON DELETE")}}',
required: true,
default: 'SET NULL',
'x-disabled': '{{ !createOnly }}',
'x-decorator': 'FormItem',
'x-component': 'Select',
enum: [

View File

@ -0,0 +1,37 @@
import { getConfigurableProperties } from './properties';
import { ICollectionTemplate } from './types';
export const calendar: ICollectionTemplate = {
name: 'calendar',
title: '{{t("Calendar collection")}}',
order: 2,
color: 'orange',
default: {
fields: [
{
name: 'cron',
type: 'string',
uiSchema: {
type: 'string',
title: '{{t("Cron")}}',
'x-component': 'Select',
enum: [
{ value: '0 0 * * *', label: '每天' },
{ value: '0 0 ? * 1', label: '每个星期一' },
{ value: '0 0 12 * * ?', label: '每天中午12点' },
{ value: '0 0 10,14,16 * * ?', label: '每天上午10点,下午2点,4点 ' },
],
},
interface: 'select',
},
{
name: 'exclude',
type: 'json',
},
],
},
availableFieldInterfaces: {
include: [],
},
configurableProperties: getConfigurableProperties('title', 'name', 'inherits'),
};

View File

@ -0,0 +1,23 @@
import { getConfigurableProperties } from './properties';
import { ICollectionTemplate } from './types';
export const general: ICollectionTemplate = {
name: 'general',
title: '{{t("General collection")}}',
order: 1,
color: 'blue',
default: {
fields: [],
},
configurableProperties: getConfigurableProperties(
'title',
'name',
'inherits',
'autoGenId',
'createdAt',
'updatedAt',
'createdBy',
'updatedBy',
'sortable',
),
};

View File

@ -0,0 +1,3 @@
export * from './calendar';
export * from './general';

View File

@ -0,0 +1,121 @@
export const defaultConfigurableProperties = {
title: {
type: 'string',
title: '{{ t("Collection display name") }}',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
title: '{{t("Collection name")}}',
required: true,
'x-disabled': '{{ !createOnly }}',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': 'uid',
description:
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
},
inherits: {
title: '{{t("Inherits")}}',
type: 'hasMany',
name: 'inherits',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
mode: 'multiple',
},
'x-disabled': '{{ !createOnly }}',
'x-visible': '{{ enableInherits}}',
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
},
autoGenId: {
type: 'boolean',
'x-content': '{{t("AutoGenId")}}',
default: true,
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-disabled': '{{ !createOnly }}',
'x-reactions': [
{
target: 'sortable',
when: '{{createOnly&&!$self.value}}',
fulfill: {
state: {
value: false,
},
schema: {
'x-disabled': true,
},
},
otherwise: {
schema: {
'x-disabled': '{{!createOnly}}',
},
},
},
],
},
createdBy: {
type: 'boolean',
'x-content': '{{t("CreatedBy")}}',
default: true,
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-disabled': '{{ !createOnly }}',
},
updatedBy: {
type: 'boolean',
'x-content': '{{t("UpdatedBy")}}',
default: true,
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-disabled': '{{ !createOnly }}',
},
createdAt: {
type: 'boolean',
'x-content': '{{t("CreatedAt")}}',
default: true,
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-disabled': '{{ !createOnly }}',
},
updatedAt: {
type: 'boolean',
'x-content': '{{t("UpdatedAt")}}',
default: true,
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-disabled': '{{ !createOnly }}',
},
sortable: {
type: 'boolean',
'x-content': '{{t("Records can be sorted")}}',
default: true,
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-disabled': '{{ !createOnly }}',
},
};
export type DefaultConfigurableKeys =
| 'name'
| 'title'
| 'inherits'
| 'autoGenId'
| 'createdBy'
| 'updatedBy'
| 'createdAt'
| 'updatedAt'
| 'sortable';
export const getConfigurableProperties = (...keys: DefaultConfigurableKeys[]) => {
const props = {};
for (const key of keys) {
if (defaultConfigurableProperties[key]) {
props[key] = defaultConfigurableProperties[key];
}
}
return props;
};

View File

@ -0,0 +1,52 @@
import { ISchema } from '@formily/react';
import { FieldOptions } from '../../collection-manager/types';
export interface ICollectionTemplate {
name: string;
title?: string;
color?:string;
/** 排序 */
order?: number;
/** 默认配置 */
default?: CollectionOptions;
/** UI 可配置的 CollectionOptions 参数(添加或编辑的 Collection 表单的字段) */
configurableProperties?: Record<string, ISchema>;
/** 当前模板可用的字段类型 */
availableFieldInterfaces?: AvailableFieldInterfacesInclude | AvailableFieldInterfacesExclude;
}
interface AvailableFieldInterfacesInclude {
include?: any[];
}
interface AvailableFieldInterfacesExclude {
exclude?: any[];
}
interface CollectionOptions {
/**
* id
* @default true
* */
autoGenId?: boolean;
/** 创建人 */
createdBy?: boolean;
/** 最后更新人 */
updatedBy?: boolean;
/** 创建日期 */
createdAt?: boolean;
/** 更新日期 */
updatedAt?: boolean;
/** 可排序 */
sortable?: boolean;
/* 树结构 */
tree?: string;
/* 日志 */
logging?: boolean;
/** 继承 */
inherits?: string | string[];
/* 字段列表 */
fields?: FieldOptions[];
}

View File

@ -4,6 +4,7 @@ export interface CollectionManagerOptions {
service?: any;
interfaces?: any;
collections?: any[];
templates?: any;
refreshCM?: () => Promise<void>;
}

View File

@ -114,6 +114,15 @@ export default {
"Collection display name": "Collection display name",
"Collection name": "Collection name",
"Inherits":"Inherits",
"AutoGenId":"Auto-generated ID field",
"CreatedBy":"Recording a row's created user",
"UpdatedBy":"Recording a row's last updated user",
"CreatedAt":"Recording a row's created time ",
"UpdatedAt":"Recording a row's last updated user",
"Records can be sorted": "Records can be sorted",
"Collection template":"Collection template",
"Calendar collection":"Calendar collection",
"General collection":"General collection",
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.",
"Storage type": "Storage type",
"Edit": "Edit",

View File

@ -107,6 +107,16 @@ export default {
"Create collection": "コレクションの作成",
"Collection display name": "コレクション名",
"Collection name": "コレクション識別子",
"Inherits":"継承",
"AutoGenId":"IDフィールドの自動生成",
"CreatedBy":"レコード作成者",
"UpdatedBy":"レコード最終更新者",
"CreatedAt":"レコード作成時間",
"UpdatedAt":"レコード最終更新時間",
"Records can be sorted":"ソート可能",
"Collection template":"データテーブルテンプレート",
"Calendar collection":"カレンダデータテーブル",
"General collection":"一般データテーブル",
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "ランダムに生成され、変更可能です。 アルファベット、数字、アンダースコアをサポートし、アルファベットから始まる必要があります。",
"Storage type": "ストレージタイプ",
"Edit": "編集",
@ -503,6 +513,5 @@ export default {
"Format": "フォーマット",
"Error message": "エラーメッセージ",
"Record picker": "レコードピッカー",
"Sortable": "ソート可能",
"Search and select collection": "Search and select collection",
}

View File

@ -118,6 +118,15 @@ export default {
"Collection display name": "数据表名称",
"Collection name": "数据表标识",
"Inherits":"继承",
"AutoGenId":"自动生成 ID 字段",
"CreatedBy":"记录创建人",
"UpdatedBy":"记录最后更新人",
"CreatedAt":"记录创建时间",
"UpdatedAt":"记录最后更新时间",
"Records can be sorted":"可以对行记录进行排序",
"Collection template":"数据表模板",
"Calendar collection":"日历数据表",
"General collection":"普通数据表",
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "随机生成,可修改。支持英文、数字和下划线,必须以英文字母开头。",
"Storage type": "存储类型",
"Edit": "编辑",
@ -228,7 +237,6 @@ export default {
"Editable": "可编辑",
"Readonly": "只读(禁止编辑)",
"Easy-reading": "只读(阅读模式)",
"Sortable": "可排序",
"Add filter": "添加筛选条件",
"Add filter group": "添加筛选分组",
"Comparision": "值比较",

View File

@ -37,7 +37,7 @@ export const TableBlockDesigner = () => {
return (
<GeneralSchemaDesigner template={template} title={title || name}>
<SchemaSettings.BlockTitleItem />
<SchemaSettings.SwitchItem
{sortable&& <SchemaSettings.SwitchItem
title={t('Enable drag and drop sorting')}
checked={field.decoratorProps.dragSort}
onChange={(dragSort) => {
@ -51,7 +51,7 @@ export const TableBlockDesigner = () => {
},
});
}}
/>
/>}
<SchemaSettings.ModalItem
title={t('Set the data scope')}
schema={

View File

@ -524,6 +524,8 @@ export const useCollectionDataSourceItems = (componentName) => {
const b = !value || selected.includes(item.name);
if (item.inherit) {
return false;
} else if (!item.fields.find((v) => v.primaryKey)) {
return false;
} else {
return b && !(item?.isThrough && item?.autoCreate);
}

View File

@ -105,6 +105,15 @@ class AppGenerator extends Generator {
break;
}
const keys = ['PLUGIN_PACKAGE_PREFIX', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD', 'DB_STORAGE'];
for (const key in env) {
if (keys.includes(key)) {
continue;
}
envs.push(`${key}=${env[key]}`);
}
return {
...this.context,
dependencies: dependencies.join(`,\n `),

View File

@ -3,23 +3,25 @@ import path from 'path';
export class PresetNocoBase extends Plugin {
async addBuiltInPlugins() {
const plugins = [
'error-handler',
'collection-manager',
'ui-schema-storage',
'ui-routes-storage',
'file-manager',
'system-settings',
'verification',
'users',
'acl',
'china-region',
'workflow',
'client',
'export',
'import',
'audit-logs',
];
const plugins = process.env.PRESET_NOCOBASE_PLUGINS
? process.env.PRESET_NOCOBASE_PLUGINS.split(',')
: [
'error-handler',
'collection-manager',
'ui-schema-storage',
'ui-routes-storage',
'file-manager',
'system-settings',
'verification',
'users',
'acl',
'china-region',
'workflow',
'client',
'export',
'import',
'audit-logs',
];
await this.app.pm.add(plugins, {
enabled: true,
builtIn: true,

@ -1 +1 @@
Subproject commit 5a5fba41ae1cecaf3b073fcb610e9dc68dad0b85
Subproject commit 67f51c7fdd132ab12821f31f737b6f0cfb70d212

View File

@ -0,0 +1,28 @@
# custom-collection-template
## Register
```ts
yarn pm add sample-custom-collection-template
```
## Activate
```bash
yarn pm enable sample-custom-collection-template
```
## Launch the app
```bash
# for development
yarn dev
# for production
yarn build
yarn start
```
## Demo
[gif]

View File

@ -0,0 +1,4 @@
// @ts-nocheck
export * from './lib/client';
export { default } from './lib/client';

View File

@ -0,0 +1,30 @@
"use strict";
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
var _index = _interopRequireWildcard(require("./lib/client"));
Object.defineProperty(exports, "__esModule", {
value: true
});
var _exportNames = {};
Object.defineProperty(exports, "default", {
enumerable: true,
get: function get() {
return _index.default;
}
});
Object.keys(_index).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _index[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function get() {
return _index[key];
}
});
});

View File

@ -0,0 +1,11 @@
{
"name": "@nocobase/plugin-sample-custom-collection-template",
"version": "0.8.0-alpha.13",
"main": "lib/server/index.js",
"devDependencies": {
"@nocobase/client": "0.8.0-alpha.13",
"@nocobase/server": "0.8.0-alpha.13",
"@nocobase/test": "0.8.0-alpha.13"
},
"gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644"
}

View File

@ -0,0 +1,31 @@
import { getConfigurableProperties, ICollectionTemplate, registerTemplate } from '@nocobase/client';
import React from 'react';
const myCollectionTemplate: ICollectionTemplate = {
name: 'myCollection',
title: '{{t("Custom template")}}',
order: 6,
color: 'blue',
default: {
fields: [
{
name: 'uuid',
type: 'string',
primaryKey: true,
allowNull: false,
uiSchema: { type: 'number', title: '{{t("UUID")}}', 'x-component': 'Input', 'x-read-pretty': true },
interface: 'input',
},
],
},
configurableProperties: getConfigurableProperties('name', 'title', 'inherits', 'createdAt', 'updatedAt'),
availableFieldInterfaces: {
exclude: ['linkTo', 'o2o'],
},
};
registerTemplate('myCollection', myCollectionTemplate);
export default React.memo((props) => {
return <>{props.children}</>;
});

View File

@ -0,0 +1 @@
export { default } from './server';

View File

@ -0,0 +1,16 @@
import { InstallOptions, Plugin } from '@nocobase/server';
export class CustomCollectionTemplatePlugin extends Plugin {
afterAdd() {}
beforeLoad() {}
async load() {}
async install(options: InstallOptions) {
// TODO
}
}
export default CustomCollectionTemplatePlugin;

337
yarn.lock
View File

@ -329,23 +329,6 @@
csstype "^3.0.8"
tslib "^2.0.3"
"@antv/x6-react-shape@^1.6.2":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@antv/x6-react-shape/-/x6-react-shape-1.6.3.tgz#053660bd555c4356e9719134e3e2a857b51301e2"
integrity sha512-iCsRVkvKCVs7HUUblF/ALg5XxeeEPXDLHuYzRvbvNlCKKLbXLbaz6vJQSgTTrk2i4lrD+7xl4riA76FyWA3k/g==
"@antv/x6@^1.34.2":
version "1.34.6"
resolved "https://registry.yarnpkg.com/@antv/x6/-/x6-1.34.6.tgz#b802b09b52c8620acf01734397714ea5e3f715b6"
integrity sha512-cl8ywN7CTNVT94/ZEbq2mnZQTQe6FUgIlGOAjNOPL8sljZTBrpE6OsuGw4BDA2/XllwELfUfvVpgBvbdwRKrxw==
dependencies:
csstype "^3.0.3"
jquery "^3.5.1"
jquery-mousewheel "^3.1.13"
lodash-es "^4.17.15"
mousetrap "^1.6.5"
utility-types "^3.10.0"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431"
@ -2969,15 +2952,6 @@
resolved "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f"
integrity sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==
"@dabh/diagnostics@^2.0.2":
version "2.0.3"
resolved "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a"
integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==
dependencies:
colorspace "1.1.x"
enabled "2.0.x"
kuler "^2.0.0"
"@dnd-kit/accessibility@^3.0.0":
version "3.0.0"
resolved "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.0.0.tgz#b56e3750414fd907b7d6972b3116aa8f96d07fde"
@ -4383,153 +4357,6 @@
call-me-maybe "^1.0.1"
glob-to-regexp "^0.3.0"
"@nocobase/acl@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/acl/-/acl-0.8.0-alpha.9.tgz#7ffab4a367befc74daa681b1d5c2c907126cb280"
integrity sha512-jEgpjO6gGi4GwSyWxlmcwd368/AQgmrdsODDTLhhrJJI9+ovj2mxO664lQgh3FkDGtmduKlBiZ+qcgK+/RWa1w==
dependencies:
"@nocobase/resourcer" "0.8.0-alpha.9"
json-templates "^4.2.0"
"@nocobase/actions@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/actions/-/actions-0.8.0-alpha.9.tgz#3804bbf30f7c4e8aa205b9802ec4f56a58c18c72"
integrity sha512-EvdNjXa+TTMU2Xn+MnA7tSa5rooGFtzsvJsSYbkpODtsVQsmTDRIZYZsOYFk/XcezxjzTTsxxXMSA7sLq6wifQ==
dependencies:
"@nocobase/cache" "0.8.0-alpha.9"
"@nocobase/database" "0.8.0-alpha.9"
"@nocobase/resourcer" "0.8.0-alpha.9"
"@nocobase/cache@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/cache/-/cache-0.8.0-alpha.9.tgz#99f7ca196dacd41b69ed7b7f179fcc1c4c5baa27"
integrity sha512-GCi7OE9nLrXKq/Og6SPtPcjtreUxxAL9x7CwmD2x3Zb7gNTc2GvQ9NWoAlO8KTLlWuzuNcJXegBhxaxV8Lt5ew==
dependencies:
cache-manager "^4.1.0"
"@nocobase/client@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/client/-/client-0.8.0-alpha.9.tgz#03446b4c4160a7ebda9dfd8aa80e2203541eb72c"
integrity sha512-cvh01k8Ujhc9D7kDYQ7QKP5tnLF+wBOqixtisivti9aeklMBaOKj1oLmxKHZlB2IJzKpT+rUntpsxGxUS6co9A==
dependencies:
"@antv/g2plot" "^2.4.18"
"@dnd-kit/core" "^5.0.1"
"@dnd-kit/sortable" "^6.0.0"
"@emotion/css" "^11.7.1"
"@formily/antd" "2.0.20"
"@formily/core" "2.0.20"
"@formily/react" "2.0.20"
"@nocobase/sdk" "0.8.0-alpha.9"
"@nocobase/utils" "0.8.0-alpha.9"
ahooks "^3.0.5"
antd "~4.19.5"
axios "^0.26.1"
classnames "^2.3.1"
cron-parser "^4.6.0"
cronstrue "^2.11.0"
file-saver "^2.0.5"
i18next "^21.6.0"
json-templates "^4.2.0"
marked "^4.0.12"
mathjs "^10.6.0"
react-beautiful-dnd "^13.1.0"
react-big-calendar "^0.38.7"
react-contenteditable "^3.3.6"
react-drag-listview "^0.1.9"
react-helmet "^6.1.0"
react-hotkeys-hook "^3.4.7"
react-i18next "^11.15.1"
react-image-lightbox "^5.1.4"
react-js-cron "^1.4.0"
react-quill "^1.3.5"
react-router-dom "^5.2.0"
react-to-print "^2.14.7"
solarlunar-es "^1.0.9"
use-deep-compare-effect "^1.8.1"
"@nocobase/database@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/database/-/database-0.8.0-alpha.9.tgz#9684b4ddcfc5396df2ef3d4ae9cd2ce59142df81"
integrity sha512-zkLTW2iFg1tlbSTlW40oEmM7O2GxHXmIzErnDR4it0GWoLqjDgzNHP5QjODOxiiB/+/unNFudfGTNCoXxu2ZvA==
dependencies:
"@nocobase/utils" "0.8.0-alpha.9"
async-mutex "^0.3.2"
cron-parser "4.4.0"
deepmerge "^4.2.2"
flat "^5.0.2"
glob "^7.1.6"
mathjs "^10.6.1"
moment "2.x"
semver "^7.3.7"
sequelize "^6.9.0"
umzug "^3.1.1"
"@nocobase/resourcer@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/resourcer/-/resourcer-0.8.0-alpha.9.tgz#5415af5944b31293e5e2da73bf5c999866a90fab"
integrity sha512-6v9bfUex2RfT8C3xX+SUhj8/zECCY6pQA3ce8GV3xd9cu6ku8c7hIiNM3KwgPOD189aND3FxNwxqpECNaoWQ1A==
dependencies:
"@nocobase/utils" "0.8.0-alpha.9"
deepmerge "^4.2.2"
koa-compose "^4.1.0"
lodash "^4.17.21"
path-to-regexp "^6.1.0"
qs "^6.9.4"
"@nocobase/sdk@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/sdk/-/sdk-0.8.0-alpha.9.tgz#5306e7bf4aabf00894a65036c584508d93bda379"
integrity sha512-v+GF4N5ldYwmoqQIukGWzz/eOm2TgxcUZq3uE+YeP/aiDa4XAfkLrpBNsvCS24k3s/U+84FYrvTWRRStJGSzBA==
dependencies:
axios "^0.26.1"
qs "^6.10.1"
"@nocobase/server@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/server/-/server-0.8.0-alpha.9.tgz#bc1955e41b2ac9f6640fdf109516376604d4092c"
integrity sha512-/mHk2Uc8RS/3GcGvnMnFKlbf2aCufGuc+GOr5gEKuyWa50ibD4nZ7LKJx023uEO07rxWkFkhR6+ExuZ+BGRDGQ==
dependencies:
"@hapi/topo" "^6.0.0"
"@koa/cors" "^3.1.0"
"@koa/router" "^9.4.0"
"@nocobase/acl" "0.8.0-alpha.9"
"@nocobase/actions" "0.8.0-alpha.9"
"@nocobase/database" "0.8.0-alpha.9"
"@nocobase/resourcer" "0.8.0-alpha.9"
chalk "^4.1.1"
commander "^9.2.0"
find-package-json "^1.2.0"
i18next "^21.6.0"
koa "^2.13.4"
koa-bodyparser "^4.3.0"
koa-static "^5.0.0"
lodash "^4.17.21"
semver "^7.3.7"
xpipe "^1.0.5"
"@nocobase/test@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/test/-/test-0.8.0-alpha.9.tgz#40aaed50af9b8fd23c0666156c806273c75b0a3f"
integrity sha512-bpzcogPKzfq8mjIsLDy0xlsd0dNAUgmju9kk+RPV5wNZfhvofaA7YduexawbL+dLJQC+iyjmhAhARxl8rVhKuA==
dependencies:
"@nocobase/server" "0.8.0-alpha.9"
"@types/supertest" "^2.0.11"
mockjs "^1.1.0"
mysql2 "^2.3.3"
pg "^8.7.3"
pg-hstore "^2.3.4"
sqlite3 "^5.0.8"
supertest "^6.1.6"
"@nocobase/utils@0.8.0-alpha.9":
version "0.8.0-alpha.9"
resolved "https://registry.yarnpkg.com/@nocobase/utils/-/utils-0.8.0-alpha.9.tgz#fa7e203412e91ab7a6ebb12a6d53d83e09465591"
integrity sha512-oEqKs4+0sO4x0TFEtf+Tf1nIkb9q+9y9YmrqOn6MyMo/ejTSh6nlNt1rAYgs02gTP4uHkSOb2/4K4oDm0JXLdg==
dependencies:
"@hapi/topo" "^6.0.0"
deepmerge "^4.2.2"
flat-to-nested "^1.1.1"
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@ -6949,11 +6776,6 @@ async@^2.6.3, async@~2.6.1:
dependencies:
lodash "^4.17.14"
async@^3.2.3:
version "3.2.4"
resolved "https://registry.npmjs.org/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@ -8334,7 +8156,7 @@ color-support@^1.1.2, color-support@^1.1.3:
resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
color@^3.0.0, color@^3.1.3:
color@^3.0.0:
version "3.2.1"
resolved "https://registry.npmjs.org/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==
@ -8357,14 +8179,6 @@ colors@~1.2.1:
resolved "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc"
integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==
colorspace@1.1.x:
version "1.1.4"
resolved "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243"
integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==
dependencies:
color "^3.1.3"
text-hex "1.0.x"
columnify@^1.5.4:
version "1.5.4"
resolved "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
@ -9268,11 +9082,6 @@ csstype@^3.0.2:
resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==
csstype@^3.0.3:
version "3.1.1"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
csstype@^3.0.8:
version "3.1.0"
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
@ -9322,14 +9131,6 @@ d3-timer@^1.0.9:
resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5"
integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
dagre@^0.8.5:
version "0.8.5"
resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee"
integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==
dependencies:
graphlib "^2.1.8"
lodash "^4.17.15"
dargs@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc"
@ -10049,11 +9850,6 @@ emojis-list@^3.0.0:
resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
enabled@2.0.x:
version "2.0.0"
resolved "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==
encodeurl@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
@ -10942,7 +10738,7 @@ fclone@1.0.11, fclone@~1.0.11:
resolved "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz#10e85da38bfea7fc599341c296ee1d77266ee640"
integrity sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA=
fecha@^4.2.0, fecha@~4.2.0:
fecha@~4.2.0:
version "4.2.3"
resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==
@ -10973,13 +10769,6 @@ file-saver@^2.0.5:
resolved "https://registry.npmmirror.com/file-saver/download/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
file-stream-rotator@^0.6.1:
version "0.6.1"
resolved "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz#007019e735b262bb6c6f0197e58e5c87cb96cec3"
integrity sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==
dependencies:
moment "^2.29.1"
file-type@^3.3.0:
version "3.9.0"
resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
@ -11158,11 +10947,6 @@ fmin@^0.0.2:
tape "^4.5.1"
uglify-js "^2.6.2"
fn.name@1.x.x:
version "1.1.0"
resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
follow-redirects@^1.14.0, follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
@ -11794,13 +11578,6 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
graphlib@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
dependencies:
lodash "^4.17.15"
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@ -14197,16 +13974,6 @@ jmespath@0.15.0:
resolved "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
jquery-mousewheel@^3.1.13:
version "3.1.13"
resolved "https://registry.yarnpkg.com/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz#06f0335f16e353a695e7206bf50503cb523a6ee5"
integrity sha512-GXhSjfOPyDemM005YCEHvzrEALhKDIswtxSHSR2e4K/suHVJKJxxRCGz3skPjNxjJjQa9AVSGGlYjv1M3VLIPg==
jquery@^3.5.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.1.tgz#fab0408f8b45fc19f956205773b62b292c147a16"
integrity sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==
js-base64@^2.5.2:
version "2.6.4"
resolved "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
@ -14674,11 +14441,6 @@ koa@^2.13.4:
type-is "^1.6.16"
vary "^1.1.2"
kuler@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
latest-version@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
@ -14919,7 +14681,7 @@ lockfile@^1.0.4:
dependencies:
signal-exit "^3.0.2"
lodash-es@^4.17.11, lodash-es@^4.17.15:
lodash-es@^4.17.11:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
@ -15086,17 +14848,6 @@ log-driver@^1.2.7:
resolved "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8"
integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==
logform@^2.3.2, logform@^2.4.0:
version "2.4.2"
resolved "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz#a617983ac0334d0c3b942c34945380062795b47c"
integrity sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==
dependencies:
"@colors/colors" "1.5.0"
fecha "^4.2.0"
ms "^2.1.1"
safe-stable-stringify "^2.3.1"
triple-beam "^1.3.0"
long@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@ -15989,7 +15740,7 @@ moment-timezone@^0.5.31:
dependencies:
moment ">= 2.9.0"
moment@2.x, moment@^2.29.1:
moment@2.x:
version "2.29.4"
resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
@ -16004,11 +15755,6 @@ moo@^0.5.0:
resolved "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4"
integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==
mousetrap@^1.6.5:
version "1.6.5"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9"
integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==
mri@^1.1.5:
version "1.2.0"
resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
@ -16689,11 +16435,6 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-hash@^2.0.1:
version "2.2.0"
resolved "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
object-inspect@^1.11.0, object-inspect@^1.9.0:
version "1.11.0"
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
@ -16814,13 +16555,6 @@ once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
dependencies:
wrappy "1"
one-time@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45"
integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==
dependencies:
fn.name "1.x.x"
onetime@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
@ -19763,7 +19497,7 @@ readable-stream@1.1.x:
isarray "0.0.1"
string_decoder "~0.10.x"
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@^3.6.0:
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@ -20644,11 +20378,6 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
safe-stable-stringify@^2.3.1:
version "2.4.1"
resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz#34694bd8a30575b7f94792aa51527551bd733d61"
integrity sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@ -21337,11 +21066,6 @@ stable@^0.1.8:
resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
stack-trace@0.0.x:
version "0.0.10"
resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==
stack-utils@^1.0.1:
version "1.0.5"
resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz#a19b0b01947e0029c8e451d5d61a498f5bb1471b"
@ -21981,11 +21705,6 @@ text-extensions@^1.0.0:
resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"
integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==
text-hex@1.0.x:
version "1.0.0"
resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@ -22243,11 +21962,6 @@ trim-newlines@^3.0.0:
resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
triple-beam@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
trough@^1.0.0:
version "1.0.5"
resolved "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
@ -23072,11 +22786,6 @@ util@^0.11.0:
dependencies:
inherits "2.0.3"
utility-types@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b"
integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==
utility@0.1.11:
version "0.1.11"
resolved "https://registry.npmjs.org/utility/-/utility-0.1.11.tgz#fde60cf9b4e4751947a0cf5d104ce29367226715"
@ -23469,42 +23178,6 @@ window-size@0.1.0:
resolved "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=
winston-daily-rotate-file@^4.7.1:
version "4.7.1"
resolved "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz#f60a643af87f8867f23170d8cd87dbe3603a625f"
integrity sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==
dependencies:
file-stream-rotator "^0.6.1"
object-hash "^2.0.1"
triple-beam "^1.3.0"
winston-transport "^4.4.0"
winston-transport@^4.4.0, winston-transport@^4.5.0:
version "4.5.0"
resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa"
integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==
dependencies:
logform "^2.3.2"
readable-stream "^3.6.0"
triple-beam "^1.3.0"
winston@^3.8.2:
version "3.8.2"
resolved "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz#56e16b34022eb4cff2638196d9646d7430fdad50"
integrity sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==
dependencies:
"@colors/colors" "1.5.0"
"@dabh/diagnostics" "^2.0.2"
async "^3.2.3"
is-stream "^2.0.0"
logform "^2.4.0"
one-time "^1.0.0"
readable-stream "^3.4.0"
safe-stable-stringify "^2.3.1"
stack-trace "0.0.x"
triple-beam "^1.3.0"
winston-transport "^4.5.0"
wkx@^0.5.0:
version "0.5.0"
resolved "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz#c6c37019acf40e517cc6b94657a25a3d4aa33e8c"