From 62a0ce2fe582eeddf59bb29ac6ce5053e3d9af18 Mon Sep 17 00:00:00 2001 From: Miguel Garcia Garcia Date: Tue, 17 Oct 2023 15:19:39 +0200 Subject: [PATCH] ui: remove form builder components Signed-off-by: Miguel Garcia Garcia --- .prettierignore | 4 +- ui/cap-react/.gitignore | 3 + ui/cap-react/package.json | 15 +- ui/cap-react/src/actions/builder.js | 287 + ui/cap-react/src/actions/schemaWizard.js | 678 -- ui/cap-react/src/antd/App/App.js | 50 +- ui/cap-react/src/antd/App/AppContainer.js | 5 + .../src/antd/admin/components/AdminIndex.js | 4 +- .../src/antd/admin/components/AdminPanel.js | 39 +- .../src/antd/admin/components/CreateForm.js | 21 +- .../src/antd/admin/components/Customize.js | 146 - .../src/antd/admin/components/Draggable.js | 33 - .../src/antd/admin/components/DropZoneForm.js | 17 +- .../src/antd/admin/components/FormPreview.js | 63 - .../src/antd/admin/components/Header.js | 37 +- .../admin/components/PropKeyEditorForm.js | 71 - .../antd/admin/components/PropertyEditor.js | 113 - .../antd/admin/components/SchemaPreview.js | 51 - .../src/antd/admin/components/SchemaTree.js | 31 - .../src/antd/admin/components/SchemaWizard.js | 83 +- .../admin/components/SelectContentType.js | 12 +- .../antd/admin/components/SelectFieldType.js | 43 - .../src/antd/admin/containers/AdminPanel.js | 29 +- .../src/antd/admin/containers/CreateForm.js | 14 - .../src/antd/admin/containers/Customize.js | 33 - .../src/antd/admin/containers/DropZoneForm.js | 14 - .../src/antd/admin/containers/FormPreview.js | 12 - .../src/antd/admin/containers/Header.js | 14 +- .../antd/admin/containers/PropertyEditor.js | 29 - .../antd/admin/containers/SchemaPreview.js | 22 - .../src/antd/admin/containers/SchemaTree.js | 12 - .../src/antd/admin/containers/SchemaWizard.js | 15 - .../admin/containers/SelectContentType.js | 13 +- .../antd/admin/containers/SelectFieldType.js | 30 - .../formComponents/ArrayFieldTemplate.js | 90 - .../src/antd/admin/formComponents/DropArea.js | 20 - .../admin/formComponents/FieldTemplate.js | 140 - .../src/antd/admin/formComponents/HoverBox.js | 64 - .../formComponents/ObjectFieldTemplate.js | 155 - .../admin/formComponents/RenderSortable.js | 21 - .../admin/formComponents/SchemaTreeItem.js | 177 - .../antd/admin/formComponents/SortableBox.js | 88 - .../formComponents/widgets/SliderWidget.js | 41 - .../formComponents/widgets/TextWidget.js | 20 - .../admin/formComponents/widgets/index.js | 7 - .../components/NotificationEdit.js | 4 +- .../notifications/components/Notifications.js | 1 - .../containers/NotificationEdit.js | 8 +- .../containers/NotificationList.js | 8 +- .../notifications/containers/Notifications.js | 10 +- .../antd/admin/permissions/AddPermissions.js | 21 +- .../src/antd/admin/permissions/Permissions.js | 249 +- .../src/antd/admin/utils/fieldTypes.js | 1179 --- ui/cap-react/src/antd/admin/utils/index.js | 75 +- .../src/antd/admin/utils/tour/admin.js | 8 +- .../src/antd/collection/Collection.js | 6 +- .../collection/CollectionPermissionsColumn.js | 21 +- .../components/DraftItemNav/DraftItemNav.js | 2 +- .../antd/drafts/components/Editor/Editor.js | 13 +- .../drafts/components/Overview/Overview.js | 1 - .../drafts/components/SideBar/DraftSideBar.js | 2 +- .../antd/drafts/components/SideBar/SideBar.js | 51 +- .../src/antd/drafts/containers/Editor.js | 18 +- ui/cap-react/src/antd/forms/Form.js | 117 - ui/cap-react/src/antd/forms/Form.less | 110 - .../{widgets => }/RichEditorPreviewPlugin.js | 0 .../forms/{widgets => }/RichEditorWidget.js | 0 .../{fields => customFields}/CapFiles.js | 0 .../CernUsers.js} | 0 .../IdFetcher.js} | 27 +- .../ImportDataField.js | 0 .../SchemaPathSuggester.js | 15 +- .../services/CAPDeposit.js | 0 .../services/Orcid.js | 0 .../{fields => customFields}/services/Ror.js | 0 .../services/Zenodo.js | 0 .../forms/customFields/services/svg/CapSvg.js | 86 + .../services/svg/OrcidSvg.js | 0 .../services/svg/RorSvg.js | 0 .../services/svg/ZenodoSvg.js | 0 .../services/svg/capLogo.js | 0 .../antd/forms/error/ErrorFieldIndicator.js | 20 - .../src/antd/forms/fields/TagsField.js | 128 - .../fields/containers/SchemaPathSuggester.js | 10 - ui/cap-react/src/antd/forms/fields/index.js | 17 - .../antd/forms/fields/internal/TitleField.js | 132 - ui/cap-react/src/antd/forms/formuleConfig.js | 207 + ui/cap-react/src/antd/forms/index.js | 1 - .../AccordionArrayFieldTemplate.js | 37 - .../ArrayFieldTemplateItem.js | 69 - .../ArrayFieldTemplates/ArrayUtils.js | 73 - .../ArrayFieldTemplates/EmptyArrayField.js | 44 - .../FixedArrayFieldTemplate.js | 87 - .../ArrayFieldTemplates/ImportListModal.js | 219 - .../LayerArrayFieldTemplate.js | 164 - .../NormalArrayFieldTemplate.js | 381 - .../templates/ArrayFieldTemplates/index.js | 185 - .../antd/forms/templates/Field/FieldHeader.js | 46 - .../forms/templates/Field/FieldTemplate.js | 127 - .../forms/templates/Field/WrapIfAdditional.js | 116 - .../forms/templates/ObjectFieldTemplate.js | 146 - .../src/antd/forms/templates/TabField.js | 161 - .../src/antd/forms/templates/TabFieldMenu.js | 69 - .../antd/forms/templates/utils/tabfield.js | 41 - .../src/antd/forms/widgets/CheckboxWidget.js | 73 - .../src/antd/forms/widgets/DateWidget.js | 75 - .../forms/widgets/MaskedInput/MaskedInput.js | 73 - .../antd/forms/widgets/MaskedInput/index.js | 1 - .../src/antd/forms/widgets/RequiredWidget.js | 16 - .../src/antd/forms/widgets/SelectWidget.js | 177 - .../src/antd/forms/widgets/SwitchWidget.js | 48 - .../src/antd/forms/widgets/TextWidget.js | 214 - .../src/antd/forms/widgets/UriWidget.js | 58 - .../widgets/containers/RequiredWidget.js | 13 - ui/cap-react/src/antd/forms/widgets/index.js | 22 - .../partials/FileList/components/FileModal.js | 1 + .../partials/FileList/components/Files.js | 4 +- .../src/antd/partials/Header/Header.js | 2 +- .../JSONSchemaPreviewer.js | 6 +- .../src/antd/partials/Markdown/Markdown.js | 21 - .../src/antd/partials/Markdown/index.js | 1 - .../src/antd/partials/Markdown/marked.js | 31 - .../src/antd/partials/Utils/schema.js | 5 +- .../src/antd/published/components/Preview.js | 1 - .../src/antd/published/components/SideBar.js | 2 +- .../src/antd/schemas/components/Schemas.js | 4 +- ui/cap-react/src/antd/utils/index.js | 2 - .../src/antd/welcome/Contact/Contact.js | 8 +- .../antd/welcome/Integrations/Integrations.js | 4 +- ui/cap-react/src/antd/welcome/Intro/Intro.js | 2 +- ui/cap-react/src/reducers/builder.js | 42 + ui/cap-react/src/reducers/index.js | 6 +- ui/cap-react/src/reducers/schemaWizard.js | 114 - ui/cap-react/src/style.less | 3 +- ui/cap-react/vite.config.js | 5 + ui/yarn.lock | 7759 +++++++++-------- 136 files changed, 5052 insertions(+), 11119 deletions(-) create mode 100644 ui/cap-react/src/actions/builder.js delete mode 100644 ui/cap-react/src/actions/schemaWizard.js delete mode 100644 ui/cap-react/src/antd/admin/components/Customize.js delete mode 100644 ui/cap-react/src/antd/admin/components/Draggable.js delete mode 100644 ui/cap-react/src/antd/admin/components/FormPreview.js delete mode 100644 ui/cap-react/src/antd/admin/components/PropKeyEditorForm.js delete mode 100644 ui/cap-react/src/antd/admin/components/PropertyEditor.js delete mode 100644 ui/cap-react/src/antd/admin/components/SchemaPreview.js delete mode 100644 ui/cap-react/src/antd/admin/components/SchemaTree.js delete mode 100644 ui/cap-react/src/antd/admin/components/SelectFieldType.js delete mode 100644 ui/cap-react/src/antd/admin/containers/CreateForm.js delete mode 100644 ui/cap-react/src/antd/admin/containers/Customize.js delete mode 100644 ui/cap-react/src/antd/admin/containers/DropZoneForm.js delete mode 100644 ui/cap-react/src/antd/admin/containers/FormPreview.js delete mode 100644 ui/cap-react/src/antd/admin/containers/PropertyEditor.js delete mode 100644 ui/cap-react/src/antd/admin/containers/SchemaPreview.js delete mode 100644 ui/cap-react/src/antd/admin/containers/SchemaTree.js delete mode 100644 ui/cap-react/src/antd/admin/containers/SchemaWizard.js delete mode 100644 ui/cap-react/src/antd/admin/containers/SelectFieldType.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/ArrayFieldTemplate.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/DropArea.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/FieldTemplate.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/HoverBox.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/ObjectFieldTemplate.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/RenderSortable.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/SchemaTreeItem.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/SortableBox.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/widgets/SliderWidget.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/widgets/TextWidget.js delete mode 100644 ui/cap-react/src/antd/admin/formComponents/widgets/index.js delete mode 100644 ui/cap-react/src/antd/admin/utils/fieldTypes.js delete mode 100644 ui/cap-react/src/antd/forms/Form.js delete mode 100644 ui/cap-react/src/antd/forms/Form.less rename ui/cap-react/src/antd/forms/{widgets => }/RichEditorPreviewPlugin.js (100%) rename ui/cap-react/src/antd/forms/{widgets => }/RichEditorWidget.js (100%) rename ui/cap-react/src/antd/forms/{fields => customFields}/CapFiles.js (100%) rename ui/cap-react/src/antd/forms/{fields/cernUsers.js => customFields/CernUsers.js} (100%) rename ui/cap-react/src/antd/forms/{fields/ServiceGetter.js => customFields/IdFetcher.js} (93%) rename ui/cap-react/src/antd/forms/{fields => customFields}/ImportDataField.js (100%) rename ui/cap-react/src/antd/forms/{fields => customFields}/SchemaPathSuggester.js (78%) rename ui/cap-react/src/antd/forms/{fields => customFields}/services/CAPDeposit.js (100%) rename ui/cap-react/src/antd/forms/{fields => customFields}/services/Orcid.js (100%) rename ui/cap-react/src/antd/forms/{fields => customFields}/services/Ror.js (100%) rename ui/cap-react/src/antd/forms/{fields => customFields}/services/Zenodo.js (100%) create mode 100644 ui/cap-react/src/antd/forms/customFields/services/svg/CapSvg.js rename ui/cap-react/src/antd/forms/{fields => customFields}/services/svg/OrcidSvg.js (100%) rename ui/cap-react/src/antd/forms/{fields => customFields}/services/svg/RorSvg.js (100%) rename ui/cap-react/src/antd/forms/{fields => customFields}/services/svg/ZenodoSvg.js (100%) rename ui/cap-react/src/antd/forms/{fields => customFields}/services/svg/capLogo.js (100%) delete mode 100644 ui/cap-react/src/antd/forms/error/ErrorFieldIndicator.js delete mode 100644 ui/cap-react/src/antd/forms/fields/TagsField.js delete mode 100644 ui/cap-react/src/antd/forms/fields/containers/SchemaPathSuggester.js delete mode 100644 ui/cap-react/src/antd/forms/fields/index.js delete mode 100644 ui/cap-react/src/antd/forms/fields/internal/TitleField.js create mode 100644 ui/cap-react/src/antd/forms/formuleConfig.js delete mode 100644 ui/cap-react/src/antd/forms/index.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/AccordionArrayFieldTemplate.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/ArrayFieldTemplateItem.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/ArrayUtils.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/EmptyArrayField.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/FixedArrayFieldTemplate.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/ImportListModal.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/LayerArrayFieldTemplate.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/NormalArrayFieldTemplate.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/index.js delete mode 100644 ui/cap-react/src/antd/forms/templates/Field/FieldHeader.js delete mode 100644 ui/cap-react/src/antd/forms/templates/Field/FieldTemplate.js delete mode 100644 ui/cap-react/src/antd/forms/templates/Field/WrapIfAdditional.js delete mode 100644 ui/cap-react/src/antd/forms/templates/ObjectFieldTemplate.js delete mode 100644 ui/cap-react/src/antd/forms/templates/TabField.js delete mode 100644 ui/cap-react/src/antd/forms/templates/TabFieldMenu.js delete mode 100644 ui/cap-react/src/antd/forms/templates/utils/tabfield.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/CheckboxWidget.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/DateWidget.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/MaskedInput/MaskedInput.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/MaskedInput/index.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/RequiredWidget.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/SelectWidget.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/SwitchWidget.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/TextWidget.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/UriWidget.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/containers/RequiredWidget.js delete mode 100644 ui/cap-react/src/antd/forms/widgets/index.js delete mode 100644 ui/cap-react/src/antd/partials/Markdown/Markdown.js delete mode 100644 ui/cap-react/src/antd/partials/Markdown/index.js delete mode 100644 ui/cap-react/src/antd/partials/Markdown/marked.js create mode 100644 ui/cap-react/src/reducers/builder.js delete mode 100644 ui/cap-react/src/reducers/schemaWizard.js diff --git a/.prettierignore b/.prettierignore index 4972c11428..1990b89843 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,4 +6,6 @@ yarn.lock .gitignore Dockerfile *.svg -.npmrc \ No newline at end of file +.npmrc +.yalc +yalc.lock \ No newline at end of file diff --git a/ui/cap-react/.gitignore b/ui/cap-react/.gitignore index bce8daf8ef..e1580677f4 100644 --- a/ui/cap-react/.gitignore +++ b/ui/cap-react/.gitignore @@ -43,3 +43,6 @@ bundle-stats.html cypress/screenshots .env + +.yalc +yalc.lock diff --git a/ui/cap-react/package.json b/ui/cap-react/package.json index 2437bb025d..d89471106f 100644 --- a/ui/cap-react/package.json +++ b/ui/cap-react/package.json @@ -2,6 +2,7 @@ "name": "cap-react", "version": "0.3.0", "description": "CERN Analysis Preservation UI", + "type": "module", "engines": { "npm": ">=3" }, @@ -39,12 +40,8 @@ "@codemirror/state": "6.1.3", "@codemirror/view": "6.4.2", "@datapunt/matomo-tracker-react": "0.5.1", - "@rjsf/antd": "5.8.1", - "@rjsf/core": "5.8.1", - "@rjsf/utils": "5.8.1", - "@rjsf/validator-ajv8": "5.8.1", "@sentry/react": "^5.0.0", - "@vitejs/plugin-react": "3.1.0", + "@vitejs/plugin-react": "^4.2.0", "antd": "^5.4.2", "axios": "0.27.2", "classnames": "2.3.1", @@ -65,9 +62,8 @@ "pretty-bytes": "^4.0.2", "query-string": "^5.1.0", "react": "^18.2.0", - "react-dnd": "^9.3.4", - "react-dnd-html5-backend": "9.3.4", "react-dom": "^18.2.0", + "react-formule": "file:.yalc/react-formule", "react-infinite-scroll-component": "6.1.0", "react-input-mask": "3.0.0-alpha.2", "react-joyride": "^2.5.4", @@ -84,8 +80,7 @@ "redux-thunk": "2.3.0", "sanitize-html": "2.4.0", "squirrelly": "8.0.8", - "vite": "4.2.1", - "vite-plugin-svgr": "2.4.0" + "vite": "^5.0.2" }, "devDependencies": { "autoprefixer": "7.1.4", @@ -110,6 +105,8 @@ "replace": "0.3.0", "rimraf": "2.6.1", "rollup-plugin-visualizer": "5.9.0", + "vite-plugin-restart": "^0.4.0", + "vite-plugin-svgr": "^4.2.0", "vitest": "0.30.1" }, "keywords": [], diff --git a/ui/cap-react/src/actions/builder.js b/ui/cap-react/src/actions/builder.js new file mode 100644 index 0000000000..6a893a685b --- /dev/null +++ b/ui/cap-react/src/actions/builder.js @@ -0,0 +1,287 @@ +import axios from "../axios"; +import { isEqual } from "lodash-es"; +import { fromJS } from "immutable"; +import { push } from "connected-react-router"; +import { notification } from "antd"; +import { CMS, CMS_NEW } from "../antd/routes"; +import { initFormuleSchemaWithNotifications } from "../antd/admin/utils"; +import { updateDepositGroups } from "./auth"; +import { getFormuleState } from "react-formule"; + +export const SYNCHRONIZE_FORMULE_STATE = "SYNCHRONIZE_FORMULE_STATE"; + +export const SET_SCHEMA_LOADING = "SET_SCHEMA_LOADING"; +export const UPDATE_SCHEMA_CONFIG = "UPDATE_SCHEMA_CONFIG"; + +export const UPDATE_NOTIFICATION_BY_INDEX = "UPDATE_NOTIFICATION_BY_INDEX"; +export const UPDATE_NOTIFICATIONS = "UPDATE_NOTIFICATIONS"; +export const REMOVE_NOTIFICATION = "REMOVE_NOTIFICATION"; +export const CREATE_NOTIFICATION_GROUP = "CREATE_NOTIFICATION_GROUP"; + +export const SET_SCHEMA_PERMISSIONS = "SET_SCHEMA_PERMISSIONS"; + +export const synchronizeFormuleState = value => ({ + type: SYNCHRONIZE_FORMULE_STATE, + value, +}); + +export const setSchemaLoading = value => ({ + type: SET_SCHEMA_LOADING, + value, +}); + +export const updateSchemaConfig = config => ({ + type: UPDATE_SCHEMA_CONFIG, + config, +}); + +export const updateNotificationByIndex = data => ({ + type: UPDATE_NOTIFICATION_BY_INDEX, + payload: data, +}); + +export const updateNotifications = item => ({ + type: UPDATE_NOTIFICATIONS, + payload: item, +}); + +export const deleteNotification = notification => ({ + type: REMOVE_NOTIFICATION, + payload: notification, +}); + +export const createNotificationCategory = category => ({ + type: CREATE_NOTIFICATION_GROUP, + path: ["config", "config", "notifications", "actions", category], +}); + +export const createNewNotification = category => (dispatch, getState) => { + const valuesPath = ["config", "config", "notifications", "actions", category]; + + let notifications = fromJS(getState().builder.getIn(valuesPath, [])); + notifications = notifications.push(fromJS({})); + + dispatch( + updateNotifications({ + path: valuesPath, + value: notifications, + category, + index: notifications.size - 1, + }) + ); + + return notifications.size - 1; +}; + +export const removeNotification = (index, category) => (dispatch, getState) => { + const path = ["config", "config", "notifications", "actions", category]; + let notification = getState().builder.getIn(path); + let newNotification = notification.delete(index); + dispatch(deleteNotification({ path, notification: newNotification })); +}; + +export const updateNotificationData = (data, index, category) => { + return dispatch => { + const valuesPath = [ + "config", + "config", + "notifications", + "actions", + category, + index, + ]; + + dispatch( + updateNotificationByIndex({ path: valuesPath, value: fromJS(data) }) + ); + }; +}; + +export const getSchema = (name, version = null) => { + const schemaLink = version + ? `/api/jsonschemas/${name}/${version}?resolve=1&config=1` + : `/api/jsonschemas/${name}?resolve=1&config=1`; + + return dispatch => { + dispatch(setSchemaLoading(true)); + axios + .get(schemaLink) + .then(resp => { + let schema = resp.data; + let { deposit_schema, deposit_options } = schema; + + if (deposit_schema && deposit_options) { + // The schemas are sent to be managed by formule but the config is kept in CAP (see function body) + initFormuleSchemaWithNotifications(schema); + dispatch(setSchemaLoading(false)); + } + }) + .catch(() => { + dispatch(push(CMS)); + notification.error({ + message: "Schema fetch failed", + description: "Make sure that schema name and version are correct ", + }); + }); + }; +}; + +export const saveSchemaChanges = () => (dispatch, getState) => { + const state = getState(); + const config = state.builder.get("config"); + const formuleState = getFormuleState(); + const pathname = state.router.location.pathname; + const sendData = { + deposit_schema: formuleState.current.schema, + deposit_options: formuleState.current.uiSchema, + ...config.toJS(), + }; + + // check if there is no name or version + // these fields are required for the schema to be created or updated + if ( + !config.get("name") || + !config.get("version") || + !config.get("fullname") + ) { + notification.warning({ + description: "Schema name, fullname and version are required", + message: "Missing information", + }); + return; + } + + // check whether there are changes to the deposit schema + const isSchemaUpdated = !isEqual( + formuleState.current.schema, + formuleState.initial.schema + ); + // check whether there are changes to the config object + const isConfigVersionUpdated = + config.get("version") != state.builder.get("initialConfig").version; + + if (isSchemaUpdated && !isConfigVersionUpdated) { + notification.warning({ + message: "These changes require new version", + description: "please make sure to update the version of the schema", + }); + return; + } + + if (pathname.startsWith(CMS_NEW) || isSchemaUpdated) { + return axios + .post("/api/jsonschemas", sendData) + .then(res => { + initFormuleSchemaWithNotifications(res.data); + notification.success({ + message: "New schema created", + description: "schema successfully created", + }); + dispatch(updateDepositGroups()); + dispatch(push(`/admin/${config.get("name")}/${config.get("version")}`)); + }) + .catch(err => { + let errorHeading, errorMessage; + if (typeof err.response.data.message === "object") { + let errMsg = Object.entries(err.response.data.message); + errorHeading = errMsg[0][0]; + errorMessage = errMsg[0][1][0]; + } else { + errorHeading = "Schema Creation"; + errorMessage = + err.response.data.message || + "Error while creating, please try again"; + } + notification.error({ + message: errorHeading, + description: errorMessage, + }); + }); + } + + return axios + .put( + `/api/jsonschemas/${config.get("name")}/${config.get("version")}`, + sendData + ) + .then(() => + notification.success({ + message: "Schema Updated", + description: "changes successfully applied", + }) + ) + .catch(() => + notification.error({ + message: "Schema Updates", + description: "Error while saving, please try again", + }) + ); +}; + +export const setSchemaPermissions = permissions => ({ + type: SET_SCHEMA_PERMISSIONS, + permissions, +}); + +export const getSchemaPermissions = (name, version = null) => { + let schemaPermissionLink; + + if (version) + schemaPermissionLink = `/api/jsonschemas/${name}/${version}/permissions`; + else schemaPermissionLink = `/api/jsonschemas/${name}/permissions`; + return function (dispatch) { + axios + .get(schemaPermissionLink) + .then(resp => { + dispatch(setSchemaPermissions(resp.data)); + }) + .catch(() => { + notification.error({ + message: "Fetching permissions failed", + description: "There was an error fetching the schema permissions", + }); + }); + }; +}; + +export const postSchemaPermissions = (name, version = null, permissions) => { + let schemaPermissionLink; + + if (version) + schemaPermissionLink = `/api/jsonschemas/${name}/${version}/permissions`; + else schemaPermissionLink = `/api/jsonschemas/${name}/permissions`; + return function (dispatch) { + axios + .post(schemaPermissionLink, permissions) + .then(() => { + dispatch(getSchemaPermissions(name, version)); + }) + .catch(() => { + notification.error({ + message: "Updating schema permissions failed", + description: "There was an error updating the schema permissions", + }); + }); + }; +}; + +export const deleteSchemaPermissions = (name, version = null, permissions) => { + let schemaPermissionLink; + + if (version) + schemaPermissionLink = `/api/jsonschemas/${name}/${version}/permissions`; + else schemaPermissionLink = `/api/jsonschemas/${name}/permissions`; + return function (dispatch) { + axios + .delete(schemaPermissionLink, { data: permissions }) + .then(() => { + dispatch(getSchemaPermissions(name, version)); + }) + .catch(() => { + notification.error({ + message: "Deleting schema permissions failed", + description: "There was an error deleting the schema permissions", + }); + }); + }; +}; diff --git a/ui/cap-react/src/actions/schemaWizard.js b/ui/cap-react/src/actions/schemaWizard.js deleted file mode 100644 index a8309c380a..0000000000 --- a/ui/cap-react/src/actions/schemaWizard.js +++ /dev/null @@ -1,678 +0,0 @@ -import axios from "../axios"; -import { merge } from "lodash-es"; -import { fromJS } from "immutable"; -import { push } from "connected-react-router"; -import { notification } from "antd"; -import { CMS, CMS_EDITOR, CMS_NEW, CMS_SCHEMA } from "../antd/routes"; -import { slugify, _initSchemaStructure } from "../antd/admin/utils"; -import { updateDepositGroups } from "./auth"; - -export const ADD_PROPERTY = "ADD_PROPERTY"; -export const ADD_PROPERTY_INIT = "ADD_PROPERTY_INIT"; - -export const CREATE_MODE_ENABLE = "CREATE_MODE_ENABLE"; - -export const PROPERTY_SELECT = "PROPERTY_SELECT"; - -export const SCHEMA_INIT_REQUEST = "SCHEMA_INIT_REQUEST"; -export const SCHEMA_INIT = "SCHEMA_INIT"; -export const SCHEMA_ERROR = "SCHEMA_ERROR"; - -export const CURRENT_UPDATE_CONFIG = "CURRENT_UPDATE_CONFIG"; -export const CURRENT_UPDATE_PATH = "CURRENT_UPDATE_PATH"; -export const CURRENT_UPDATE_SCHEMA_PATH = "CURRENT_UPDATE_SCHEMA_PATH"; -export const CURRENT_UPDATE_UI_SCHEMA_PATH = "CURRENT_UPDATE_UI_SCHEMA_PATH"; - -export const UPDATE_NOTIFICATION_BY_INDEX = "UPDATE_NOTIFICATION_BY_INDEX"; -export const ADD_NEW_NOTIFICATION = "ADD_NEW_NOTIFICATION"; -export const REMOVE_NOTIFICATION = "REMOVE_NOTIFICATION"; -export const CREATE_NOTIFICATION_GROUP = "CREATE_NOTIFICATION_GROUP"; - -export const SET_SCHEMA_PERMISSIONS = "SET_SCHEMA_PERMISSIONS"; - -const NOTIFICATIONS = { - notifications: { - actions: { - review: [], - publish: [], - }, - }, -}; - -export function updateNotification(data) { - return { - type: UPDATE_NOTIFICATION_BY_INDEX, - payload: data, - }; -} - -export function addNewNotification(item) { - return { - type: ADD_NEW_NOTIFICATION, - payload: item, - }; -} - -export function deleteNotification(notification) { - return { - type: REMOVE_NOTIFICATION, - payload: notification, - }; -} - -export function setSchemaPermissions(permissions) { - return { - type: SET_SCHEMA_PERMISSIONS, - permissions, - }; -} - -export function schemaError(error) { - return { - type: SCHEMA_ERROR, - payload: error, - }; -} - -export function schemaInitRequest() { - return { - type: SCHEMA_INIT_REQUEST, - }; -} - -export function schemaInit(id, data, configs = {}) { - return { - type: SCHEMA_INIT, - id, - data, - configs, - }; -} - -export function enableCreateMode() { - return { type: CREATE_MODE_ENABLE }; -} - -export function selectProperty(path) { - return { - type: PROPERTY_SELECT, - path, - }; -} - -const findParentPath = schemaPath => { - // Objects have to be required always for validation to work inside - let isObj; - for (let i = schemaPath.length - 1; i >= 0; i--) { - // If we find a properties, it means we're inside an object (and not an array) - if (schemaPath[i] === "properties") { - isObj = true; - } else if (isObj) { - return schemaPath.splice(0, i + 1); - } else { - isObj = false; - } - } - return []; -}; - -export function updateRequired(schemaPath, checked) { - return function (dispatch) { - if (schemaPath.length) { - const parentPath = findParentPath(schemaPath); - const fieldName = schemaPath[schemaPath.length - 1]; - dispatch(updateRequiredByPath(parentPath, fieldName, checked)); - dispatch(updateRequired(parentPath, checked)); - } - }; -} - -export function updateRequiredByPath(path, fieldName, isRequired) { - return function (dispatch, getState) { - let schema = getState() - .schemaWizard.getIn(["current", "schema", ...path]) - .toJS(); - - let required = schema.required || []; - - if (isRequired) { - if (!required.includes(fieldName)) { - required.push(fieldName); - } - } else { - required = required.filter(e => e !== fieldName); - } - - let updatedSchema = { ...schema, required: required }; - - if (!required.length) { - delete updatedSchema.required; - } - - dispatch(updateSchemaByPath(path, updatedSchema)); - }; -} - -export function initSchemaWizard(data) { - return function (dispatch) { - const { id, deposit_schema, deposit_options, ...configs } = data; - - configs.config = merge(configs.config, NOTIFICATIONS); - dispatch( - schemaInit( - id || "Schema Name", - { schema: deposit_schema, uiSchema: deposit_options }, - configs - ) - ); - dispatch(push(CMS_NEW)); - }; -} - -export function getSchema(name, version = null) { - let schemaLink; - - if (version) - schemaLink = `/api/jsonschemas/${name}/${version}?resolve=1&config=1`; - else schemaLink = `/api/jsonschemas/${name}?resolve=1&config=1`; - return function (dispatch) { - dispatch(schemaInitRequest()); - axios - .get(schemaLink) - .then(resp => { - let schema = resp.data; - let { id, deposit_schema, deposit_options, ...configs } = schema; - - configs.config = merge(configs.config, NOTIFICATIONS); - - if (deposit_schema && deposit_options) - dispatch( - schemaInit( - id || "Schema Name", - { schema: deposit_schema, uiSchema: deposit_options }, - configs - ) - ); - }) - .catch(err => { - dispatch(push(CMS)); - notification.error({ - message: "Schema fetch failed", - description: "Make sure that schema name and version are correct ", - }); - dispatch(schemaError(err)); - }); - }; -} - -export function getSchemaPermissions(name, version = null) { - let schemaPermissionLink; - - if (version) - schemaPermissionLink = `/api/jsonschemas/${name}/${version}/permissions`; - else schemaPermissionLink = `/api/jsonschemas/${name}/permissions`; - return function (dispatch) { - // dispatch(schemaInitRequest()); - axios - .get(schemaPermissionLink) - .then(resp => { - dispatch(setSchemaPermissions(resp.data)) - }) - .catch(() => { - notification.error({ - message: "Fetching permissions failed", - description: "There was an error fetching the schema permissions", - }); - }); - }; -} - -export function postSchemaPermissions(name, version = null, permissions) { - let schemaPermissionLink; - - if (version) - schemaPermissionLink = `/api/jsonschemas/${name}/${version}/permissions`; - else schemaPermissionLink = `/api/jsonschemas/${name}/permissions`; - return function (dispatch) { - // dispatch(schemaInitRequest()); - axios - .post(schemaPermissionLink, permissions) - .then(() => { - dispatch(getSchemaPermissions(name, version)) - }) - .catch(() => { - notification.error({ - message: "Updating schema permissions failed", - description: "There was an error updating the schema permissions", - }); - }); - }; -} - -export function deleteSchemaPermissions(name, version = null, permissions) { - let schemaPermissionLink; - - if (version) - schemaPermissionLink = `/api/jsonschemas/${name}/${version}/permissions`; - else schemaPermissionLink = `/api/jsonschemas/${name}/permissions`; - return function (dispatch) { - axios - .delete(schemaPermissionLink, {data: permissions}) - .then(() => { - dispatch(getSchemaPermissions(name, version)) - }) - .catch(() => { - notification.error({ - message: "Deleting schema permissions failed", - description: "There was an error deleting the schema permissions", - }); - }); - }; -} - - -export function getSchemasLocalStorage() { - return function () { - //let availableSchemas = localStorage.getItem("availableSchemas"); - let availableSchemas = JSON.parse(availableSchemas); - }; -} - -export function createContentType(content_type) { - return function (dispatch) { - dispatch(schemaInitRequest()); - - let { name, description } = content_type; - const _id = slugify(Math.random().toString() + "_" + name); - let config = { - config: merge({ fullname: name }, NOTIFICATIONS), - }; - - dispatch(schemaInit(_id, _initSchemaStructure(name, description), config)); - dispatch(push(CMS_NEW)); - }; -} - -export function selectContentTypeEdit(id) { - return function (dispatch) { - dispatch(push(`${CMS_EDITOR}/${id}`)); - }; -} - -export function selectContentTypeView(id) { - return function (dispatch) { - dispatch(push(`${CMS_SCHEMA}/${id}`)); - }; -} - -export function selectFieldType(path, change) { - return function (dispatch) { - dispatch(updateByPath(path, change)); - }; -} -export function updateCurrentSchemaWithField(schema) { - return function (dispatch, getState) { - let state = getState().schemaWizard; - let propKey = state.getIn(["field", "propKey"]); - let path = state.getIn(["field", "path"]).toJS(); - - const pathToChange = propKey ? [...path, propKey] : path; - dispatch(updateSchemaByPath(pathToChange, schema)); - }; -} - -export function updateSchemaConfig(config) { - return { - type: CURRENT_UPDATE_CONFIG, - config, - }; -} - -export function updateSchemaByPath(path, value) { - return { - type: CURRENT_UPDATE_SCHEMA_PATH, - path, - value, - }; -} - -export function updateUiSchemaByPath(path, value) { - return { - type: CURRENT_UPDATE_UI_SCHEMA_PATH, - path, - value, - }; -} - -export function updateByPath(path, value) { - return { - type: CURRENT_UPDATE_PATH, - path, - value, - }; -} - -export function addByPath({ schema: path, uiSchema: uiPath }, data) { - return function (dispatch, getState) { - let schema = getState() - .schemaWizard.getIn(["current", "schema", ...path]) - .toJS(); - - let _path = path; - let _uiPath = uiPath; - - let random_name = `item_${Math.random().toString(36).substring(2, 8)}`; - - if (schema.type) { - if (schema.type == "object") { - if (!schema.properties) schema.properties = {}; - _path = [...path, "properties", random_name]; - _uiPath = [...uiPath, random_name]; - } else if (schema.type == "array") { - if (!schema.items) schema.items = {}; - _path = [...path, "items"]; - _uiPath = [...uiPath, "items"]; - } - - dispatch(updateByPath({ schema: _path, uiSchema: _uiPath }, data)); - } - }; -} - -export function initAddProperty(path) { - return { - type: ADD_PROPERTY_INIT, - path, - }; -} - -export function addProperty(path, key) { - return { - type: ADD_PROPERTY, - path, - key, - }; -} - -// delete item from schema and uiSchema -export function deleteByPath(item) { - return function (dispatch, getState) { - const { path, uiPath } = item; - const uiItemToDelete = uiPath.pop(); - - dispatch(updateRequired(path, false)); - - // ********* schema ********** - let itemToDelete = path.pop(); - // if the last item is items then pop again since it is an array, in order to fetch the proper id - itemToDelete = itemToDelete === "items" ? path.pop() : itemToDelete; - - let schema = getState() - .schemaWizard.getIn(["current", "schema", ...path]) - .toJS(); - - delete schema[itemToDelete]; - - // ********* uiSchema ********** - let uiSchema = getState() - .schemaWizard.getIn(["current", "uiSchema", ...uiPath]) - .toJS(); - - delete uiSchema[uiItemToDelete]; - - if (uiSchema["ui:order"]) { - // remove the itemToDelete from the ui:order - uiSchema["ui:order"] = uiSchema["ui:order"].filter( - item => item !== uiItemToDelete - ); - } - - // ********* update changes ********** - dispatch( - updateByPath({ schema: path, uiSchema: uiPath }, { schema, uiSchema }) - ); - dispatch(enableCreateMode()); - }; -} - -// update the id field of a property -export function renameIdByPath(item, newName) { - return function (dispatch, getState) { - const path = item.path; - const uiPath = item.uiPath; - - let itemToDelete = path.pop(); - // if the last item is items then pop again since it is an array, in order to fetch the proper id - itemToDelete = itemToDelete === "items" ? path.pop() : itemToDelete; - - const uiItemToDelete = uiPath.pop(); - - // check if the new id is empty or exact same with the current id - if (newName === itemToDelete || newName === "") { - notification.warning({ - description: "Make sure that the new id is different and not empty", - }); - return; - } - - if (newName.indexOf(" ") >= 0) { - notification.warning({ description: "An id cannot contain spaces" }); - return; - } - - // navigate to the correct path - let schema = getState() - .schemaWizard.getIn(["current", "schema", ...path]) - .toJS(); - let uiSchema = getState() - .schemaWizard.getIn(["current", "uiSchema", ...uiPath]) - .toJS(); - - // ********* schema ********** - let keys = Object.keys(schema); - // make sure that the new name is unique among sibling widgets - if (keys.includes(newName)) { - notification.error({ - description: "The id should be unique, this name already exists", - }); - return; - } - - // create new obj with the information and then delete the old one - schema[newName] = schema[itemToDelete]; - delete schema[itemToDelete]; - - // ********* uiSchema ********** - if (!uiSchema["ui:order"]) { - uiSchema["ui:order"] = []; - } - // update the uiOrder array - let pos = uiSchema["ui:order"].indexOf(uiItemToDelete); - if (pos > -1) { - uiSchema["ui:order"][pos] = newName; - } - - if (uiSchema[uiItemToDelete]) { - uiSchema[newName] = uiSchema[uiItemToDelete]; - } - // remove from the uiSchema - delete uiSchema[uiItemToDelete]; - - // ********* update changes ********** - dispatch( - updateByPath({ schema: path, uiSchema: uiPath }, { schema, uiSchema }) - ); - - dispatch( - selectProperty({ - schema: [...path, newName], - uiSchema: [...uiPath, newName], - }) - ); - }; -} - -export function createNotificationCategory(category) { - return { - type: CREATE_NOTIFICATION_GROUP, - path: ["config", "config", "notifications", "actions", category], - }; -} - -export function createNewNotification(category) { - return function (dispatch, getState) { - const valuesPath = [ - "config", - "config", - "notifications", - "actions", - category, - ]; - - let notifications = fromJS(getState().schemaWizard.getIn(valuesPath, [])); - notifications = notifications.push(fromJS({})); - - dispatch( - addNewNotification({ - path: valuesPath, - item: notifications, - category, - index: notifications.size - 1, - }) - ); - - return notifications.size - 1; - }; -} - -export function removeNotification(index, category) { - return function (dispatch, getState) { - const path = ["config", "config", "notifications", "actions", category]; - - let notification = getState().schemaWizard.getIn(path); - - let newNotification = notification.delete(index); - dispatch(deleteNotification({ path, notification: newNotification })); - const pathname = getState().router.location.pathname; - dispatch(push(pathname.split(`/${index}`)[0])); - }; -} - -export function updateNotificationData(data, index, category) { - return function (dispatch) { - const valuesPath = [ - "config", - "config", - "notifications", - "actions", - category, - index, - ]; - - dispatch(updateNotification({ path: valuesPath, value: fromJS(data) })); - }; -} - -export function saveSchemaChanges() { - return function (dispatch, getState) { - const state = getState(); - const config = state.schemaWizard.get("config"); - const pathname = state.router.location.pathname; - const sendData = { - deposit_schema: state.schemaWizard.getIn(["current", "schema"]).toJS(), - deposit_options: state.schemaWizard.getIn(["current", "uiSchema"]).toJS(), - ...config.toJS(), - }; - - // check if there is no name or version - // these fields are required for the schema to be created or updated - if ( - !config.get("name") || - !config.get("version") || - !config.get("fullname") - ) { - notification.warning({ - description: "Schema name, fullname and version are required", - message: "Missing information", - }); - return; - } - - // Removes 'fullname' from config data (so that schema can validate in the backend) - delete sendData["config"]["fullname"] - // check whether there are changes to the deposit schema - const isSchemaUpdated = !state.schemaWizard - .getIn(["current", "schema"]) - .isSubset(state.schemaWizard.getIn(["initial", "schema"])); - // check whether there are changes to the config object - const isConfigVersionUpdated = - config.get("version") != state.schemaWizard.get("initialConfig").version; - - if (isSchemaUpdated && !isConfigVersionUpdated) { - notification.warning({ - message: "These changes require new version", - description: "please make sure to update the version of the schema", - }); - return; - } - - if (pathname.startsWith(CMS_NEW) || isSchemaUpdated) { - return axios - .post("/api/jsonschemas", sendData) - .then(res => { - const { deposit_options, deposit_schema, ...configs } = res.data; - configs.config = merge(configs.config, NOTIFICATIONS); - dispatch( - schemaInit( - "Schema Name", - { schema: deposit_schema, uiSchema: deposit_options }, - configs - ) - ); - notification.success({ - message: "New schema created", - description: "schema successfully created", - }); - dispatch(updateDepositGroups()); - dispatch( - push(`/admin/${config.get("name")}/${config.get("version")}`) - ); - }) - .catch(err => { - let errorHeading, errorMessage; - if (typeof err.response.data.message === "object") { - let errMsg = Object.entries(err.response.data.message); - errorHeading = errMsg[0][0]; - errorMessage = errMsg[0][1][0]; - } else { - errorHeading = "Schema Creation"; - errorMessage = - err.response.data.message || - "Error while creating, please try again"; - } - notification.error({ - message: errorHeading, - description: errorMessage, - }); - }); - } - - return axios - .put( - `/api/jsonschemas/${config.get("name")}/${config.get("version")}`, - sendData - ) - .then(() => - notification.success({ - message: "Schema Updated", - description: "changes successfully applied", - }) - ) - .catch(() => - notification.error({ - message: "Schema Updates", - description: "Error while saving, please try again", - }) - ); - }; -} diff --git a/ui/cap-react/src/antd/App/App.js b/ui/cap-react/src/antd/App/App.js index e67e844591..7d11cff1d7 100644 --- a/ui/cap-react/src/antd/App/App.js +++ b/ui/cap-react/src/antd/App/App.js @@ -24,20 +24,40 @@ import useTrackPageViews from "../hooks/useTrackPageViews"; import { lazy } from "react"; import Loading from "../routes/Loading/Loading"; import MessageBanner from "../partials/MessageBanner"; +import { FormuleContext } from "react-formule"; +import { theme } from "../utils/theme"; +import { customFieldTypes, customFields } from "../forms/formuleConfig"; +import { isEmpty } from "lodash-es"; +import { transformSchema } from "../partials/Utils/schema"; const AdminPage = lazy(() => import("../admin")); -const App = ({ initCurrentUser, loadingInit, history, roles }) => { +const App = ({ + initCurrentUser, + loadingInit, + history, + roles, + synchronizeFormuleState, + formDataChange, +}) => { useEffect(() => { initCurrentUser(history.location.state); }, []); useTrackPageViews(history.location.pathname); + const handleFormuleStateChange = newState => { + synchronizeFormuleState(newState); + const newFormData = { ...newState.formData }; + if (!isEmpty(newFormData)) { + formDataChange(newFormData); + } + }; + const isAdmin = roles && (roles.get("isSuperUser") || roles.get("schemaAdmin").size > 0); - if (loadingInit) + if (loadingInit) { return ( @@ -45,6 +65,7 @@ const App = ({ initCurrentUser, loadingInit, history, roles }) => { ); + } return ( @@ -60,13 +81,24 @@ const App = ({ initCurrentUser, loadingInit, history, roles }) => { }> - - - - - {isAdmin && } - - + + + + + + {isAdmin && } + + + diff --git a/ui/cap-react/src/antd/App/AppContainer.js b/ui/cap-react/src/antd/App/AppContainer.js index 245a738c3a..f0c0e7b984 100644 --- a/ui/cap-react/src/antd/App/AppContainer.js +++ b/ui/cap-react/src/antd/App/AppContainer.js @@ -1,4 +1,6 @@ import { initCurrentUser } from "../../actions/auth"; +import { synchronizeFormuleState } from "../../actions/builder"; +import { formDataChange } from "../../actions/draftItem"; import App from "./App"; import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; @@ -10,6 +12,9 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ initCurrentUser: next => dispatch(initCurrentUser(next)), + synchronizeFormuleState: newState => + dispatch(synchronizeFormuleState(newState)), + formDataChange: newFormData => dispatch(formDataChange(newFormData)), }); export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App)); diff --git a/ui/cap-react/src/antd/admin/components/AdminIndex.js b/ui/cap-react/src/antd/admin/components/AdminIndex.js index 30e03503ba..d1d080c04e 100644 --- a/ui/cap-react/src/antd/admin/components/AdminIndex.js +++ b/ui/cap-react/src/antd/admin/components/AdminIndex.js @@ -1,6 +1,6 @@ import { Card, Col, Row } from "antd"; -import CreateForm from "../containers/CreateForm"; -import DropZoneForm from "../containers/DropZoneForm"; +import CreateForm from "../components/CreateForm"; +import DropZoneForm from "../components/DropZoneForm"; import SelectContentType from "../containers/SelectContentType"; const AdminIndex = () => { diff --git a/ui/cap-react/src/antd/admin/components/AdminPanel.js b/ui/cap-react/src/antd/admin/components/AdminPanel.js index 0bade3179b..473342b623 100644 --- a/ui/cap-react/src/antd/admin/components/AdminPanel.js +++ b/ui/cap-react/src/antd/admin/components/AdminPanel.js @@ -1,9 +1,8 @@ import { useEffect, useState } from "react"; import PropTypes from "prop-types"; import Header from "../containers/Header"; -import { CMS_NEW } from "../../routes"; import DocumentTitle from "../../partials/DocumentTitle"; -import SchemaWizard from "../containers/SchemaWizard"; +import SchemaWizard from "../components/SchemaWizard"; import Notifications from "../notifications/containers/Notifications"; import { FloatButton, Layout } from "antd"; import Joyride, { STATUS } from "react-joyride"; @@ -12,20 +11,22 @@ import TourTooltip from "../utils/tour/TourTooltip"; import { CarOutlined } from "@ant-design/icons"; import useStickyState from "../../hooks/useStickyState"; import { PRIMARY_COLOR } from "../../utils/theme"; +import { isEmpty } from "lodash-es"; +import { initFormuleSchemaWithNotifications } from "../utils"; import Permissions from "../permissions/Permissions"; -const AdminPanel = ({ location, match, schema, schemaInit, getSchema }) => { +const AdminPanel = ({ location, match, getSchema, loading, formuleState }) => { useEffect(() => { let { schema_name, schema_version } = match.params; - const { pathname } = location; - // fetch schema - // in order to cover all the potential situations creating from the already exist schemas - // and create a new one from scratch - if (schema_name) { - pathname.startsWith(CMS_NEW) - ? schema.size == 0 && schemaInit() - : getSchema(schema_name, schema_version); + if (schema_name == "new") { + // If the schema hasn't been initialized yet (i.e. the user has navigated directly to /new), initialize it + if (isEmpty(formuleState?.current?.schema)) { + initFormuleSchemaWithNotifications(); + } + // Otherwise do nothing as it means it's been already initialized in CreateForm or DropDownBox + } else { + getSchema(schema_name, schema_version); } }, []); @@ -35,8 +36,10 @@ const AdminPanel = ({ location, match, schema, schemaInit, getSchema }) => { const getPageTitle = () => location.pathname.includes("notifications") - ? "Notifications" : location.pathname.includes("permissions") ? - "Permissions" : "Form Builder"; + ? "Notifications" + : location.pathname.includes("permissions") + ? "Permissions" + : "Form Builder"; const getDisplay = () => { switch (display) { @@ -45,7 +48,11 @@ const AdminPanel = ({ location, match, schema, schemaInit, getSchema }) => { case "permissions": return ; default: - return ; + return ( + + ); } }; @@ -93,10 +100,8 @@ const AdminPanel = ({ location, match, schema, schemaInit, getSchema }) => { AdminPanel.propTypes = { match: PropTypes.object, location: PropTypes.object, - replacePath: PropTypes.func, - schema: PropTypes.object, - schemaInit: PropTypes.func, getSchema: PropTypes.func, + loading: PropTypes.bool, }; export default AdminPanel; diff --git a/ui/cap-react/src/antd/admin/components/CreateForm.js b/ui/cap-react/src/antd/admin/components/CreateForm.js index 8ecabbae3c..54571ffeeb 100644 --- a/ui/cap-react/src/antd/admin/components/CreateForm.js +++ b/ui/cap-react/src/antd/admin/components/CreateForm.js @@ -1,9 +1,18 @@ -import PropTypes from "prop-types"; import { Button, Form, Input } from "antd"; +import { CMS_NEW } from "../../routes"; +import { withRouter } from "react-router"; +import { initFormuleSchemaWithNotifications } from "../utils"; + +const CreateForm = ({ history }) => { + const onFinish = content => { + let { name, description } = content; + const config = { config: { fullname: name } }; + initFormuleSchemaWithNotifications({ config }, name, description); + history.push(CMS_NEW); + }; -const CreateForm = ({ createContentType }) => { return ( -
+ { ); }; -CreateForm.propTypes = { - createContentType: PropTypes.func, -}; - -export default CreateForm; +export default withRouter(CreateForm); diff --git a/ui/cap-react/src/antd/admin/components/Customize.js b/ui/cap-react/src/antd/admin/components/Customize.js deleted file mode 100644 index 7f29a710f3..0000000000 --- a/ui/cap-react/src/antd/admin/components/Customize.js +++ /dev/null @@ -1,146 +0,0 @@ -import { useEffect, useState } from "react"; -import PropTypes from "prop-types"; -import PropertyKeyEditorForm from "./PropKeyEditorForm"; - -import { Radio, Space, Tabs, Typography } from "antd"; -import { SIZE_OPTIONS } from "../utils"; - -const JUSTIFY_OPTIONS = ["start", "center", "end"]; - -const Customize = ({ - schema, - uiSchema, - onSchemaChange, - onUiSchemaChange, - path, - _path, - _uiPath, -}) => { - const [justify, setJustify] = useState(() => "start"); - const [size, setSize] = useState("xlarge"); - - useEffect(() => { - if (uiSchema && Object.hasOwn(uiSchema.toJS(), "ui:options")) { - setSize(uiSchema.toJS()["ui:options"].size); - setJustify(uiSchema.toJS()["ui:options"].justify); - } - }, [uiSchema]); - - const _onSchemaChange = data => { - onSchemaChange(path.get("path").toJS(), data.formData); - }; - const _onUiSchemaChange = data => { - onUiSchemaChange(path.get("uiPath").toJS(), data.formData); - }; - const sizeChange = newSize => { - uiSchema = uiSchema ? uiSchema.toJS() : {}; - - let { "ui:options": uiOptions = {}, ...rest } = uiSchema; - let { size, ...restUIOptions } = uiOptions; - - size = newSize; - let _uiOptions = { size, ...restUIOptions }; - - onUiSchemaChange(path.get("uiPath").toJS(), { - ...rest, - "ui:options": _uiOptions, - }); - }; - - const alignChange = newAlign => { - uiSchema = uiSchema ? uiSchema.toJS() : {}; - - let { "ui:options": uiOptions = {}, ...rest } = uiSchema; - let { justify, ...restUIOptions } = uiOptions; - - justify = newAlign; - let _uiOptions = { justify, ...restUIOptions }; - - onUiSchemaChange(path.get("uiPath").toJS(), { - ...rest, - "ui:options": _uiOptions, - }); - }; - - return ( - - ), - }, - { - key: "2", - label: "UI Schema Settings", - children: - _path.size != 0 ? ( - - ) : ( - - Size Options - sizeChange(e.target.value)} - value={size} - style={{ paddingBottom: "15px" }} - > - {Object.keys(SIZE_OPTIONS).map(size => ( - - {size} - - ))} - - Align Options - alignChange(e.target.value)} - value={justify} - > - {JUSTIFY_OPTIONS.map(justify => ( - - {justify} - - ))} - - - ), - }, - ]} - /> - ); -}; - -Customize.propTypes = { - schema: PropTypes.object, - uiSchema: PropTypes.object, - onSchemaChange: PropTypes.func, - onUiSchemaChange: PropTypes.func, - path: PropTypes.object, - _path: PropTypes.object, -}; - -export default Customize; diff --git a/ui/cap-react/src/antd/admin/components/Draggable.js b/ui/cap-react/src/antd/admin/components/Draggable.js deleted file mode 100644 index febd31c147..0000000000 --- a/ui/cap-react/src/antd/admin/components/Draggable.js +++ /dev/null @@ -1,33 +0,0 @@ -import PropTypes from "prop-types"; -import { useDrag } from "react-dnd"; - -const style = { - border: "1px dashed gray", - backgroundColor: "white", - padding: "0.1rem", - cursor: "move", -}; -const Draggable = ({ data, children }) => { - const type = "FIELD_TYPE"; - const [{ isDragging }, drag] = useDrag({ - item: { type, data }, - collect: monitor => ({ - isDragging: monitor.isDragging(), - handlerId: monitor.getHandlerId(), - }), - }); - const opacity = isDragging ? 0.4 : 1; - - return ( -
- {children} -
- ); -}; - -Draggable.propTypes = { - children: PropTypes.node, - data: PropTypes.object, -}; - -export default Draggable; diff --git a/ui/cap-react/src/antd/admin/components/DropZoneForm.js b/ui/cap-react/src/antd/admin/components/DropZoneForm.js index b9033b9af8..d3f9997f16 100644 --- a/ui/cap-react/src/antd/admin/components/DropZoneForm.js +++ b/ui/cap-react/src/antd/admin/components/DropZoneForm.js @@ -1,6 +1,9 @@ -import PropTypes from "prop-types"; import { notification, Upload } from "antd"; import { InboxOutlined } from "@ant-design/icons"; +import { initFormuleSchemaWithNotifications } from "../utils"; +import { CMS_NEW } from "../../routes"; +import { withRouter } from "react-router"; + const isfileJson = filename => { const extension = filename.substr(filename.lastIndexOf(".") + 1); if (!/(json)$/gi.test(extension)) { @@ -9,7 +12,8 @@ const isfileJson = filename => { return extension; } }; -const DropZoneForm = ({ initWizard }) => { + +const DropZoneForm = ({ history }) => { return ( { reader.onload = function (event) { const newSchema = JSON.parse(event.target.result); if (newSchema["deposit_schema"] && newSchema["deposit_options"]) { - initWizard(newSchema); + initFormuleSchemaWithNotifications(newSchema); + history.push(CMS_NEW); } else { notification.error({ message: "Missing Keys", @@ -54,8 +59,4 @@ const DropZoneForm = ({ initWizard }) => { ); }; -DropZoneForm.propTypes = { - initWizard: PropTypes.func, -}; - -export default DropZoneForm; +export default withRouter(DropZoneForm); diff --git a/ui/cap-react/src/antd/admin/components/FormPreview.js b/ui/cap-react/src/antd/admin/components/FormPreview.js deleted file mode 100644 index 91fdc2823a..0000000000 --- a/ui/cap-react/src/antd/admin/components/FormPreview.js +++ /dev/null @@ -1,63 +0,0 @@ -import PropTypes from "prop-types"; -import Form from "../../forms/Form"; -import { transformSchema } from "../../partials/Utils/schema"; -import { shoudDisplayGuideLinePopUp } from "../utils"; -import { Row, Empty, Space, Typography, Col } from "antd"; - -const FormPreview = ({ schema, uiSchema }) => { - return ( -
- - Preview - - {shoudDisplayGuideLinePopUp(schema) ? ( - - - - Your form is empty - - - Add fields to the drop area to initialize your form - - - } - /> - - ) : ( - - - {}} - /> - - - )} -
- ); -}; - -FormPreview.propTypes = { - schema: PropTypes.object, - uiSchema: PropTypes.object, -}; - -export default FormPreview; diff --git a/ui/cap-react/src/antd/admin/components/Header.js b/ui/cap-react/src/antd/admin/components/Header.js index bc9e4e2d59..4a3fdf109f 100644 --- a/ui/cap-react/src/antd/admin/components/Header.js +++ b/ui/cap-react/src/antd/admin/components/Header.js @@ -31,10 +31,7 @@ const Header = ({ config, pushPath, saveSchemaChanges, - schema, - uiSchema, - initialUiSchema, - initialSchema, + formuleState, display, setDisplay, }) => { @@ -47,11 +44,14 @@ const Header = ({ maxHeight: "calc(100vh - 300px)", }; + const formuleCurrentSchema = formuleState?.current?.schema; + const formuleCurrentUiSchema = formuleState?.current?.uiSchema; + const _getSchema = () => { const fileData = JSON.stringify( { - deposit_schema: schema.toJS(), - deposit_options: uiSchema.toJS(), + deposit_schema: formuleCurrentSchema, + deposit_options: formuleCurrentUiSchema, ...config.toJS(), }, null, @@ -61,37 +61,39 @@ const Header = ({ const a = document.createElement("a"); const file = new Blob([fileData], { type: "text/json" }); a.href = URL.createObjectURL(file); - a.download = "fileName.json"; //TODO: Should be a proper name + a.download = `${config.toJS().name || "cap-schema"}-export-v${ + config.toJS().version + }-${Date.now()}.json`; a.click(); }; const _renderSchemaPreview = schemaPreviewDisplay => { let previews = { uiSchema: ( ), schema: ( ), uiSchemaDiff: ( ), schemaDiff: ( @@ -243,14 +245,11 @@ const Header = ({ Header.propTypes = { config: PropTypes.object, - schema: PropTypes.object, - uiSchema: PropTypes.object, - initialUiSchema: PropTypes.object, - initialSchema: PropTypes.object, pushPath: PropTypes.func, - updateSchemaConfig: PropTypes.func, saveSchemaChanges: PropTypes.func, - pathname: PropTypes.string, + formuleState: PropTypes.object, + display: PropTypes.string, + setDisplay: PropTypes.func, }; export default Header; diff --git a/ui/cap-react/src/antd/admin/components/PropKeyEditorForm.js b/ui/cap-react/src/antd/admin/components/PropKeyEditorForm.js deleted file mode 100644 index d68e633b6e..0000000000 --- a/ui/cap-react/src/antd/admin/components/PropKeyEditorForm.js +++ /dev/null @@ -1,71 +0,0 @@ -import PropTypes from "prop-types"; -import Form from "../../forms/Form"; -import fieldTypes, { hiddenFields } from "../utils/fieldTypes"; -import widgets from "../formComponents/widgets"; - -const PropertyKeyEditorForm = ({ - uiSchema = {}, - schema = {}, - formData = {}, - onChange = null, - optionsSchemaObject, - optionsUiSchemaObject, -}) => { - let type; - - const cleanupSelect = () => - schema.type === "array" ? delete formData.enum : delete formData.items; - - // in case we can not define the type of the element from the uiSchema, - // extract the type from the schema - if ( - !uiSchema || - (!uiSchema["ui:widget"] && !uiSchema["ui:field"] && !uiSchema["ui:object"]) - ) { - type = schema.type === "string" ? "text" : schema.type; - } else { - if (uiSchema["ui:widget"]) { - type = uiSchema["ui:widget"]; - if (uiSchema["ui:widget"] === "select") { - cleanupSelect(); - } - } - if (uiSchema["ui:field"]) { - type = uiSchema["ui:field"]; - } - if (uiSchema["ui:object"]) { - type = uiSchema["ui:object"]; - } - } - - // if there is no type then there is nothing to return - if (!type) return; - const objs = { - ...fieldTypes.collections.fields, - ...fieldTypes.simple.fields, - ...fieldTypes.advanced.fields, - ...hiddenFields, - }; - - return ( - - ); -}; - -PropertyKeyEditorForm.propTypes = { - schema: PropTypes.object, - uiSchema: PropTypes.object, - formData: PropTypes.object, - onChange: PropTypes.func, - optionsSchemaObject: PropTypes.object, - optionsUiSchemaObject: PropTypes.object, -}; - -export default PropertyKeyEditorForm; diff --git a/ui/cap-react/src/antd/admin/components/PropertyEditor.js b/ui/cap-react/src/antd/admin/components/PropertyEditor.js deleted file mode 100644 index b1d53f1b07..0000000000 --- a/ui/cap-react/src/antd/admin/components/PropertyEditor.js +++ /dev/null @@ -1,113 +0,0 @@ -import { useEffect, useState } from "react"; -import PropTypes from "prop-types"; -import { - Breadcrumb, - Button, - Col, - Grid, - Popconfirm, - Row, - Typography, -} from "antd"; -import { PageHeader } from "@ant-design/pro-layout"; -import Customize from "../containers/Customize"; -import { DeleteOutlined } from "@ant-design/icons"; - -const { useBreakpoint } = Grid; - -const renderPath = pathToUpdate => { - let prev; - let content; - const breadcrumbItems = []; - - let path = pathToUpdate.getIn(["path"]).toJS(); - - path && - path.map(item => { - if (breadcrumbItems.length == 0) { - if (item == "properties") content = "{ } root"; - else if (item == "items") content = "[ ] root"; - } else { - if (item == "properties") { - content = `{ } ${prev || ""}`; - prev = null; - } else if (item == "items") { - content = `[ ] ${prev || ""}`; - prev = null; - } else prev = item; - } - - if (!prev) breadcrumbItems.push({ title: content }); - }); - - if (prev) breadcrumbItems.push({ title: prev }); - - return ; -}; -const PropertyEditor = ({ path, renameId, enableCreateMode, deleteByPath }) => { - const [name, setName] = useState(); - const screens = useBreakpoint(); - - useEffect(() => { - if (path) { - const p = path.getIn(["path"]).toJS(); - if (p.length) { - setName(p.findLast(item => item !== "properties" && item != "items")); - } else { - setName("root"); - } - } - }, [path]); - - return ( -
- 0 && ( - { - deleteByPath(path.toJS()); - enableCreateMode(); - }} - > -
- ); -}; - -PropertyEditor.propTypes = { - path: PropTypes.object, - renameId: PropTypes.func, - enableCreateMode: PropTypes.func, - deleteByPath: PropTypes.func, -}; - -export default PropertyEditor; diff --git a/ui/cap-react/src/antd/admin/components/SchemaPreview.js b/ui/cap-react/src/antd/admin/components/SchemaPreview.js deleted file mode 100644 index ee9b188189..0000000000 --- a/ui/cap-react/src/antd/admin/components/SchemaPreview.js +++ /dev/null @@ -1,51 +0,0 @@ -import PropTypes from "prop-types"; -import { Button, Col, Row, Typography } from "antd"; -import SchemaTree from "../containers/SchemaTree"; -import { SettingOutlined } from "@ant-design/icons"; - -const SchemaPreview = ({ schema, selectProperty }) => { - return ( -
- - - - Schema tree - - - - - - {(schema && schema.get("title")) || "root"} - -
- ); -}; - -SchemaPreview.propTypes = { - schema: PropTypes.object, - selectProperty: PropTypes.func, -}; - -export default SchemaPreview; diff --git a/ui/cap-react/src/antd/admin/components/SchemaTree.js b/ui/cap-react/src/antd/admin/components/SchemaTree.js deleted file mode 100644 index 2e2d02402d..0000000000 --- a/ui/cap-react/src/antd/admin/components/SchemaTree.js +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from "prop-types"; -import Form from "../../forms/Form"; -import { transformSchema } from "../../partials/Utils/schema"; -import ObjectFieldTemplate from "../formComponents/ObjectFieldTemplate"; -import ArrayFieldTemplate from "../formComponents/ArrayFieldTemplate"; -import FieldTemplate from "../formComponents/FieldTemplate"; -import { _validate } from "../utils"; - -const SchemaTree = ({ schema, uiSchema }) => { - return ( - {}} - validate={_validate} - liveValidate - formContext={{ schema: [], uiSchema: [] }} - /> - ); -}; - -SchemaTree.propTypes = { - schema: PropTypes.object, - uiSchema: PropTypes.object, -}; - -export default SchemaTree; diff --git a/ui/cap-react/src/antd/admin/components/SchemaWizard.js b/ui/cap-react/src/antd/admin/components/SchemaWizard.js index a0051d33d3..d8547ee69f 100644 --- a/ui/cap-react/src/antd/admin/components/SchemaWizard.js +++ b/ui/cap-react/src/antd/admin/components/SchemaWizard.js @@ -1,57 +1,50 @@ import PropTypes from "prop-types"; -import HTML5Backend from "react-dnd-html5-backend"; -import { DndProvider } from "react-dnd"; import { Col, Row, Spin } from "antd"; -import PropertyEditor from "../containers/PropertyEditor"; -import SelectFieldType from "../containers/SelectFieldType"; -import SchemaPreview from "../containers/SchemaPreview"; -import FormPreview from "../containers/FormPreview"; +import { FormPreview, SchemaPreview, SelectOrEdit } from "react-formule"; -const SchemaWizard = ({ field, loader }) => { - if (loader) +const SchemaWizard = ({ loading }) => { + if (loading) return ( ); return ( - - - - {field ? : } - - - - - - - - - + + + + + + + + + + + ); }; diff --git a/ui/cap-react/src/antd/admin/components/SelectContentType.js b/ui/cap-react/src/antd/admin/components/SelectContentType.js index d4706de204..26d09a5ca9 100644 --- a/ui/cap-react/src/antd/admin/components/SelectContentType.js +++ b/ui/cap-react/src/antd/admin/components/SelectContentType.js @@ -2,9 +2,11 @@ import PropTypes from "prop-types"; import { Button, Col, Row, Space } from "antd"; import CheckableTag from "antd/es/tag/CheckableTag"; import { useState } from "react"; -import { PRIMARY_COLOR } from "../../utils"; import { EditOutlined, EyeOutlined } from "@ant-design/icons"; -const SelectContentType = ({ contentTypes, selectEdit, selectView }) => { +import { CMS_EDITOR, CMS_SCHEMA } from "../../routes"; +import { withRouter } from "react-router"; +import { PRIMARY_COLOR } from "../../utils/theme"; +const SelectContentType = ({ contentTypes, history }) => { const [selectedTag, setSelectedTag] = useState(); return ( @@ -31,7 +33,7 @@ const SelectContentType = ({ contentTypes, selectEdit, selectView }) => { - )} - {!editable && ( - - )} - - } - > - {addEnabled && ( - - )} - {!addEnabled ? ( - <> - - - Here you can manage access to your{" "} - - {schemaName} ({schemaVersion}) - {" "} - collection. You can determine who can perform specific - action for both states of your document (draft/published) - - - - - Actions: - read, - create, - update, - admin, - review - - - - {editable && ( - + updateSchemaConfig(data.formData)} /> - )} - - - ) : ( - - )} - - ) - } + + ), + }, + { + label: "Permissions", + key: "permissions", + children: ( + + {!addEnabled && ( + + )} + {!editable && ( + + )} + + } + > + {addEnabled && ( + + )} + {!addEnabled ? ( + <> + + + Here you can manage access to your{" "} + + {schemaName} ({schemaVersion}) + {" "} + collection. You can determine who can perform specific + action for both states of your document + (draft/published) + + + + + Actions: + read, + create, + update, + admin, + review + + + + {editable && ( + + )} + + + ) : ( + + )} + + ), + }, ]} /> - - + ); }; @@ -214,12 +203,14 @@ Permissions.propTypes = { createNotificationCategory: PropTypes.func, }; -const mapStateToProps = state => ({ - schemaName: state.schemaWizard.getIn(["config", "name"]), - schemaVersion: state.schemaWizard.getIn(["config", "version"]), - permissions: state.schemaWizard.getIn(["permissions"]), - config: state.schemaWizard.get("config"), -}); +const mapStateToProps = state => { + return { + schemaName: state.builder.get("formuleState").config.name, + schemaVersion: state.builder.get("formuleState").config.version, + permissions: state.builder.get("permissions"), + config: state.builder.get("config"), + }; +}; const mapDispatchToProps = dispatch => ({ getSchemaPermissions: (schema, version) => diff --git a/ui/cap-react/src/antd/admin/utils/fieldTypes.js b/ui/cap-react/src/antd/admin/utils/fieldTypes.js deleted file mode 100644 index 56a23d8dd8..0000000000 --- a/ui/cap-react/src/antd/admin/utils/fieldTypes.js +++ /dev/null @@ -1,1179 +0,0 @@ -import { - AimOutlined, - AppstoreOutlined, - BookOutlined, - BorderHorizontalOutlined, - BorderTopOutlined, - CalendarOutlined, - CheckSquareOutlined, - CloudDownloadOutlined, - ContainerOutlined, - FileOutlined, - FontSizeOutlined, - LayoutOutlined, - LinkOutlined, - NumberOutlined, - SwapOutlined, - TagOutlined, - UnorderedListOutlined, -} from "@ant-design/icons"; - -// COMMON / EXTRA PROPERTIES: - -const common = { - optionsSchema: { - title: { - type: "string", - title: "Title", - description: "Provide a title to be displayed for your field", - }, - description: { - title: "Description", - type: "string", - description: "Provide a description to be displayed for your field", - }, - }, - optionsUiSchema: { - type: "object", - title: "UI Schema", - properties: { - "ui:options": { - type: "object", - title: "UI Options", - properties: { - span: { - title: "Field Width", - type: "integer", - defaultValue: 24, - values: [6, 8, 12, 16, 18, 24], - labels: ["25%", "33%", "50%", "66%", "75%", "100%"], - }, - }, - }, - }, - }, - optionsUiSchemaUiSchema: { - "ui:options": { - span: { - "ui:widget": "slider", - }, - }, - }, -}; - -const extra = { - optionsSchema: { - readOnly: { - type: "boolean", - title: "Read-only", - }, - isRequired: { - title: "Required", - type: "boolean", - }, - }, - optionsSchemaUiSchema: { - readOnly: { - "ui:widget": "switch", - }, - isRequired: { - "ui:widget": "required", - }, - }, -}; - -// FIELDS: - -const collections = { - object: { - title: "Object", - icon:
{ }
, - description: "Data in JSON format, Grouped section", - className: "tour-object-field", - child: {}, - optionsSchema: { - type: "object", - title: "Object Schema", - properties: { - ...common.optionsSchema, - }, - }, - optionsSchemaUiSchema: {}, - optionsUiSchema: { - type: "object", - title: "UI Schema", - properties: { - "ui:options": { - type: "object", - title: "UI Options", - properties: { - ...common.optionsUiSchema.properties["ui:options"].properties, - hidden: { - type: "boolean", - title: "Do you want this field to be hidden?", - description: "If yes, this field will not be visible in the form", - }, - }, - }, - }, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "object", - properties: {}, - }, - uiSchema: {}, - }, - }, - array: { - title: "List", - icon: , - description: - "A list of things. List of strings, numbers, objects, references", - className: "tour-list-field", - child: {}, - optionsSchema: { - type: "object", - title: "Array Schema", - properties: { - ...common.optionsSchema, - }, - }, - optionsSchemaUiSchema: {}, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "array", - items: {}, - }, - uiSchema: {}, - }, - }, - accordionObjectField: { - title: "Accordion", - icon: , - description: "Data in JSON format, Grouped section", - child: {}, - optionsSchema: { - type: "object", - title: "Accordion Field Schema", - properties: { - ...common.optionsSchema, - }, - }, - optionsSchemaUiSchema: {}, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "object", - properties: {}, - }, - uiSchema: { - "ui:object": "accordionObjectField", - }, - }, - }, - tabView: { - title: "Tab", - icon: , - description: "Data in JSON format, Grouped section", - child: {}, - optionsSchema: { - type: "object", - title: "Tab Field Schema", - properties: { - ...common.optionsSchema, - }, - }, - optionsSchemaUiSchema: {}, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "object", - properties: {}, - }, - uiSchema: { - "ui:object": "tabView", - }, - }, - }, - layerObjectField: { - title: "Layer", - icon: , - description: "Data in JSON format, Grouped section", - child: {}, - optionsSchema: { - type: "object", - title: "Layer Field Schema", - properties: { - ...common.optionsSchema, - }, - }, - optionsSchemaUiSchema: {}, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "object", - properties: {}, - }, - uiSchema: { - "ui:object": "layerObjectField", - }, - }, - }, -}; - -const simple = { - importData: { - title: "Import Data", - icon: , - description: "Provided a URL or query", - child: {}, - optionsSchema: { - type: "object", - title: "File upload widget", - properties: { - ...common.optionsSchema, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - "properties": { - ...common.optionsUiSchema.properties, - "x-cap-import-data": { - type: "object", - properties: { - queryUrl: { - title: "Query URL", - description: "URL to query for and wait for response data", - type: "string", - placeholder: "/api/deposits" - }, - queryParam: { - type: "string", - title: "Query Param", - description: "URL to query for and wait for response data", - }, - hitTitle: { - type: "string", - title: "Item Title", - description: "What to display as result item title? Should be path of the response data, e.g 'metadata.general_title'", - }, - hitDescription: { - type: "string", - title: "Item Description", - description: "What to display as result item description? Should be path of the response data, e.g 'metadata.general_title'", - }, - resultsPath: { - type: "string", - title: "Results path", - description: "If response data is not an array, provide the path in the data where the results array exists. E.g 'hits.hits'", - }, - resultsTotalPath: { - type: "string", - title: "Results Total path", - description: "If results total is returned, specify the path in the response data. E.g 'hits.hits'", - }, - } - }, - } - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - "x-cap-import-data": { - queryUrl: { - "ui:placeholder": "/api/deposits" - }, - queryParam: { - "ui:placeholder": "q" - }, - hitTitle: { - "ui:placeholder": "metadata.general_title" - }, - hitDescription: { - "ui:placeholder": "created_by.email" - }, - resultsPath: { - "ui:placeholder": "hits.hits" - }, - } - }, - default: { - schema: { - type: "object", - }, - uiSchema: { - "ui:field": "importData", - }, - }, - }, - text: { - title: "Text", - icon: , - description: "Titles, names, paragraphs, IDs, list of names", - className: "tour-text-field", - child: {}, - optionsSchema: { - type: "object", - title: "Text Schema", - properties: { - ...common.optionsSchema, - pattern: { - title: "Validation regex", - description: - "The input will be validated against this regex on form submission", - type: "string", - format: "regex", - }, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - - pattern: { - "ui:placeholder": "^.*$", - }, - }, - optionsUiSchema: { - type: "object", - title: "UI Schema", - properties: { - "ui:options": { - type: "object", - title: "UI Options", - properties: { - ...common.optionsUiSchema.properties["ui:options"].properties, - suggestions: { - type: "string", - title: "Add a suggestion URL endpoint", - description: "Provide an URL endpoint, to fetch data from there", - }, - convertToUppercase: { - type: "boolean", - title: "Convert input to uppercase", - }, - mask: { - type: "string", - title: "Input mask", - description: - "Add a mask to visualize and limit the format of the input. Use the following format: `0` (number), `a` (lowercase letter), `A` (uppercase letter), `*` (letter or number). You can escape all these with `\\`. The rest of the characters will be treated as constants", - }, - }, - }, - }, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - "ui:options": { - ...common.optionsUiSchemaUiSchema["ui:options"], - mask: { - "ui:placeholder": "BN-000/aa", - "ui:options": { - descriptionIsMarkdown: true, - }, - }, - }, - }, - default: { - schema: { - type: "string", - }, - uiSchema: { - "ui:widget": "text", - }, - }, - }, - textarea: { - title: "Text area", - icon: , - description: "Text Area field", - child: {}, - optionsSchema: { - type: "object", - title: "TextArea Schema", - properties: { - ...common.optionsSchema, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - type: "object", - title: "UI Schema", - properties: { - "ui:options": { - type: "object", - title: "UI Options", - properties: { - ...common.optionsUiSchema.properties["ui:options"].properties, - rows: { - title: "Rows", - description: "The number of rows in the textarea", - type: "number", - }, - maxLength: { - title: "Max Length", - description: "Infinity if not provided", - type: "number", - }, - minLength: { - title: "Min Length", - description: "Empty if not provided", - type: "number", - }, - placeholder: { - title: "Placeholder", - description: "Provide a placeholder for the field", - type: "string", - }, - }, - }, - }, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "string", - }, - uiSchema: { - "ui:widget": "textarea", - }, - }, - }, - number: { - title: "Number", - icon: , - description: "IDs, order number, rating, quantity", - child: {}, - optionsSchema: { - type: "object", - title: "Number Schema", - properties: { - ...common.optionsSchema, - type: { - title: "Type of the number", - type: "string", - oneOf: [ - { const: "integer", title: "Integer" }, - { const: "number", title: "Float" }, - ], - }, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "number", - }, - uiSchema: {}, - }, - }, - checkbox: { - title: "Checkbox", - icon: , - description: "IDs, order number, rating, quantity", - child: {}, - optionsSchema: { - type: "object", - title: "Checkbox Schema", - properties: { - ...common.optionsSchema, - type: { - title: "Type", - type: "string", - oneOf: [ - { const: "boolean", title: "One Option" }, - { const: "array", title: "Multiple Options" }, - ], - }, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - dependencies: { - type: { - oneOf: [ - { - properties: { - type: { - enum: ["boolean"], - }, - checkedValue: { - title: "Returned value when checked", - description: "Default: true", - type: "string", - }, - uncheckedValue: { - title: "Returned value when unchecked", - description: "Default: false", - type: "string", - }, - }, - }, - { - properties: { - type: { - enum: ["array"], - }, - items: { - title: "Define your options", - type: "object", - description: "The options for the widget", - properties: { - enum: { - title: "Options List", - type: "array", - items: { - title: "Option", - type: "string", - }, - }, - }, - }, - }, - }, - ], - }, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "boolean", - items: { - type: "string", - enum: ["Option A", "Option B"], - }, - uniqueItems: true, - }, - uiSchema: { - "ui:widget": "checkbox", - }, - }, - }, - switch: { - title: "Switch", - icon: , - description: "IDs, order number, rating, quantity", - child: {}, - optionsSchema: { - type: "object", - title: "Switch Schema", - properties: { - ...common.optionsSchema, - type: { - type: "string", - title: "Type of the returned value", - description: "Define the type of the returned value", - oneOf: [ - { const: "boolean", title: "Boolean" }, - { const: "string", title: "String" }, - { const: "number", title: "Number" }, - ], - }, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - type: "object", - title: "UI Schema", - properties: { - "ui:options": { - type: "object", - title: "UI Options", - properties: { - ...common.optionsUiSchema.properties["ui:options"].properties, - falseToUndefined: { - type: "boolean", - title: "Do you want to return undefined instead of false?", - description: - "In some cases the returned value is preferred to be undefined than false", - }, - }, - }, - }, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "boolean", - }, - uiSchema: { - "ui:widget": "switch", - }, - }, - }, - radio: { - title: "Radio", - icon: , - description: "IDs, order number, rating, quantity", - child: {}, - optionsSchema: { - type: "object", - title: "Radio Schema", - properties: { - ...common.optionsSchema, - enum: { - title: "Define your options", - type: "array", - description: "The options for the radio widget", - items: { - title: "Radio Option", - type: "string", - }, - }, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "string", - enum: ["Option A", "Option B"], - }, - uiSchema: { - "ui:widget": "radio", - }, - }, - }, - select: { - title: "Select", - icon: , - description: "IDs, order number, rating, quantity", - child: {}, - optionsSchema: { - type: "object", - title: "Select Schema", - properties: { - ...common.optionsSchema, - type: { - title: "Type", - type: "string", - oneOf: [ - { const: "string", title: "Select one value (text)" }, - { const: "number", title: "Select one value (number)" }, - { const: "array", title: "Select multiple values" }, - ], - }, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - allOf: [ - { - if: { - properties: { - type: { - const: "string", - }, - }, - }, - then: { - properties: { - enum: { - title: "Define your options", - type: "array", - description: "The options for the widget", - items: { - title: "Option", - type: "string", - }, - }, - }, - }, - }, - { - if: { - properties: { - type: { - const: "number", - }, - }, - }, - then: { - properties: { - enum: { - title: "Define your options", - type: "array", - description: "The options for the widget", - items: { - title: "Option", - type: "number", - }, - }, - }, - }, - }, - { - if: { - properties: { - type: { - const: "array", - }, - }, - }, - then: { - properties: { - items: { - title: "Define your options", - type: "object", - properties: { - enum: { - title: "Options List", - type: "array", - items: { - title: "Option", - type: "string", - }, - }, - }, - }, - }, - }, - }, - ], - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "string", - uniqueItems: true, - }, - uiSchema: { - "ui:widget": "select", - }, - }, - }, - date: { - title: "Date", - icon: , - description: "Date", - child: {}, - optionsSchema: { - type: "object", - title: "Date Schema", - properties: { - ...common.optionsSchema, - format: { - type: "string", - title: "Type", - enum: ["date", "date-time"], - default: "date", - }, - customFormat: { - type: "string", - title: "Format", - description: - "Define the date format ([help](https://day.js.org/docs/en/display/format#list-of-all-available-formats)). Remember to include the time in the format if you have selected `date-time` as type", - }, - minDate: { - type: "string", - title: "Minimum date allowed", - }, - maxDate: { - type: "string", - title: "Maximum date allowed", - }, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - customFormat: { - "ui:placeholder": "DD/MM/YYYY", - "ui:options": { - descriptionIsMarkdown: true, - }, - }, - minDate: { - "ui:widget": "date", - }, - maxDate: { - "ui:widget": "date", - }, - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "string", - }, - uiSchema: { - "ui:widget": "date", - }, - }, - }, -}; - -const advanced = { - uri: { - title: "URI", - icon: , - description: "Add uri text", - child: {}, - optionsSchema: { - type: "object", - title: "Uri Schema", - properties: { - ...common.optionsSchema, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - type: "object", - title: "UI Schema", - properties: { - "ui:options": { - type: "object", - title: "UI Options", - properties: { - ...common.optionsUiSchema.properties["ui:options"].properties, - suggestions: { - type: "string", - title: "Add a suggestion URL endpoint", - description: "Provide an URL endpoint, to fetch data from there", - }, - }, - }, - }, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "string", - format: "uri", - }, - }, - }, - richeditor: { - title: "Rich editor", - icon: , - description: "Rich Editor Field", - child: {}, - optionsSchema: { - type: "object", - title: "Rich Editor Schema", - properties: { - ...common.optionsSchema, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "string", - }, - uiSchema: { - "ui:widget": "richeditor", - }, - }, - }, - idFetcher: { - title: "ID fetcher", - icon: , - description: "Fetch data from ZENODO, ORCiD or ROR", - child: {}, - optionsSchema: { - type: "object", - title: "ID Fetcher Field Schema", - properties: { - ...common.optionsSchema, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - type: "object", - title: "UI Schema", - properties: { - ...common.optionsUiSchema.properties, - "ui:servicesList": { - title: "Select the services you want to allow", - type: "array", - items: { - type: "string", - oneOf: [ - { const: "orcid", title: "ORCiD" }, - { const: "ror", title: "ROR" }, - { const: "zenodo", title: "Zenodo" }, - { const: "capRecords", title: "CAP Records" }, - { const: "capDeposits", title: "CAP Deposits" }, - ], - }, - uniqueItems: "true", - }, - }, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - "ui:servicesList": { - "ui:widget": "checkbox", - }, - }, - default: { - schema: { - type: "object", - properties: {}, - }, - uiSchema: { - "ui:serfvicesList": ["orcid", "ror", "zenodo"], - "ui:servicesList": ["capDeposits"], - "ui:field": "idFetcher", - }, - }, - }, - tags: { - title: "Tags", - icon: , - description: "Add keywords, tags, etc", - child: {}, - optionsSchema: { - title: "Tags Schema", - type: "object", - properties: { - ...common.optionsSchema, - tagPattern: { - type: "string", - title: "Pattern", - description: "Provide a regex for your pattern", - }, - tagPatternErrorMessage: { - type: "string", - title: "Pattern error message", - description: - "Provide a message to display when the input does not match the pattern", - }, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "array", - items: { - type: "string", - }, - }, - uiSchema: { - "ui:field": "tags", - }, - }, - }, - CapFiles: { - title: "File upload", - icon: , - description: "Upload Files", - child: {}, - optionsSchema: { - type: "object", - title: "File upload widget", - properties: { - ...common.optionsSchema, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "string", - }, - uiSchema: { - "ui:field": "CapFiles", - }, - }, - }, -}; - -// HIDDEN FIELDS (not directly selectable by the user): - -export const hiddenFields = { - integer: { - title: "Integer", - icon: , - description: "IDs, order number, rating, quantity", - child: {}, - optionsSchema: { - type: "object", - title: "Integer Schema", - properties: { - ...common.optionsSchema, - type: { - title: "Type of the number", - type: "string", - oneOf: [ - { const: "integer", title: "Integer" }, - { const: "number", title: "Float" }, - ], - }, - readOnly: extra.optionsSchema.readOnly, - isRequired: extra.optionsSchema.isRequired, - }, - }, - optionsSchemaUiSchema: { - readOnly: extra.optionsSchemaUiSchema.readOnly, - isRequired: extra.optionsSchemaUiSchema.isRequired, - }, - optionsUiSchema: { - ...common.optionsUiSchema, - }, - optionsUiSchemaUiSchema: { - ...common.optionsUiSchemaUiSchema, - }, - default: { - schema: { - type: "integer", - }, - uiSchema: {}, - }, - }, -}; - -const fields = { - collections: { - title: "Collections", - description: "", - fields: collections, - className: "tour-collections", - }, - simple: { - title: "Fields", - description: "", - fields: simple, - }, - advanced: { - title: "Advanced fields", - description: "", - fields: advanced, - }, -}; - -export default fields; diff --git a/ui/cap-react/src/antd/admin/utils/index.js b/ui/cap-react/src/antd/admin/utils/index.js index e08b2b7c1b..31c8d91119 100644 --- a/ui/cap-react/src/antd/admin/utils/index.js +++ b/ui/cap-react/src/antd/admin/utils/index.js @@ -1,3 +1,8 @@ +import { initFormuleSchema } from "react-formule"; +import { merge } from "lodash-es"; +import store from "../../../store/configureStore"; +import { updateSchemaConfig } from "../../../actions/builder"; + export const SIZE_OPTIONS = { xsmall: 8, small: 12, @@ -6,6 +11,15 @@ export const SIZE_OPTIONS = { xlarge: 24, }; +const NOTIFICATIONS = { + notifications: { + actions: { + review: [], + publish: [], + }, + }, +}; + export const slugify = text => { return text .toString() @@ -17,55 +31,14 @@ export const slugify = text => { .replace(/-+$/, ""); // Trim - from end of text }; -export const _initSchemaStructure = ( - name = "New schema", - description = "" -) => ({ - schema: { - title: name, - description: description, - type: "object", - properties: {}, - }, - uiSchema: {}, -}); - -export const _addSchemaToLocalStorage = (_id, name, description) => { - let availableSchemas = localStorage.getItem("availableSchemas") || "{}"; - let newAvailableSchemas = Object.assign(availableSchemas, { - [_id]: _initSchemaStructure(name, description), - }); - - localStorage.setItem("availableSchemas", JSON.stringify(newAvailableSchemas)); - - return newAvailableSchemas; +export const initFormuleSchemaWithNotifications = ( + data = {}, + name, + description +) => { + data.config = merge(data.config || {}, NOTIFICATIONS); + initFormuleSchema(data, name, description); + /* eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ + const { deposit_schema, deposit_options, ...configs } = data; + store.dispatch(updateSchemaConfig(configs)); }; - -let _addErrors = (errors, path) => { - errors.addError({ schema: path.schema, uiSchema: path.uiSchema }); - - Object.keys(errors).map(error => { - if (error != "__errors" && error != "addError") { - _addErrors(errors[error], { - schema: [...path.schema, "properties", error], - uiSchema: [...path.uiSchema, error], - }); - } - }); - return errors; -}; -export const _validate = function (formData, errors) { - return _addErrors(errors, { schema: [], uiSchema: [] }); -}; - -export const shoudDisplayGuideLinePopUp = schema => { - return schema.get("properties") && schema.get("properties").size === 0; -}; - -export const isItTheArrayField = (schema, uiSchema) => { - return ( - schema.type === "array" && !uiSchema["ui:field"] && !uiSchema["ui:widget"] - ); -}; - -export const timer = ms => new Promise(res => setTimeout(res, ms)); diff --git a/ui/cap-react/src/antd/admin/utils/tour/admin.js b/ui/cap-react/src/antd/admin/utils/tour/admin.js index fe1b9cee33..72a320a386 100644 --- a/ui/cap-react/src/antd/admin/utils/tour/admin.js +++ b/ui/cap-react/src/antd/admin/utils/tour/admin.js @@ -63,14 +63,14 @@ export const steps = [ "You can see the JSON schema representation of your form and a diff view of all the current unsaved changes here.", }, { - target: ".tour-schema-settings", + target: ".tour-notifications-tab", content: - "On a new schema, you will have to provide some settings like id, version, name or experiment before being able to save your changes.", + "The notifications tab allows you create notification templates and define patterns to send them to the appropriate users when an event takes place.", }, { - target: ".tour-notifications-tab", + target: ".tour-settings-tab", content: - "The notifications tab allows you create notification templates and define patterns to send them to the appropriate users when an event takes place.", + "On a new schema, you will have to provide some settings like id, version, name or experiment before being able to save your changes. Here you can also define permissions for the schema.", }, { target: "body", diff --git a/ui/cap-react/src/antd/collection/Collection.js b/ui/cap-react/src/antd/collection/Collection.js index 4e4fc29444..57d6cc69d3 100644 --- a/ui/cap-react/src/antd/collection/Collection.js +++ b/ui/cap-react/src/antd/collection/Collection.js @@ -1,7 +1,7 @@ import { fetchRecordsResults } from "../../actions/common"; import DashboardList from "../dashboard/components/DashboardList"; import { _getCollectionList } from "../dashboard/utils"; -import RichEditorWidget from "../forms/widgets/RichEditorWidget"; +import RichEditorWidget from "../forms/RichEditorWidget"; import ErrorScreen from "../partials/Error"; import CollectionPermissions from "./CollectionPermissions"; import { CloseOutlined, EditOutlined, SaveOutlined } from "@ant-design/icons"; @@ -165,11 +165,11 @@ const Collection = ({ )) } - bodyStyle={{ + style={{ body: { padding: "1px 0 0 0", maxHeight: "300px", overflowY: "auto", - }} + }}} > {readme || editing ? ( { +const CollectionPermissionsColumn = ({ + e, + item, + type, + handlePermissions, + editable = false, +}) => { const [draftIncluded, setDraftIncluded] = useState( - e.includes(`deposit-schema-${type}`) + e.includes(`deposit-schema-${type}`) ); const [pubIncluded, setPubIncluded] = useState( e.includes(`record-schema-${type}`) ); const requestPermissionUpdate = (rec_type, checked) => { - handlePermissions(item.email, [`${rec_type}-schema-${type}`], item.type, checked ? "add": "delete"); + handlePermissions( + item.email, + [`${rec_type}-schema-${type}`], + item.type, + checked ? "add" : "delete" + ); rec_type == "record" ? setPubIncluded(checked) : setDraftIncluded(checked); }; diff --git a/ui/cap-react/src/antd/drafts/components/DraftItemNav/DraftItemNav.js b/ui/cap-react/src/antd/drafts/components/DraftItemNav/DraftItemNav.js index 9531f0c09d..6011f22abf 100644 --- a/ui/cap-react/src/antd/drafts/components/DraftItemNav/DraftItemNav.js +++ b/ui/cap-react/src/antd/drafts/components/DraftItemNav/DraftItemNav.js @@ -18,7 +18,7 @@ const DraftItemNav = ({ placement="left" onClose={onClose} open={visibleMenuDrawer} - bodyStyle={{ padding: 0 }} + style={{ body: { padding: 0 }}} key="menu" > diff --git a/ui/cap-react/src/antd/drafts/components/Editor/Editor.js b/ui/cap-react/src/antd/drafts/components/Editor/Editor.js index 26ad218cc3..5395fdfe7b 100644 --- a/ui/cap-react/src/antd/drafts/components/Editor/Editor.js +++ b/ui/cap-react/src/antd/drafts/components/Editor/Editor.js @@ -4,15 +4,13 @@ import { Col, Layout } from "antd"; import Error from "../../../partials/Error/"; import { transformSchema } from "../../../partials/Utils/schema"; import Header from "../../containers/EditorHeader"; -import Form from "../../../forms"; import { canEdit } from "../../utils/permissions"; -import { debounce } from "lodash-es"; +import { FormuleForm } from "react-formule"; const Editor = ({ schemaErrors, schemas = { schema: {}, uiSchema: {} }, formData, - formDataChange, extraErrors, formRef, history, @@ -29,18 +27,16 @@ const Editor = ({ let _schema = schemas && schemas.schema ? transformSchema(schemas.schema) : null; - const _formDataChange = change => formDataChange(change.formData); return (
- {}} > diff --git a/ui/cap-react/src/antd/drafts/components/SideBar/DraftSideBar.js b/ui/cap-react/src/antd/drafts/components/SideBar/DraftSideBar.js index 88cab1e470..f99fabe55c 100644 --- a/ui/cap-react/src/antd/drafts/components/SideBar/DraftSideBar.js +++ b/ui/cap-react/src/antd/drafts/components/SideBar/DraftSideBar.js @@ -13,7 +13,7 @@ const DraftSideBar = ({ visibleFileDrawer, onClose }) => { open={visibleFileDrawer} key="fileSettings" size="large" - bodyStyle={{ padding: "5px" }} + style={{ body: { padding: "5px" } }} contentWrapperStyle={{ width: !screens.lg ? "80%" : "40%" }} > diff --git a/ui/cap-react/src/antd/drafts/components/SideBar/SideBar.js b/ui/cap-react/src/antd/drafts/components/SideBar/SideBar.js index cb4295acc0..15aab5dbdd 100644 --- a/ui/cap-react/src/antd/drafts/components/SideBar/SideBar.js +++ b/ui/cap-react/src/antd/drafts/components/SideBar/SideBar.js @@ -1,11 +1,11 @@ import { useState } from "react"; import PropTypes from "prop-types"; import { Button, Row, Space, Tag, Typography, Descriptions, Card } from "antd"; -import { Link, Route } from "react-router-dom"; +import { Link } from "react-router-dom"; import { PlusOutlined, ReloadOutlined } from "@ant-design/icons"; import DepositFilesList from "../../../partials/FileList"; import { canEdit } from "../../utils/permissions"; -import { DRAFT_ITEM, COLLECTION_BASE } from "../../../routes"; +import { COLLECTION_BASE } from "../../../routes"; import Timeago from "react-timeago"; import FileManager from "../../containers/FileManager"; @@ -96,33 +96,28 @@ const SideBar = ({ ( - - - - )} - {uiEmail && ( - - - )} - - ); -}; - -EmptyArrayField.propTypes = { - items: PropTypes.array, - onAddClick: PropTypes.func, - disabled: PropTypes.bool, - readonly: PropTypes.bool, - canAdd: PropTypes.bool, -}; - -export default EmptyArrayField; diff --git a/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/FixedArrayFieldTemplate.js b/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/FixedArrayFieldTemplate.js deleted file mode 100644 index c3a2f46d3c..0000000000 --- a/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/FixedArrayFieldTemplate.js +++ /dev/null @@ -1,87 +0,0 @@ -import { Row, Col, Button } from "antd"; -import PlusCircleOutlined from "@ant-design/icons/PlusCircleOutlined"; -import PropTypes from "prop-types"; -import ArrayFieldTemplateItem from "./ArrayFieldTemplateItem"; -import FieldHeader from "../Field/FieldHeader"; - -const FixedArrayFieldTemplate = ({ - canAdd, - className, - disabled, - formContext, - idSchema, - items, - options, - onAddClick, - readonly, - schema, - title, - uiSchema, -}) => { - const { rowGutter = 24 } = formContext; - - return ( -
- - - - - - - {items && - items.map((itemProps, index) => ( - - ))} - - - - - {canAdd && !readonly && ( - - - - - - - - )} - -
- ); -}; - -FixedArrayFieldTemplate.propTypes = { - canAdd: PropTypes.bool, - className: PropTypes.string, - disabled: PropTypes.bool, - formContext: PropTypes.object, - idSchema: PropTypes.object, - items: PropTypes.array, - onAddClick: PropTypes.func, - prefixCls: PropTypes.string, - readonly: PropTypes.bool, - required: PropTypes.bool, - schema: PropTypes.object, - title: PropTypes.string, - uiSchema: PropTypes.object, -}; - -export default FixedArrayFieldTemplate; diff --git a/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/ImportListModal.js b/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/ImportListModal.js deleted file mode 100644 index 5dd695772f..0000000000 --- a/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/ImportListModal.js +++ /dev/null @@ -1,219 +0,0 @@ -import { useEffect, useState } from "react"; -import PropTypes from "prop-types"; -import { Checkbox, Input, List, Modal, Tabs, Typography } from "antd"; -import axios from "../../../../axios"; -import { timer } from "../../../admin/utils"; - -const ImportListModal = ({ - open, - onCancel, - uiImport, - schema, - onAddClick, - formData, - formItems, -}) => { - const { - description, - listSuggestions, - placeholder, - to, - delimiter = "\n", - } = uiImport; - - const [fetchedResults, setFetchedResults] = useState(null); - const [currentIndex, setCurrentIndex] = useState(null); - const [data, setData] = useState(null); - - useEffect(() => { - if (currentIndex) { - formItems[currentIndex.index].children.props.onChange(currentIndex.value); - } - }, [currentIndex]); - - const updateAll = (items = [], add = true) => { - let updated = items.map(item => item + "\n"); - if (!data) setData(updated.join(",").replace(/,/g, "")); - else { - if (!add) { - setData(data => [ - ...data, - updated - .filter(item => !data.includes(item)) - .join(",") - .replace(/,/g, ""), - ]); - } else { - let clips = data; - updated.map(item => { - if (data.includes(item)) { - clips = clips.replace(item, ""); - } - }); - setData(clips); - } - } - }; - const fetchSuggestions = async val => { - try { - const { data } = await axios.get(listSuggestions + val); - setFetchedResults(data); - updateAll(data, true); - } catch (err) { - // - } - }; - - const populateItems = async (values, type) => { - for (let [index, value] of values.entries()) { - let _index = formData.length + index; - if (type == "object" && to) { - value = { [to]: value }; - } - setCurrentIndex({ - index: _index, - value, - }); - await timer(1); - } - }; - - const addItem = async (values, type) => { - // eslint-disable-next-line no-unused-vars - for (const _ of values) { - onAddClick(); - await timer(1); - } - populateItems(values, type); - }; - - const _batchImport = () => { - let values = []; - if (!data) return; - - // Replace multiple spaces with one - let value = data.replace(/ +(?= )/g, ""); - // Trim whitespaces from beginning/end - value = value.trim(); - // Remove empty lines - value = value.replace(/^\s*[\r\n]/gm, ""); - - // Split string depending on the delimiter passed in the uiOptionns - values = value.split(delimiter); - - // Get form configurations/options - let { items: { type } = {} } = schema; - - if (Array.isArray(values)) { - addItem(values, type); - } - - onCancel(); - }; - return ( - { - onCancel(); - setData(null); - setFetchedResults(null); - }} - open={open} - title="Provide a pattern to fetch available paths" - okButtonProps={{ - disabled: !data, - onClick: () => { - _batchImport(); - setData(null); - setFetchedResults(null); - }, - }} - okText="Import" - destroyOnClose - > - - { - e.target.value == "" && - fetchedResults && - setFetchedResults(null); - updateAll(fetchedResults || [], false); - }} - onSearch={fetchSuggestions} - /> - {fetchedResults && ( - ( - - { - if (!data) setData(item + "\n"); - else { - data.includes(item) - ? setData(data => - data.replace(item + "\n", "") - ) - : setData(data => data + item + "\n"); - } - }} - > - {item} - - } - /> - - )} - /> - )} - - ), - }, - { - key: "2", - label: "Add list manually", - children: ( - <> - - {description || - "Paste your list here. Insert one item per line:"} - - setData(e.target.value)} - /> - - ), - }, - ]} - /> - - ); -}; - -ImportListModal.propTypes = { - open: PropTypes.bool, - onCancel: PropTypes.func, - uiImport: PropTypes.object, - schema: PropTypes.object, - formItems: PropTypes.object, - formData: PropTypes.object, - onAddClick: PropTypes.func, -}; - -export default ImportListModal; diff --git a/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/LayerArrayFieldTemplate.js b/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/LayerArrayFieldTemplate.js deleted file mode 100644 index fce4317b79..0000000000 --- a/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/LayerArrayFieldTemplate.js +++ /dev/null @@ -1,164 +0,0 @@ -import { useEffect, useState } from "react"; -import PropTypes from "prop-types"; -import { Button, Col, List, Modal, Row, Typography } from "antd"; - -import * as Sqrl from "squirrelly"; - -import ArrowUpOutlined from "@ant-design/icons/ArrowUpOutlined"; -import ArrowDownOutlined from "@ant-design/icons/ArrowDownOutlined"; -import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; -import ErrorFieldIndicator from "../../error/ErrorFieldIndicator"; - -const LayerArrayFieldTemplate = ({ items = [] }) => { - const [itemToDisplay, setItemToDisplay] = useState(null); - const [visible, setVisible] = useState(false); - - const stringifyItem = (options, item) => { - let stringifyTmpl = options ? options.stringifyTmpl : null; - if (stringifyTmpl) { - try { - let str = Sqrl.render(stringifyTmpl, item); - return str; - } catch (_err) { - return null; - } - } - - const stringify = options ? options.stringify : [], - reducer = (acc, val) => (item[val] ? `${acc} ${item[val]}` : acc); - - return stringify ? stringify.reduce(reducer, "") : null; - }; - useEffect(() => { - if (items && itemToDisplay) - setItemToDisplay({ - index: itemToDisplay.index, - children: items[itemToDisplay.index].children, - }); - }, [items]); - - const getActionsButtons = item => { - if (!item.hasToolbar) return []; - - return [ - - {(item.hasMoveUp || item.hasMoveDown) && ( - - - - -
- - } - > - - - )} - {uiImport && ( - setImportModal(false)} - /> - )} - {uiEmail && formData && ( - setEmailModal(false)} - title="Select user & egroups emails to send" - okText="Send Email" - okType="link" - okButtonProps={{ - href: `mailto:${uiEmailDefaults - .concat(selectedEmailList) - .join(",")}`, - }} - width={900} - > - - updateEmailSelectedListAll()} - checked={formData.length === selectedEmailList.length} - > - Select all - - {uiEmailDefaults.length > 0 ? ( - - Default email recepients:{" "} - - {uiEmailDefaults.map(i => ( - {i} - ))} - - - ) : null} - i.profile || i)} - columns={[ - { - title: "Email User", - key: "action", - render: (_, user) => ( - updateEmailSelectedList(user.email)} - /> - ), - }, - { - title: "Name", - dataIndex: "name", - key: "name", - }, - { - title: "Email", - dataIndex: "email", - key: "email", - render: txt => {txt}, - }, - { - title: "Department", - dataIndex: "department", - key: "department", - render: txt => {txt}, - }, - ]} - /> - - - )} - -
- {title && ( -
- _enableLatex()} - enableImport={() => setImportModal(true)} - enableEmail={() => setEmailModal(true)} - /> - - )} - - - - - {items && ( - - {items.length > 0 ? ( - getArrayContent(typeOfArrayToDisplay) - ) : ( - - )} - - )} - - - {items && items.length > 0 && canAdd && !readonly && ( - - - - - - - - )} - - - ); -}; - -NormalArrayFieldTemplate.propTypes = { - canAdd: PropTypes.bool, - className: PropTypes.string, - disabled: PropTypes.bool, - formContext: PropTypes.object, - idSchema: PropTypes.object, - items: PropTypes.array, - onAddClick: PropTypes.func, - prefixCls: PropTypes.string, - readonly: PropTypes.bool, - required: PropTypes.bool, - schema: PropTypes.object, - title: PropTypes.string, - uiSchema: PropTypes.object, - formData: PropTypes.object, -}; - -export default NormalArrayFieldTemplate; diff --git a/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/index.js b/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/index.js deleted file mode 100644 index 33a6f146b2..0000000000 --- a/ui/cap-react/src/antd/forms/templates/ArrayFieldTemplates/index.js +++ /dev/null @@ -1,185 +0,0 @@ -import { getDefaultRegistry } from "@rjsf/core"; -import { - getUiOptions, - getWidget, - isFilesArray, - isFixedItems, - isMultiSelect, - optionsList, - retrieveSchema, -} from "@rjsf/utils"; -import PropTypes from "prop-types"; -import FixedArrayFieldTemplate from "./FixedArrayFieldTemplate"; -import NormalArrayFieldTemplate from "./NormalArrayFieldTemplate"; - -const ArrayFieldTemplate = ({ - autofocus, - canAdd, - className, - disabled, - formContext, - formData, - idSchema, - items, - label, - name, - onAddClick, - onBlur, - onChange, - onFocus, - placeholder, - rawErrors, - readonly, - registry = getDefaultRegistry(), - required, - schema, - title, - uiSchema, -}) => { - const { fields, rootSchema, widgets } = registry; - const { UnsupportedField } = fields; - - const renderFiles = () => { - const { widget = "files", ...options } = getUiOptions(uiSchema); - - const Widget = getWidget(schema, widget, widgets); - - return ( - - ); - }; - - const renderMultiSelect = () => { - const itemsSchema = retrieveSchema(schema.items, rootSchema, formData); - const enumOptions = optionsList(itemsSchema); - const { widget = "select", ...options } = { - ...getUiOptions(uiSchema), - enumOptions, - }; - - const Widget = getWidget(schema, widget, widgets); - - return ( - - ); - }; - - if (!Object.prototype.hasOwnProperty.call(schema, "items")) { - return ( - - ); - } - - if (isFixedItems(schema)) { - const { ...options } = getUiOptions(uiSchema); - return ( - - ); - } - if (isFilesArray(schema, uiSchema, rootSchema)) { - return renderFiles(); - } - if (isMultiSelect(schema, rootSchema)) { - return renderMultiSelect(); - } - - const { ...options } = getUiOptions(uiSchema); - - return ( - - ); -}; -ArrayFieldTemplate.propTypes = { - autofocus: PropTypes.bool, - canAdd: PropTypes.bool, - className: PropTypes.string, - disabled: PropTypes.bool, - formContext: PropTypes.object, - formData: PropTypes.object, - idSchema: PropTypes.object, - items: PropTypes.array, - label: PropTypes.string, - name: PropTypes.string, - onAddClick: PropTypes.func, - onBlur: PropTypes.func, - onChange: PropTypes.func, - onFocus: PropTypes.func, - placeholder: PropTypes.string, - rawErrors: PropTypes.object, - readonly: PropTypes.bool, - registry: PropTypes.object, - required: PropTypes.bool, - schema: PropTypes.object, - title: PropTypes.string, - uiSchema: PropTypes.object, -}; -export default ArrayFieldTemplate; diff --git a/ui/cap-react/src/antd/forms/templates/Field/FieldHeader.js b/ui/cap-react/src/antd/forms/templates/Field/FieldHeader.js deleted file mode 100644 index 2c3d90539a..0000000000 --- a/ui/cap-react/src/antd/forms/templates/Field/FieldHeader.js +++ /dev/null @@ -1,46 +0,0 @@ -import PropTypes from "prop-types"; -import { Space, Typography } from "antd"; -import Markdown from "../../../partials/Markdown"; -import TitleField from "../../fields/internal/TitleField"; - -const FieldHeader = ({ label, description, uiSchema, isObject, idSchema }) => { - return ( - - {uiSchema["ui:title"] !== false && label && ( - - )} - - {description && ( - - )} - - - ); -}; - -FieldHeader.propTypes = { - displayLabel: PropTypes.bool, - label: PropTypes.string, - uiSchema: PropTypes.object, - description: PropTypes.node, - isObject: PropTypes.bool, - idSchema: PropTypes.object, -}; - -export default FieldHeader; diff --git a/ui/cap-react/src/antd/forms/templates/Field/FieldTemplate.js b/ui/cap-react/src/antd/forms/templates/Field/FieldTemplate.js deleted file mode 100644 index ce55045d2d..0000000000 --- a/ui/cap-react/src/antd/forms/templates/Field/FieldTemplate.js +++ /dev/null @@ -1,127 +0,0 @@ -import Form from "antd/lib/form"; -import PropTypes from "prop-types"; -import FieldHeader from "./FieldHeader"; - -import WrapIfAdditional from "./WrapIfAdditional"; -import { Col, Row } from "antd"; -import { SIZE_OPTIONS } from "../../../admin/utils"; - -const VERTICAL_LABEL_COL = { span: 24 }; -const VERTICAL_WRAPPER_COL = { span: 24 }; - -const FieldTemplate = ({ - children, - classNames, - disabled, - displayLabel, - formContext, - help, - hidden, - id, - label, - onDropPropertyClick, - onKeyChange, - rawErrors, - rawHelp, - readonly, - required, - schema, - uiSchema = {}, - rawDescription, -}) => { - const { - colon, - labelCol = VERTICAL_LABEL_COL, - wrapperCol = VERTICAL_WRAPPER_COL, - wrapperStyle, - } = formContext; - - if (hidden) { - return
{children}
; - } - - const renderFieldErrors = () => - [...new Set(rawErrors)].map(error => ( -
{error}
- )); - - const { ["ui:options"]: uiOptions = {} } = uiSchema; - - let content = ( - - {id === "root" ? ( - children - ) : ( - - ) - } - labelCol={labelCol} - required={required} - style={wrapperStyle} - validateStatus={rawErrors ? "error" : undefined} - wrapperCol={wrapperCol} - tooltip={schema.tooltip} - > - {children} - - )} - - ); - - if (id != "root" || uiSchema["ui:object"] == "tabView") return content; - else { - return ( - -
{content} - - ); - } -}; - -FieldTemplate.propTypes = { - displayLabel: PropTypes.bool, - classNames: PropTypes.string, - disabled: PropTypes.bool, - formContext: PropTypes.object, - rawErrors: PropTypes.array, - onDropPropertyClick: PropTypes.func, - onKeyChange: PropTypes.func, - rawDescription: PropTypes.string, - readonly: PropTypes.bool, - required: PropTypes.bool, - hidden: PropTypes.bool, - schema: PropTypes.object, - help: PropTypes.string, - label: PropTypes.string, - rawHelp: PropTypes.string, - id: PropTypes.string, - children: PropTypes.node, - uiSchema: PropTypes.object, -}; - -export default FieldTemplate; diff --git a/ui/cap-react/src/antd/forms/templates/Field/WrapIfAdditional.js b/ui/cap-react/src/antd/forms/templates/Field/WrapIfAdditional.js deleted file mode 100644 index 75b3f983a2..0000000000 --- a/ui/cap-react/src/antd/forms/templates/Field/WrapIfAdditional.js +++ /dev/null @@ -1,116 +0,0 @@ -import { ADDITIONAL_PROPERTY_FLAG } from "@rjsf/utils"; -import { Button, Col, Form, Input, Row } from "antd"; -import DeleteOutlined from "@ant-design/icons/DeleteOutlined"; -import PropTypes from "prop-types"; - -const VERTICAL_LABEL_COL = { span: 24 }; -const VERTICAL_WRAPPER_COL = { span: 24 }; - -const INPUT_STYLE = { - width: "100%", -}; - -const WrapIfAdditional = ({ - children, - classNames, - disabled, - formContext, - id, - label, - onDropPropertyClick, - onKeyChange, - readonly, - required, - schema, - isTabView, -}) => { - const { - colon, - labelCol = VERTICAL_LABEL_COL, - readonlyAsDisabled = true, - rowGutter = 24, - toolbarAlign = "top", - wrapperCol = VERTICAL_WRAPPER_COL, - wrapperStyle, - } = formContext; - - const keyLabel = `${label} Key`; // i18n ? - const additional = Object.hasOwn(schema, ADDITIONAL_PROPERTY_FLAG); - - if (!additional) { - return ( -
- {children} -
- ); - } - - const handleBlur = ({ target }) => onKeyChange(target.value); - - return ( -
- -
-
- - - -
- - - {children} - - - - - - - - {properties - .filter(e => !e.hidden) - .map(element => ( - - {element.content} - - ))} - - - - - {canExpand(schema, uiSchema, formData) && !readonly && ( - - - - - - - - )} - - ); -}; - -ObjectFieldTemplate.propTypes = { - disabled: PropTypes.bool, - formContext: PropTypes.object, - onAddClick: PropTypes.func, - description: PropTypes.string, - readonly: PropTypes.bool, - required: PropTypes.bool, - schema: PropTypes.object, - formData: PropTypes.object, - idSchema: PropTypes.object, - prefixCls: PropTypes.string, - title: PropTypes.string, - uiSchema: PropTypes.object, - properties: PropTypes.object, -}; - -export default ObjectFieldTemplate; diff --git a/ui/cap-react/src/antd/forms/templates/TabField.js b/ui/cap-react/src/antd/forms/templates/TabField.js deleted file mode 100644 index 3cfabb224d..0000000000 --- a/ui/cap-react/src/antd/forms/templates/TabField.js +++ /dev/null @@ -1,161 +0,0 @@ -import { useState, useEffect } from "react"; -import PropTypes from "prop-types"; -import { isTabContainsError, _filterTabs } from "./utils/tabfield"; -import { - Col, - Layout, - Row, - Space, - Switch, - Typography, - Grid, - Select, -} from "antd"; -import { connect } from "react-redux"; - -import TabFieldMenu from "./TabFieldMenu"; - -const TabField = ({ uiSchema, properties, formErrors }) => { - let options = uiSchema["ui:options"] || {}; - - // fetch tabs either from view object or from properties - let fetched_tabs = options.tabs ? options.tabs : properties; - - // check if there is analysis_reuse_mode - let analysis_mode = fetched_tabs.filter( - item => item.name === "analysis_reuse_mode" - ); - - let idsList = []; - let active_tabs_content = []; - - const [active, setActive] = useState(""); - const [analysisChecked, setAnalysisChecked] = useState( - analysis_mode.length > 0 - ? analysis_mode[0].content.props.formData == "true" - : false - ); - - // remove components which are meant to be hidden - // remove from the tab list the analysis_reuse_mode if exists - let tabs = _filterTabs(options.tabs, idsList, options, properties); - - let active_tab = tabs.filter(prop => prop.name == active); - if (options.tabs) { - active_tabs_content = properties.filter(prop => { - return ( - active_tab[0].content && active_tab[0].content.indexOf(prop.name) > -1 - ); - }); - } else { - active_tabs_content = active_tab; - } - - useEffect(() => { - if (!active) { - let act = null; - let availableTabs = _filterTabs( - options.tabs, - idsList, - options, - properties - ); - if (availableTabs.length > 0) { - if (options.initTab) { - act = options.initTab; - } else { - act = availableTabs[0].name; - } - } - setActive(act); - } - }, []); - - const { useBreakpoint } = Grid; - const screens = useBreakpoint(); - - return ( - - {screens.md ? ( - - - - ) : ( - - - {analysis_mode.length > 0 && ( - - Reuse Mode - { - analysis_mode[0].content.props.onChange( - checked ? "true" : undefined - ); - setAnalysisChecked(checked); - }} - /> - - )} - - - - )} - - - - - {active_tabs_content.map(item => item.content)} - - - - - ); -}; - -TabField.propTypes = { - uiSchema: PropTypes.object, - properties: PropTypes.object, - formErrors: PropTypes.object, -}; - -const mapStateToProps = state => ({ - formErrors: state.draftItem.get("formErrors"), -}); - -export default connect(mapStateToProps, null)(TabField); diff --git a/ui/cap-react/src/antd/forms/templates/TabFieldMenu.js b/ui/cap-react/src/antd/forms/templates/TabFieldMenu.js deleted file mode 100644 index a0cca22c43..0000000000 --- a/ui/cap-react/src/antd/forms/templates/TabFieldMenu.js +++ /dev/null @@ -1,69 +0,0 @@ -import PropTypes from "prop-types"; -import { Menu, Switch, Typography, Row } from "antd"; -import { isTabContainsError } from "./utils/tabfield"; - -const TabFieldMenu = ({ - tabs, - active, - analysis_mode, - showReuseMode, - analysisChecked, - setAnalysisChecked, - setActive, - formErrors, - optionsTabs, -}) => { - return ( - 0 && - showReuseMode && { - key: "analysis_reuse_mode", - label: ( - - Reuse Mode - { - analysis_mode[0].content.props.onChange( - checked ? "true" : undefined - ); - setAnalysisChecked(checked); - }} - /> - - ), - }, - ].concat( - tabs.map(item => ({ - key: item.name, - label: item.title || item.content.props.schema.title, - onClick: () => setActive(item.name), - danger: isTabContainsError( - optionsTabs ? item.idsList : item.content.props.idSchema.$id, - formErrors - ), - })) - )} - /> - ); -}; - -TabFieldMenu.propTypes = { - formErrors: PropTypes.array, - setActive: PropTypes.func, - setActiveLabel: PropTypes.func, - setAnalysisChecked: PropTypes.func, - tabs: PropTypes.array, - active: PropTypes.string, - analysis_mode: PropTypes.array, - showReuseMode: PropTypes.bool, - analysisChecked: PropTypes.bool, - optionsTabs: PropTypes.object, -}; - -export default TabFieldMenu; diff --git a/ui/cap-react/src/antd/forms/templates/utils/tabfield.js b/ui/cap-react/src/antd/forms/templates/utils/tabfield.js deleted file mode 100644 index 2b65474b6a..0000000000 --- a/ui/cap-react/src/antd/forms/templates/utils/tabfield.js +++ /dev/null @@ -1,41 +0,0 @@ -export const _filterTabs = (tabs, idsList, options, properties) => { - if (tabs) { - options.tabs.map(tab => { - tab.idsList = []; - properties.map(item => { - if (tab.content.includes(item.name)) { - idsList.push(item.content.props.idSchema.$id); - tab.idsList.push(item.content.props.idSchema.$id); - } - }); - }); - return options.tabs; - } - return properties.filter( - item => !_checkIfHidden(item.name) && item.name !== "analysis_reuse_mode" - ); -}; - -const _checkIfHidden = (name, uiSchema) => { - return ( - uiSchema && - uiSchema[name] && - uiSchema[name]["ui:options"] && - uiSchema[name]["ui:options"].hidden - ); -}; - -export const isTabContainsError = (id, errors) => { - // If there is an error that startsWith the "id", probably means that - // this is the parent of an erronous field - let isCurrentErrored; - if (Array.isArray(id)) { - isCurrentErrored = errors.some(error => { - return id.filter(ids => error.startsWith(ids)).length > 0; - }); - } else { - isCurrentErrored = errors.filter(error => error.startsWith(id)); - } - - return isCurrentErrored.size > 0; -}; diff --git a/ui/cap-react/src/antd/forms/widgets/CheckboxWidget.js b/ui/cap-react/src/antd/forms/widgets/CheckboxWidget.js deleted file mode 100644 index 9b19b04404..0000000000 --- a/ui/cap-react/src/antd/forms/widgets/CheckboxWidget.js +++ /dev/null @@ -1,73 +0,0 @@ -import { Checkbox } from "antd"; - -const CheckboxWidget = ({ - autofocus, - disabled, - formContext, - id, - label, - onBlur, - onChange, - onFocus, - readonly, - value, - options, - schema, -}) => { - const { readonlyAsDisabled = true } = formContext; - - const handleChange = event => { - if (schema.type === "boolean") { - if (event.target.checked) { - onChange(schema.checkedValue || true); - } else { - onChange(schema.uncheckedValue || false); - } - } else { - onChange(event); - } - }; - - const handleBlur = ({ target }) => onBlur(id, target.checked); - - const handleFocus = ({ target }) => onFocus(id, target.checked); - - if (schema.type === "boolean") { - return ( - - {label} - - ); - } else { - return ( - - !option.value ? { ...option, value: "null" } : option - )} - autoFocus={autofocus} - value={value || []} - disabled={disabled || (readonlyAsDisabled && readonly)} - onBlur={!readonly ? handleBlur : undefined} - onChange={!readonly ? handleChange : undefined} - onFocus={!readonly ? handleFocus : undefined} - /> - ); - } -}; - -export default CheckboxWidget; diff --git a/ui/cap-react/src/antd/forms/widgets/DateWidget.js b/ui/cap-react/src/antd/forms/widgets/DateWidget.js deleted file mode 100644 index 1e45f8519d..0000000000 --- a/ui/cap-react/src/antd/forms/widgets/DateWidget.js +++ /dev/null @@ -1,75 +0,0 @@ -import { useEffect, useState } from "react"; - -import { DatePicker } from "antd"; - -import dayjs from "dayjs"; - -import weekday from "dayjs/plugin/weekday"; -import localeData from "dayjs/plugin/localeData"; - -dayjs.extend(weekday); -dayjs.extend(localeData); - -const DATE_ISO_FORMAT = "YYYY-MM-DD"; -const DATE_TIME_ISO_FORMAT = "YYYY-MM-DD HH:mm:ss"; -const DATE_DEFAULT_FORMAT = "DD/MM/YYYY"; -const DATE_TIME_DEFAULT_FORMAT = "DD/MM/YYYY HH:mm:ss"; - -const DateWidget = ({ - autofocus, - disabled, - formContext, - id, - onBlur, - onChange, - onFocus, - readonly, - value, - schema, -}) => { - const { readonlyAsDisabled = true } = formContext; - - const [isoFormat, setIsoFormat] = useState(); - - useEffect(() => { - setIsoFormat( - schema.format === "date-time" ? DATE_TIME_ISO_FORMAT : DATE_ISO_FORMAT - ); - }, [schema]); - - const handleChange = date => - onChange(date ? date.format(isoFormat) : undefined); - - const handleBlur = ({ target }) => onBlur(id, target.checked); - - const handleFocus = ({ target }) => onFocus(id, target.checked); - - return ( - - current && - ((schema.minDate && current < dayjs(schema.minDate, DATE_ISO_FORMAT)) || - (schema.maxDate && - current > dayjs(schema.maxDate, DATE_ISO_FORMAT).add(1, "d"))) - } - autoFocus={autofocus} - disabled={disabled || (readonlyAsDisabled && readonly)} - id={id} - name={id} - onBlur={!readonly ? handleBlur : undefined} - onChange={!readonly ? handleChange : undefined} - onFocus={!readonly ? handleFocus : undefined} - style={{ width: "100%" }} - value={value && dayjs(value, isoFormat)} - /> - ); -}; - -export default DateWidget; diff --git a/ui/cap-react/src/antd/forms/widgets/MaskedInput/MaskedInput.js b/ui/cap-react/src/antd/forms/widgets/MaskedInput/MaskedInput.js deleted file mode 100644 index 9f50f3f3c2..0000000000 --- a/ui/cap-react/src/antd/forms/widgets/MaskedInput/MaskedInput.js +++ /dev/null @@ -1,73 +0,0 @@ -import { Input } from "antd"; -import InputMask from "react-input-mask"; - -const MAPPINGS = { - a: /[a-z]/, - A: /[A-Z]/, - 0: /[0-9]/, - "*": /[a-zA-Z0-9]/, -}; - -const MaskedInput = ({ - id, - mask, - pattern, - name, - onBlur, - onChange, - onFocus, - onPressEnter, - placeholder, - buttons, - value, - disabled, - message, - convertToUppercase, -}) => { - const status = new RegExp(pattern).test(value); - - return ( -
- i) // needed to remove empty entries - .map(i => { - let mappings = convertToUppercase - ? { ...MAPPINGS, a: /[a-zA-Z]/, A: /[a-zA-Z]/ } - : MAPPINGS; - return i in mappings ? mappings[i] : i.replace("\\", ""); - }) - } - onChange={onChange} - onFocus={onFocus} - onBlur={onBlur} - onPressEnter={onPressEnter} - value={value} - disabled={disabled} - > - - - {message && ( -
- {message.message} -
- )} -
- ); -}; - -export default MaskedInput; diff --git a/ui/cap-react/src/antd/forms/widgets/MaskedInput/index.js b/ui/cap-react/src/antd/forms/widgets/MaskedInput/index.js deleted file mode 100644 index 8c88179735..0000000000 --- a/ui/cap-react/src/antd/forms/widgets/MaskedInput/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./MaskedInput"; diff --git a/ui/cap-react/src/antd/forms/widgets/RequiredWidget.js b/ui/cap-react/src/antd/forms/widgets/RequiredWidget.js deleted file mode 100644 index 6eaa44f2e2..0000000000 --- a/ui/cap-react/src/antd/forms/widgets/RequiredWidget.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Switch } from "antd"; - -const RequiredWidget = ({ value, onChange, path, updateRequired }) => { - const handleChange = checked => { - onChange(checked); - updateRequired(path.get("path").toJS(), checked); - }; - - return ( - - Required - - ); -}; - -export default RequiredWidget; diff --git a/ui/cap-react/src/antd/forms/widgets/SelectWidget.js b/ui/cap-react/src/antd/forms/widgets/SelectWidget.js deleted file mode 100644 index 06185341dc..0000000000 --- a/ui/cap-react/src/antd/forms/widgets/SelectWidget.js +++ /dev/null @@ -1,177 +0,0 @@ -import { useState } from "react"; -import { connect } from "react-redux"; -import Select from "antd/lib/select"; -import { debounce } from "lodash-es"; -import axios from "../../../axios"; -import { fromJS } from "immutable"; -import { Empty } from "antd"; -import PropTypes from "prop-types"; - -import { - ariaDescribedByIds, - enumOptionsIndexForValue, - enumOptionsValueForIndex, -} from "@rjsf/utils"; -import isString from "lodash-es"; - -const SelectWidget = ({ - autofocus, - disabled, - formContext, - id, - multiple, - onBlur, - onChange, - onFocus, - options, - placeholder, - readonly, - value, - formData, -}) => { - const { readonlyAsDisabled = true } = formContext; - - const { enumOptions, enumDisabled, suggestions, params, emptyValue } = - options; - - const [loading, setLoading] = useState(false); - const [data, setData] = useState([]); - - const handleChange = nextValue => { - onChange( - enumOptionsValueForIndex(nextValue, enumOptions || data, emptyValue) - ); - }; - - const handleBlur = () => - onBlur(id, enumOptionsValueForIndex(value, enumOptions, emptyValue)); - - const handleFocus = () => - onFocus(id, enumOptionsValueForIndex(value, enumOptions, emptyValue)); - - const handleSearch = newValue => { - updateSearch(newValue, setData); - }; - - const filterOption = (input, option) => { - if (option && isString(option.label)) { - // labels are strings in this context - return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0; - } - return false; - }; - - const getPopupContainer = node => node.parentNode; - - const selectedIndexes = enumOptionsIndexForValue( - value, - enumOptions, - multiple - ); - - const _replace_hash_with_current_indexes = path => { - let indexes = id.split("_").filter(item => !isNaN(item)), - index_cnt = 0; - - return path.map(item => { - item = item === "#" ? indexes[index_cnt] : item; - if (!isNaN(item)) ++index_cnt; - return item; - }); - }; - - const updateSearch = (value, callback) => { - let data = fromJS(formData); - if (params) { - Object.entries(params).map(param => { - const path = _replace_hash_with_current_indexes(param[1]); - suggestions.replace( - `${param[0]}=`, - `${param[0]}=${data.getIn(path, "") || ""}` - ); - }); - } - setLoading(true); - axios - .get(`${suggestions}${value}`) - .then(({ data }) => { - callback(data.map(value => ({ value, label: value }))); - setLoading(false); - }) - .catch(() => { - callback([]); - setLoading(false); - }); - }; - - let valuesToRender = suggestions ? data : enumOptions; - - // Antd's typescript definitions do not contain the following props that are actually necessary and, if provided, - // they are used, so hacking them in via by spreading `extraProps` on the component to avoid typescript errors - const extraProps = { - name: id, - }; - return ( - onChange(e.target.value)} - onBlur={handleBlur} - onFocus={handleFocus} - disabled={disabled || (readonlyAsDisabled && readonly)} - suffix={ - <> - -