Feat(plugin workflow): cron field for schedule trigger configuration (#495)
* feat(plugin-workflow): add cron field component * refactor(plugin-workflow): break schedule trigger into component files and add locale
This commit is contained in:
parent
3fa13d8465
commit
082e27ff10
@ -31,12 +31,13 @@
|
|||||||
"mathjs": "^10.6.0",
|
"mathjs": "^10.6.0",
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-big-calendar": "^0.38.7",
|
"react-big-calendar": "^0.38.7",
|
||||||
|
"react-contenteditable": "^3.3.6",
|
||||||
"react-drag-listview": "^0.1.9",
|
"react-drag-listview": "^0.1.9",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-i18next": "^11.15.1",
|
"react-i18next": "^11.15.1",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
|
"react-js-cron": "^1.4.0",
|
||||||
"react-quill": "^1.3.5",
|
"react-quill": "^1.3.5",
|
||||||
"react-contenteditable": "^3.3.6",
|
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"slate": "^0.76.1",
|
"slate": "^0.76.1",
|
||||||
"slate-history": "^0.66.0",
|
"slate-history": "^0.66.0",
|
||||||
|
@ -5,11 +5,12 @@ import { Registry } from "@nocobase/utils";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { message, Tag } from "antd";
|
import { message, Tag } from "antd";
|
||||||
|
|
||||||
import { SchemaComponent, useActionContext, useAPIClient, useCompile, useRecord, useRequest, useResourceActionContext } from '../../';
|
import { SchemaComponent, useActionContext, useAPIClient, useCompile, useResourceActionContext } from '../../';
|
||||||
import collection from './collection';
|
|
||||||
import { nodeCardClass, nodeMetaClass } from "../style";
|
import { nodeCardClass, nodeMetaClass } from "../style";
|
||||||
import schedule from "./schedule";
|
|
||||||
import { useFlowContext } from "../WorkflowCanvas";
|
import { useFlowContext } from "../WorkflowCanvas";
|
||||||
|
import collection from './collection';
|
||||||
|
import schedule from "./schedule/";
|
||||||
|
|
||||||
|
|
||||||
function useUpdateConfigAction() {
|
function useUpdateConfigAction() {
|
||||||
|
@ -1,499 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { InputNumber, Select } from 'antd';
|
|
||||||
import { observer, useForm, useFormEffects } from '@formily/react';
|
|
||||||
|
|
||||||
import { useCollectionDataSource, useCollectionManager } from '../../collection-manager';
|
|
||||||
import { SchemaComponent, useCompile, DatePicker } from '../../schema-component';
|
|
||||||
|
|
||||||
import { useFlowContext } from '../WorkflowCanvas';
|
|
||||||
import { BaseTypeSet } from '../calculators';
|
|
||||||
import { collection } from '../schemas/collection';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { onFieldValueChange } from '@formily/core';
|
|
||||||
import { css } from '@emotion/css';
|
|
||||||
|
|
||||||
const DateFieldsSelect: React.FC<any> = observer((props) => {
|
|
||||||
const compile = useCompile();
|
|
||||||
const { getCollectionFields } = useCollectionManager();
|
|
||||||
const { values } = useForm();
|
|
||||||
const fields = getCollectionFields(values?.config?.collection);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select {...props}>
|
|
||||||
{fields
|
|
||||||
.filter(field => (
|
|
||||||
!field.hidden
|
|
||||||
&& (field.uiSchema ? field.type === 'date' : false)
|
|
||||||
))
|
|
||||||
.map(field => (
|
|
||||||
<Select.Option key={field.name} value={field.name}>{compile(field.uiSchema?.title)}</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function OnField({ value, onChange }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [dir, setDir] = useState(value.offset ? value.offset / Math.abs(value.offset) : 0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<fieldset className={css`
|
|
||||||
display: flex;
|
|
||||||
gap: .5em;
|
|
||||||
`}>
|
|
||||||
<DateFieldsSelect value={value.field} onChange={field => onChange({ ...value, field })} />
|
|
||||||
{value.field
|
|
||||||
? (
|
|
||||||
<Select value={dir} onChange={(v) => {
|
|
||||||
setDir(v);
|
|
||||||
onChange({ ...value, offset: Math.abs(value.offset) * v });
|
|
||||||
}}>
|
|
||||||
<Select.Option value={0}>{t('Exactly at')}</Select.Option>
|
|
||||||
<Select.Option value={-1}>{t('Before')}</Select.Option>
|
|
||||||
<Select.Option value={1}>{t('After')}</Select.Option>
|
|
||||||
</Select>
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
{dir
|
|
||||||
? (
|
|
||||||
<>
|
|
||||||
<InputNumber value={Math.abs(value.offset)} onChange={(v) => onChange({ ...value, offset: v * dir })}/>
|
|
||||||
<Select value={value.unit || 86400000} onChange={unit => onChange({ ...value, unit })}>
|
|
||||||
<Select.Option value={86400000}>{t('Days')}</Select.Option>
|
|
||||||
<Select.Option value={3600000}>{t('Hours')}</Select.Option>
|
|
||||||
<Select.Option value={60000}>{t('Minutes')}</Select.Option>
|
|
||||||
<Select.Option value={1000}>{t('Seconds')}</Select.Option>
|
|
||||||
</Select>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
</fieldset>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EndsByField({ value, onChange }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [type, setType] = useState(typeof value === 'object' && !(value instanceof Date) ? 'field' : 'date');
|
|
||||||
return (
|
|
||||||
<fieldset className={css`
|
|
||||||
display: flex;
|
|
||||||
gap: .5em;
|
|
||||||
`}>
|
|
||||||
<Select value={type} onChange={t => {
|
|
||||||
onChange(t === 'field' ? {} : null);
|
|
||||||
setType(t);
|
|
||||||
}}>
|
|
||||||
<Select.Option value={'field'}>{t('By field')}</Select.Option>
|
|
||||||
<Select.Option value={'date'}>{t('By custom date')}</Select.Option>
|
|
||||||
</Select>
|
|
||||||
{type === 'field'
|
|
||||||
? (
|
|
||||||
<OnField value={value} onChange={onChange} />
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<DatePicker showTime value={value} onChange={onChange} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</fieldset>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseCronRule(cron: string) {
|
|
||||||
const rules = cron.split(/\s+/).slice(1).map(v => v.split('/'));
|
|
||||||
let index = rules.findIndex(rule => rule[0] === '*');
|
|
||||||
if (index === -1) {
|
|
||||||
return {
|
|
||||||
mode: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// fix days of week
|
|
||||||
if (index === 3 && rules[4][0] === '*') {
|
|
||||||
index = 4;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
mode: index + 1,
|
|
||||||
step: rules[index][1] ?? 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const CronUnits = [
|
|
||||||
{ value: 1, option: 'By minute', unitText: 'Minutes' },
|
|
||||||
{ value: 2, option: 'By hour', unitText: 'Hours' },
|
|
||||||
{ value: 3, option: 'By date', unitText: 'Days', conflict: true, startFrom: 1 },
|
|
||||||
{ value: 4, option: 'By month', unitText: 'Months', startFrom: 1 },
|
|
||||||
{ value: 5, option: 'By day of week', unitText: 'Days', conflict: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
const RepeatOptions = [
|
|
||||||
{ value: 'none', text: 'No repeat' },
|
|
||||||
{ value: 60_000, text: 'By minute', unitText: 'Minutes' },
|
|
||||||
{ value: 3600_000, text: 'By hour', unitText: 'Hours' },
|
|
||||||
{ value: 86400_000, text: 'By day', unitText: 'Days' },
|
|
||||||
{ value: 604800_000, text: 'By week', unitText: 'Weeks' },
|
|
||||||
// { value: 18144_000_000, text: 'By 30 days' },
|
|
||||||
{ value: 'cron', text: 'Advance', disabled: true }
|
|
||||||
];
|
|
||||||
|
|
||||||
function getNumberOption(v) {
|
|
||||||
const opts = RepeatOptions.filter(option => typeof option.value === 'number').reverse() as any[];
|
|
||||||
return opts.find(item => !(v % item.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRepeatTypeValue(v) {
|
|
||||||
switch (typeof v) {
|
|
||||||
case 'number':
|
|
||||||
const option = getNumberOption(v);
|
|
||||||
return option ? option.value : 'none';
|
|
||||||
case 'string':
|
|
||||||
return 'cron';
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChangedCron({ mode, step }) {
|
|
||||||
const m = mode - 1;
|
|
||||||
const left = [0, ...Array(m).fill(null).map((_, i) => {
|
|
||||||
if (CronUnits[m].conflict && CronUnits[i].conflict) {
|
|
||||||
return '?';
|
|
||||||
}
|
|
||||||
return i === 3 ? '*' : CronUnits[i].startFrom ?? 0;
|
|
||||||
})].join(' ');
|
|
||||||
const right = Array(5 - mode).fill(null).map((_, i) => {
|
|
||||||
if (CronUnits[m].conflict && CronUnits[mode + i].conflict || mode === 4) {
|
|
||||||
return '?';
|
|
||||||
}
|
|
||||||
return '*';
|
|
||||||
}).join(' ');
|
|
||||||
return `${left} ${!step || step == 1 ? '*' : `*/${step}`}${right ? ` ${right}` : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CronField({ value, onChange }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const cron = parseCronRule(value);
|
|
||||||
const unit = CronUnits[cron.mode - 1];
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<InputNumber
|
|
||||||
value={cron.step}
|
|
||||||
onChange={v => onChange(getChangedCron({ step: v, mode: cron.mode }))}
|
|
||||||
min={1}
|
|
||||||
addonBefore={t('Every')}
|
|
||||||
addonAfter={t(unit.unitText)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommonRepeatField({ value, onChange }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const option = getNumberOption(value);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputNumber
|
|
||||||
value={value / option.value}
|
|
||||||
onChange={v => onChange(v * option.value)}
|
|
||||||
min={1}
|
|
||||||
addonBefore={t('Every')}
|
|
||||||
addonAfter={t(option.unitText)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RepeatField({ value = null, onChange }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const typeValue = getRepeatTypeValue(value);
|
|
||||||
function onTypeChange(v) {
|
|
||||||
if (v === 'none') {
|
|
||||||
onChange(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (v === 'cron') {
|
|
||||||
onChange('0 * * * * *');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onChange(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<fieldset className={css`
|
|
||||||
display: flex;
|
|
||||||
gap: .5em;
|
|
||||||
`}>
|
|
||||||
<Select
|
|
||||||
value={typeValue}
|
|
||||||
onChange={onTypeChange}
|
|
||||||
>
|
|
||||||
{RepeatOptions.map(item => (
|
|
||||||
<Select.Option
|
|
||||||
key={item.value}
|
|
||||||
value={item.value}
|
|
||||||
disabled={item.disabled}
|
|
||||||
>
|
|
||||||
{t(item.text)}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{typeof typeValue === 'number'
|
|
||||||
? <CommonRepeatField value={value} onChange={onChange} />
|
|
||||||
: null}
|
|
||||||
{typeValue === 'cron'
|
|
||||||
? <CronField value={value} onChange={onChange} />
|
|
||||||
: null}
|
|
||||||
</fieldset>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ModeFieldsets = {
|
|
||||||
0: {
|
|
||||||
startsOn: {
|
|
||||||
type: 'datetime',
|
|
||||||
name: 'startsOn',
|
|
||||||
title: '{{t("Starts on")}}',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'DatePicker',
|
|
||||||
'x-component-props': {
|
|
||||||
showTime: true
|
|
||||||
},
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
repeat: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'repeat',
|
|
||||||
title: '{{t("Repeat mode")}}',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'RepeatField',
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
target: 'config.endsOn',
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: '{{!!$self.value}}',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: 'config.limit',
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: '{{!!$self.value}}',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
endsOn: {
|
|
||||||
type: 'datetime',
|
|
||||||
name: 'endsOn',
|
|
||||||
title: '{{t("Ends on")}}',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'DatePicker',
|
|
||||||
'x-component-props': {
|
|
||||||
showTime: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
type: 'number',
|
|
||||||
name: 'limit',
|
|
||||||
title: '{{t("Repeat limit")}}',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'InputNumber',
|
|
||||||
'x-component-props': {
|
|
||||||
placeholder: '{{t("No limit")}}',
|
|
||||||
min: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
1: {
|
|
||||||
collection: {
|
|
||||||
...collection,
|
|
||||||
'x-reactions': [
|
|
||||||
...collection['x-reactions'],
|
|
||||||
{
|
|
||||||
// only full path works
|
|
||||||
target: 'config.startsOn',
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: '{{!!$self.value}}',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
startsOn: {
|
|
||||||
type: 'object',
|
|
||||||
title: '{{t("Starts on")}}',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'OnField',
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
target: 'config.repeat',
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: '{{!!$self.value}}',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
repeat: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'repeat',
|
|
||||||
title: '{{t("Repeat mode")}}',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'RepeatField',
|
|
||||||
'x-reactions': [
|
|
||||||
{
|
|
||||||
target: 'config.endsOn',
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: '{{!!$self.value}}',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
target: 'config.limit',
|
|
||||||
fulfill: {
|
|
||||||
state: {
|
|
||||||
visible: '{{!!$self.value}}',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
endsOn: {
|
|
||||||
type: 'object',
|
|
||||||
title: '{{t("Ends on")}}',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'EndsByField'
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
type: 'number',
|
|
||||||
name: 'limit',
|
|
||||||
title: '{{t("Repeat limit")}}',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'InputNumber',
|
|
||||||
'x-component-props': {
|
|
||||||
placeholder: '{{t("No limit")}}',
|
|
||||||
min: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ScheduleConfig = () => {
|
|
||||||
const { values = {}, clearFormGraph } = useForm();
|
|
||||||
const { config = {} } = values;
|
|
||||||
const [mode, setMode] = useState(config.mode);
|
|
||||||
useFormEffects(() => {
|
|
||||||
onFieldValueChange('config.mode', (field) => {
|
|
||||||
setMode(field.value);
|
|
||||||
clearFormGraph('config.collection');
|
|
||||||
clearFormGraph('config.startsOn');
|
|
||||||
clearFormGraph('config.repeat');
|
|
||||||
clearFormGraph('config.endsOn');
|
|
||||||
clearFormGraph('config.limit');
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SchemaComponent
|
|
||||||
schema={{
|
|
||||||
type: 'number',
|
|
||||||
title: '{{t("Trigger mode")}}',
|
|
||||||
name: 'mode',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Radio.Group',
|
|
||||||
'x-component-props': {
|
|
||||||
options: [
|
|
||||||
{ value: 0, label: '{{t("Based on certain date")}}' },
|
|
||||||
{ value: 1, label: '{{t("Based on date field of collection")}}' },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
required: true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SchemaComponent
|
|
||||||
schema={{
|
|
||||||
type: 'void',
|
|
||||||
properties: {
|
|
||||||
[`mode-${mode}`]: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'fieldset',
|
|
||||||
'x-component-props': {
|
|
||||||
className: css`
|
|
||||||
.ant-select{
|
|
||||||
width: auto;
|
|
||||||
min-width: 6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-input-number{
|
|
||||||
width: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-picker{
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
},
|
|
||||||
properties: ModeFieldsets[mode]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
components={{
|
|
||||||
DateFieldsSelect,
|
|
||||||
OnField,
|
|
||||||
RepeatField,
|
|
||||||
EndsByField
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: '{{t("Schedule event")}}',
|
|
||||||
type: 'schedule',
|
|
||||||
fieldset: {
|
|
||||||
config: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'config',
|
|
||||||
'x-component': 'ScheduleConfig',
|
|
||||||
'x-component-props': {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scope: {
|
|
||||||
useCollectionDataSource
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
// FieldsSelect
|
|
||||||
ScheduleConfig
|
|
||||||
},
|
|
||||||
getter({ type, options, onChange }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const compile = useCompile();
|
|
||||||
const { collections = [] } = useCollectionManager();
|
|
||||||
const { workflow } = useFlowContext();
|
|
||||||
const collection = collections.find(item => item.name === workflow.config.collection) ?? { fields: [] };
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
placeholder={t('Fields')}
|
|
||||||
value={options?.path?.replace(/^data\./, '')}
|
|
||||||
onChange={(path) => {
|
|
||||||
onChange({ type, options: { ...options, path: `data.${path}` } });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{collection.fields
|
|
||||||
.filter(field => BaseTypeSet.has(field?.uiSchema?.type))
|
|
||||||
.map(field => (
|
|
||||||
<Select.Option key={field.name} value={field.name}>{compile(field.uiSchema.title)}</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
@ -0,0 +1,25 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { observer, useForm } from "@formily/react";
|
||||||
|
import { Select } from "antd";
|
||||||
|
|
||||||
|
import { useCollectionManager, useCompile } from "@nocobase/client";
|
||||||
|
|
||||||
|
export const DateFieldsSelect: React.FC<any> = observer((props) => {
|
||||||
|
const compile = useCompile();
|
||||||
|
const { getCollectionFields } = useCollectionManager();
|
||||||
|
const { values } = useForm();
|
||||||
|
const fields = getCollectionFields(values?.config?.collection);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select {...props}>
|
||||||
|
{fields
|
||||||
|
.filter(field => (
|
||||||
|
!field.hidden
|
||||||
|
&& (field.uiSchema ? field.type === 'date' : false)
|
||||||
|
))
|
||||||
|
.map(field => (
|
||||||
|
<Select.Option key={field.name} value={field.name}>{compile(field.uiSchema?.title)}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,35 @@
|
|||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { DatePicker, Select } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { OnField } from "./OnField";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function EndsByField({ value, onChange }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [type, setType] = useState(typeof value === 'object' && !(value instanceof Date) ? 'field' : 'date');
|
||||||
|
return (
|
||||||
|
<fieldset className={css`
|
||||||
|
display: flex;
|
||||||
|
gap: .5em;
|
||||||
|
`}>
|
||||||
|
<Select value={type} onChange={t => {
|
||||||
|
onChange(t === 'field' ? {} : null);
|
||||||
|
setType(t);
|
||||||
|
}}>
|
||||||
|
<Select.Option value={'field'}>{t('By field')}</Select.Option>
|
||||||
|
<Select.Option value={'date'}>{t('By custom date')}</Select.Option>
|
||||||
|
</Select>
|
||||||
|
{type === 'field'
|
||||||
|
? (
|
||||||
|
<OnField value={value} onChange={onChange} />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<DatePicker showTime value={value} onChange={onChange} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { InputNumber, Select } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { DateFieldsSelect } from "./DateFieldsSelect";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function OnField({ value, onChange }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [dir, setDir] = useState(value.offset ? value.offset / Math.abs(value.offset) : 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<fieldset className={css`
|
||||||
|
display: flex;
|
||||||
|
gap: .5em;
|
||||||
|
`}>
|
||||||
|
<DateFieldsSelect value={value.field} onChange={field => onChange({ ...value, field })} />
|
||||||
|
{value.field
|
||||||
|
? (
|
||||||
|
<Select value={dir} onChange={(v) => {
|
||||||
|
setDir(v);
|
||||||
|
onChange({ ...value, offset: Math.abs(value.offset) * v });
|
||||||
|
}}>
|
||||||
|
<Select.Option value={0}>{t('Exactly at')}</Select.Option>
|
||||||
|
<Select.Option value={-1}>{t('Before')}</Select.Option>
|
||||||
|
<Select.Option value={1}>{t('After')}</Select.Option>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
{dir
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
<InputNumber value={Math.abs(value.offset)} onChange={(v) => onChange({ ...value, offset: v * dir })}/>
|
||||||
|
<Select value={value.unit || 86400000} onChange={unit => onChange({ ...value, unit })}>
|
||||||
|
<Select.Option value={86400000}>{t('Days')}</Select.Option>
|
||||||
|
<Select.Option value={3600000}>{t('Hours')}</Select.Option>
|
||||||
|
<Select.Option value={60000}>{t('Minutes')}</Select.Option>
|
||||||
|
<Select.Option value={1000}>{t('Seconds')}</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { InputNumber, Select } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Cron } from 'react-js-cron';
|
||||||
|
|
||||||
|
import CronZhCN from './locale/Cron.zh-CN';
|
||||||
|
|
||||||
|
const languages = {
|
||||||
|
'zh-CN': CronZhCN,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const RepeatOptions = [
|
||||||
|
{ value: 'none', text: 'No repeat' },
|
||||||
|
{ value: 60_000, text: 'By minute', unitText: 'Minutes' },
|
||||||
|
{ value: 3600_000, text: 'By hour', unitText: 'Hours' },
|
||||||
|
{ value: 86400_000, text: 'By day', unitText: 'Days' },
|
||||||
|
{ value: 604800_000, text: 'By week', unitText: 'Weeks' },
|
||||||
|
// { value: 18144_000_000, text: 'By 30 days' },
|
||||||
|
{ value: 'cron', text: 'Advance' }
|
||||||
|
];
|
||||||
|
|
||||||
|
function getNumberOption(v) {
|
||||||
|
const opts = RepeatOptions.filter(option => typeof option.value === 'number').reverse() as any[];
|
||||||
|
return opts.find(item => !(v % item.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRepeatTypeValue(v) {
|
||||||
|
switch (typeof v) {
|
||||||
|
case 'number':
|
||||||
|
const option = getNumberOption(v);
|
||||||
|
return option ? option.value : 'none';
|
||||||
|
case 'string':
|
||||||
|
return 'cron';
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function CommonRepeatField({ value, onChange }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const option = getNumberOption(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputNumber
|
||||||
|
value={value / option.value}
|
||||||
|
onChange={v => onChange(v * option.value)}
|
||||||
|
min={1}
|
||||||
|
addonBefore={t('Every')}
|
||||||
|
addonAfter={t(option.unitText)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RepeatField({ value = null, onChange }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const typeValue = getRepeatTypeValue(value);
|
||||||
|
function onTypeChange(v) {
|
||||||
|
if (v === 'none') {
|
||||||
|
onChange(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (v === 'cron') {
|
||||||
|
onChange('0 * * * * *');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChange(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
const locale = languages[localStorage.getItem('NOCOBASE_LOCALE') || 'en-US'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<fieldset className={css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: ${typeValue === 'cron' ? 'column' : 'row'};
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: .5em;
|
||||||
|
|
||||||
|
.react-js-cron{
|
||||||
|
padding: .5em .5em 0 .5em;
|
||||||
|
border: 1px dashed #ccc;
|
||||||
|
|
||||||
|
.react-js-cron-field{
|
||||||
|
margin-bottom: .5em;
|
||||||
|
|
||||||
|
> span{
|
||||||
|
margin: 0 .5em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .react-js-cron-select{
|
||||||
|
margin: 0 .5em 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}>
|
||||||
|
<Select
|
||||||
|
value={typeValue}
|
||||||
|
onChange={onTypeChange}
|
||||||
|
>
|
||||||
|
{RepeatOptions.map(item => (
|
||||||
|
<Select.Option
|
||||||
|
key={item.value}
|
||||||
|
value={item.value}
|
||||||
|
>
|
||||||
|
{t(item.text)}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{typeof typeValue === 'number'
|
||||||
|
? <CommonRepeatField value={value} onChange={onChange} />
|
||||||
|
: null}
|
||||||
|
{typeValue === 'cron'
|
||||||
|
? (
|
||||||
|
<Cron
|
||||||
|
value={value.trim().split(/\s+/).slice(1).join(' ')}
|
||||||
|
setValue={v => onChange(`0 ${v}`)}
|
||||||
|
clearButton={false}
|
||||||
|
locale={locale}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,221 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { onFieldValueChange } from '@formily/core';
|
||||||
|
import { useForm, useFormEffects } from '@formily/react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
import { SchemaComponent } from '@nocobase/client';
|
||||||
|
|
||||||
|
import { collection } from '../../schemas/collection';
|
||||||
|
// import { DateFieldsSelect } from './DateFieldsSelect';
|
||||||
|
import { OnField } from './OnField';
|
||||||
|
import { EndsByField } from './EndsByField';
|
||||||
|
import { RepeatField } from './RepeatField';
|
||||||
|
|
||||||
|
const ModeFieldsets = {
|
||||||
|
0: {
|
||||||
|
startsOn: {
|
||||||
|
type: 'datetime',
|
||||||
|
name: 'startsOn',
|
||||||
|
title: '{{t("Starts on")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
showTime: true
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
repeat: {
|
||||||
|
type: 'string',
|
||||||
|
name: 'repeat',
|
||||||
|
title: '{{t("Repeat mode")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'RepeatField',
|
||||||
|
'x-reactions': [
|
||||||
|
{
|
||||||
|
target: 'config.endsOn',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{!!$self.value}}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'config.limit',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{!!$self.value}}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
endsOn: {
|
||||||
|
type: 'datetime',
|
||||||
|
name: 'endsOn',
|
||||||
|
title: '{{t("Ends on")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {
|
||||||
|
showTime: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
name: 'limit',
|
||||||
|
title: '{{t("Repeat limit")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '{{t("No limit")}}',
|
||||||
|
min: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
collection: {
|
||||||
|
...collection,
|
||||||
|
'x-reactions': [
|
||||||
|
...collection['x-reactions'],
|
||||||
|
{
|
||||||
|
// only full path works
|
||||||
|
target: 'config.startsOn',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{!!$self.value}}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
startsOn: {
|
||||||
|
type: 'object',
|
||||||
|
title: '{{t("Starts on")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'OnField',
|
||||||
|
'x-reactions': [
|
||||||
|
{
|
||||||
|
target: 'config.repeat',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{!!$self.value}}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
repeat: {
|
||||||
|
type: 'string',
|
||||||
|
name: 'repeat',
|
||||||
|
title: '{{t("Repeat mode")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'RepeatField',
|
||||||
|
'x-reactions': [
|
||||||
|
{
|
||||||
|
target: 'config.endsOn',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{!!$self.value}}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'config.limit',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{!!$self.value}}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
endsOn: {
|
||||||
|
type: 'object',
|
||||||
|
title: '{{t("Ends on")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'EndsByField'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
name: 'limit',
|
||||||
|
title: '{{t("Repeat limit")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '{{t("No limit")}}',
|
||||||
|
min: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ScheduleConfig = () => {
|
||||||
|
const { values = {}, clearFormGraph } = useForm();
|
||||||
|
const { config = {} } = values;
|
||||||
|
const [mode, setMode] = useState(config.mode);
|
||||||
|
useFormEffects(() => {
|
||||||
|
onFieldValueChange('config.mode', (field) => {
|
||||||
|
setMode(field.value);
|
||||||
|
clearFormGraph('config.collection');
|
||||||
|
clearFormGraph('config.startsOn');
|
||||||
|
clearFormGraph('config.repeat');
|
||||||
|
clearFormGraph('config.endsOn');
|
||||||
|
clearFormGraph('config.limit');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={{
|
||||||
|
type: 'number',
|
||||||
|
title: '{{t("Trigger mode")}}',
|
||||||
|
name: 'mode',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {
|
||||||
|
options: [
|
||||||
|
{ value: 0, label: '{{t("Based on certain date")}}' },
|
||||||
|
{ value: 1, label: '{{t("Based on date field of collection")}}' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={{
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
[`mode-${mode}`]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'fieldset',
|
||||||
|
'x-component-props': {
|
||||||
|
className: css`
|
||||||
|
.ant-select{
|
||||||
|
width: auto;
|
||||||
|
min-width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-number{
|
||||||
|
width: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-picker{
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
properties: ModeFieldsets[mode]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
// DateFieldsSelect,
|
||||||
|
OnField,
|
||||||
|
RepeatField,
|
||||||
|
EndsByField
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Select } from 'antd';
|
||||||
|
|
||||||
|
import { useCompile, useCollectionDataSource, useCollectionManager } from '@nocobase/client';
|
||||||
|
|
||||||
|
import { ScheduleConfig } from './ScheduleConfig';
|
||||||
|
import { useFlowContext } from '../../WorkflowCanvas';
|
||||||
|
import { BaseTypeSet } from '../../calculators';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: '{{t("Schedule event")}}',
|
||||||
|
type: 'schedule',
|
||||||
|
fieldset: {
|
||||||
|
config: {
|
||||||
|
type: 'object',
|
||||||
|
name: 'config',
|
||||||
|
'x-component': 'ScheduleConfig',
|
||||||
|
'x-component-props': {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scope: {
|
||||||
|
useCollectionDataSource
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
// FieldsSelect
|
||||||
|
ScheduleConfig
|
||||||
|
},
|
||||||
|
getter({ type, options, onChange }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { collections = [] } = useCollectionManager();
|
||||||
|
const { workflow } = useFlowContext();
|
||||||
|
const collection = collections.find(item => item.name === workflow.config.collection) ?? { fields: [] };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
placeholder={t('Fields')}
|
||||||
|
value={options?.path?.replace(/^data\./, '')}
|
||||||
|
onChange={(path) => {
|
||||||
|
onChange({ type, options: { ...options, path: `data.${path}` } });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{collection.fields
|
||||||
|
.filter(field => BaseTypeSet.has(field?.uiSchema?.type))
|
||||||
|
.map(field => (
|
||||||
|
<Select.Option key={field.name} value={field.name}>{compile(field.uiSchema.title)}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,79 @@
|
|||||||
|
export default {
|
||||||
|
everyText: '每',
|
||||||
|
emptyMonths: '每月',
|
||||||
|
emptyMonthDays: '每日(月)',
|
||||||
|
emptyMonthDaysShort: '每日',
|
||||||
|
emptyWeekDays: '每天(周)',
|
||||||
|
emptyWeekDaysShort: '每天(周)',
|
||||||
|
emptyHours: '每小时',
|
||||||
|
emptyMinutes: '每分钟',
|
||||||
|
emptyMinutesForHourPeriod: '每',
|
||||||
|
yearOption: '年',
|
||||||
|
monthOption: '月',
|
||||||
|
weekOption: '周',
|
||||||
|
dayOption: '天',
|
||||||
|
hourOption: '小时',
|
||||||
|
minuteOption: '分钟',
|
||||||
|
rebootOption: '重启',
|
||||||
|
prefixPeriod: '每',
|
||||||
|
prefixMonths: '的',
|
||||||
|
prefixMonthDays: '的',
|
||||||
|
prefixWeekDays: '的',
|
||||||
|
prefixWeekDaysForMonthAndYearPeriod: '和',
|
||||||
|
prefixHours: '的',
|
||||||
|
prefixMinutes: ':',
|
||||||
|
prefixMinutesForHourPeriod: '的',
|
||||||
|
suffixMinutesForHourPeriod: '分钟',
|
||||||
|
errorInvalidCron: '不符合 cron 规则的表达式',
|
||||||
|
clearButtonText: '清空',
|
||||||
|
weekDays: [
|
||||||
|
// Order is important, the index will be used as value
|
||||||
|
'周日', // Sunday must always be first, it's "0"
|
||||||
|
'周一',
|
||||||
|
'周二',
|
||||||
|
'周三',
|
||||||
|
'周四',
|
||||||
|
'周五',
|
||||||
|
'周六',
|
||||||
|
],
|
||||||
|
months: [
|
||||||
|
// Order is important, the index will be used as value
|
||||||
|
'一月',
|
||||||
|
'二月',
|
||||||
|
'三月',
|
||||||
|
'四月',
|
||||||
|
'五月',
|
||||||
|
'六月',
|
||||||
|
'七月',
|
||||||
|
'八月',
|
||||||
|
'九月',
|
||||||
|
'十月',
|
||||||
|
'十一月',
|
||||||
|
'十二月',
|
||||||
|
],
|
||||||
|
altWeekDays: [
|
||||||
|
// Order is important, the index will be used as value
|
||||||
|
'周日', // Sunday must always be first, it's "0"
|
||||||
|
'周一',
|
||||||
|
'周二',
|
||||||
|
'周三',
|
||||||
|
'周四',
|
||||||
|
'周五',
|
||||||
|
'周六',
|
||||||
|
],
|
||||||
|
altMonths: [
|
||||||
|
// Order is important, the index will be used as value
|
||||||
|
'一月',
|
||||||
|
'二月',
|
||||||
|
'三月',
|
||||||
|
'四月',
|
||||||
|
'五月',
|
||||||
|
'六月',
|
||||||
|
'七月',
|
||||||
|
'八月',
|
||||||
|
'九月',
|
||||||
|
'十月',
|
||||||
|
'十一月',
|
||||||
|
'十二月',
|
||||||
|
],
|
||||||
|
}
|
25
yarn.lock
25
yarn.lock
@ -5234,7 +5234,14 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||||
|
|
||||||
"@types/react-dom@^16.9.8", "@types/react-dom@^17.0.0":
|
"@types/react-dom@^16.9.8":
|
||||||
|
version "16.9.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.16.tgz#c591f2ed1c6f32e9759dfa6eb4abfd8041f29e39"
|
||||||
|
integrity sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "^16"
|
||||||
|
|
||||||
|
"@types/react-dom@^17.0.0":
|
||||||
version "17.0.11"
|
version "17.0.11"
|
||||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
|
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
|
||||||
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
|
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
|
||||||
@ -5294,7 +5301,7 @@
|
|||||||
"@types/history" "*"
|
"@types/history" "*"
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^16.9.43", "@types/react@^17.0.0":
|
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^17.0.0":
|
||||||
version "17.0.34"
|
version "17.0.34"
|
||||||
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
|
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
|
||||||
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
|
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
|
||||||
@ -5303,6 +5310,15 @@
|
|||||||
"@types/scheduler" "*"
|
"@types/scheduler" "*"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
|
"@types/react@^16", "@types/react@^16.9.43":
|
||||||
|
version "16.14.26"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.26.tgz#82540a240ba7207ebe87d9579051bc19c9ef7605"
|
||||||
|
integrity sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
"@types/scheduler" "*"
|
||||||
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/resolve@1.17.1":
|
"@types/resolve@1.17.1":
|
||||||
version "1.17.1"
|
version "1.17.1"
|
||||||
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
||||||
@ -18729,6 +18745,11 @@ react-is@^17.0.1, react-is@^17.0.2:
|
|||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||||
|
|
||||||
|
react-js-cron@^1.4.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-js-cron/-/react-js-cron-1.4.0.tgz#58341434e3db00da11bc8009abb0ea6cd926cda7"
|
||||||
|
integrity sha512-gbx1NXMDJgiKBnn1ljTS/jIXBw+jAEDMFOW/kWeGXuLwV+HEDIfj2oIsgStGGzCjRgcjgM3UTtHxaWrBdo0lFQ==
|
||||||
|
|
||||||
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
|
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
|
Loading…
Reference in New Issue
Block a user