From d31aa4a91ce3e0966e3dfe5e7460a91a3e02e51e Mon Sep 17 00:00:00 2001 From: gchust Date: Tue, 22 Apr 2025 13:25:29 +0800 Subject: [PATCH] feat: add convert template block to page block action (#6662) * feat: add convert template block to page block action * feat: convert template block to normal block * fix: remove remaining template properties * fix: list template not loading data * fix: user menu render error * fix: save as template and convert to block should hide in workflow config page * fix: incorrect translation --- .../ConvertToNormalBlockSetting.tsx | 182 ++++++++++++++++++ .../components/SaveAsTemplateSetting.tsx | 6 +- .../src/client/hooks/useIsPageBlock.ts | 3 +- .../src/client/index.tsx | 5 +- .../settings/convertToNormalBlockSetting.ts | 23 +++ .../src/locale/en-US.json | 3 + .../src/locale/zh-CN.json | 3 + 7 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-block-template/src/client/components/ConvertToNormalBlockSetting.tsx create mode 100644 packages/plugins/@nocobase/plugin-block-template/src/client/settings/convertToNormalBlockSetting.ts diff --git a/packages/plugins/@nocobase/plugin-block-template/src/client/components/ConvertToNormalBlockSetting.tsx b/packages/plugins/@nocobase/plugin-block-template/src/client/components/ConvertToNormalBlockSetting.tsx new file mode 100644 index 0000000000..2f1310fe8c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-template/src/client/components/ConvertToNormalBlockSetting.tsx @@ -0,0 +1,182 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { SchemaSettingsItem, useAPIClient, useDesignable, useFormBlockProps } from '@nocobase/client'; +import { useFieldSchema, useForm, useField } from '@formily/react'; +import { App } from 'antd'; +import React from 'react'; +import _ from 'lodash'; +import { Schema } from '@formily/json-schema'; +import { useT } from '../locale'; +import { uid } from '@nocobase/utils/client'; + +const findInsertPosition = (parentSchema, uid) => { + const postion = { + insertPosition: 'beforeBegin', + insertTarget: null, + }; + const properties = Object.values(parentSchema.properties || {}).sort((a, b) => { + return (a as any)['x-index'] - (b as any)['x-index']; + }); + for (let i = 0; i < properties.length; i++) { + const property = properties[i]; + if ((property as any)['x-uid'] === uid) { + postion.insertPosition = 'beforeBegin'; + if (i === properties.length - 1) { + postion.insertPosition = 'beforeEnd'; + postion.insertTarget = parentSchema['x-uid']; + } else { + postion.insertPosition = 'beforeBegin'; + postion.insertTarget = (properties[i + 1] as any)['x-uid']; + } + } + } + return postion; +}; + +const findParentRootTemplateSchema = (fieldSchema) => { + if (!fieldSchema) { + return null; + } + if (fieldSchema['x-template-root-uid']) { + return fieldSchema; + } else { + return findParentRootTemplateSchema(fieldSchema.parent); + } +}; + +// Create a copy of the schema with all template associations removed +const convertToNormalBlockSchema = (schema) => { + const newSchema = _.cloneDeep(schema); + + // Remove template associations from the schema + const removeTemplateAssociations = (s) => { + // Remove template-specific properties + delete s['x-template-uid']; + delete s['x-template-root-uid']; + delete s['x-template-version']; + delete s['x-block-template-key']; + delete s['x-template-root-ref']; + delete s['x-template-title']; + delete s['x-virtual']; + + if (s['x-toolbar-props']?.toolbarClassName?.includes('nb-in-template')) { + s['x-toolbar-props'].toolbarClassName = s['x-toolbar-props'].toolbarClassName.replace('nb-in-template', ''); + } + + if (s['x-uid']) { + s['x-uid'] = uid(); + } + // Process nested properties + if (s.properties) { + for (const key in s.properties) { + if (!s.properties[key]['x-template-root-uid']) { + removeTemplateAssociations(s.properties[key]); + } + } + } + }; + + removeTemplateAssociations(newSchema); + return newSchema; +}; + +export const ConvertToNormalBlockSetting = () => { + const { refresh } = useDesignable(); + const t = useT(); + const api = useAPIClient(); + const form = useForm(); + const field = useField(); + const { form: blockForm } = useFormBlockProps(); + const fieldSchema = useFieldSchema(); + const { modal, message } = App.useApp(); + const blockTemplatesResource = api.resource('blockTemplates'); + + const confirm = { + okText: t('Yes'), + cancelText: t('No'), + }; + + return ( + { + modal.confirm({ + title: t('Convert to normal block'), + content: t('Are you sure you want to convert this template block to a normal block?'), + ...confirm, + async onOk() { + const newSchema = convertToNormalBlockSchema(fieldSchema.toJSON()); + const position = findInsertPosition(fieldSchema.parent, fieldSchema['x-uid']); + // TODO: Remove old schema, and links + + // Remove old schema + await api.request({ + url: `/uiSchemas:remove/${fieldSchema['x-uid']}`, + }); + + // Insert new schema + const schema = new Schema(newSchema); + await api.request({ + url: `/uiSchemas:insertAdjacent/${position.insertTarget}?position=${position.insertPosition}`, + method: 'post', + data: { + schema, + }, + }); + + // Update the UI to show the new schema + fieldSchema.toJSON = () => { + const ret = schema.toJSON(); + return ret; + }; + + refresh({ refreshParentSchema: true }); + + // Update component properties + field['componentProps'] = { + ...field['componentProps'], + key: uid(), + }; + + if (field.parent?.['componentProps']) { + field.parent['componentProps'] = { + ...field.parent['componentProps'], + key: uid(), + }; + } + + // Update decorator properties + field['decoratorProps'] = { + ...field['decoratorProps'], + key: uid(), + }; + + if (field.parent?.['decoratorProps']) { + field.parent['decoratorProps'] = { + ...field.parent['decoratorProps'], + key: uid(), + }; + } + + // Reset forms + form.reset(); + blockForm?.reset(); + form.clearFormGraph('*', false); + blockForm?.clearFormGraph('*', false); + + message.success(t('Converted successfully'), 0.2); + }, + }); + }} + > + {t('Convert to normal block')} + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-block-template/src/client/components/SaveAsTemplateSetting.tsx b/packages/plugins/@nocobase/plugin-block-template/src/client/components/SaveAsTemplateSetting.tsx index 1ab2d6a7a5..769c7622f5 100644 --- a/packages/plugins/@nocobase/plugin-block-template/src/client/components/SaveAsTemplateSetting.tsx +++ b/packages/plugins/@nocobase/plugin-block-template/src/client/components/SaveAsTemplateSetting.tsx @@ -74,7 +74,7 @@ export const SaveAsTemplateSetting = () => { key: { type: 'string', 'x-decorator': 'FormItem', - title: t('Key'), + title: t('Name'), 'x-component': 'Input', 'x-validator': 'uid', required: true, @@ -298,6 +298,10 @@ function getTemplateSchemaFromPage(schema: ISchema) { } _.set(t, `properties.['${key}']`, {}); traverseSchema(s.properties[key], t.properties[key]); + // array's key will be set to number when render, so we need to set the name to the key + if (s.type === 'array' && t['properties']?.[key]?.name) { + _.set(t, `properties.['${key}'].name`, key); + } } } }; diff --git a/packages/plugins/@nocobase/plugin-block-template/src/client/hooks/useIsPageBlock.ts b/packages/plugins/@nocobase/plugin-block-template/src/client/hooks/useIsPageBlock.ts index 86a2e0e4e2..e89dd5108e 100644 --- a/packages/plugins/@nocobase/plugin-block-template/src/client/hooks/useIsPageBlock.ts +++ b/packages/plugins/@nocobase/plugin-block-template/src/client/hooks/useIsPageBlock.ts @@ -22,8 +22,9 @@ export const useIsPageBlock = () => { const isPage = location.pathname.startsWith('/admin/') || location.pathname.startsWith('/page/'); const notInPopup = !location.pathname.includes('/popups/'); const notInSetting = !location.pathname.startsWith('/admin/settings/'); + const notInWorkflow = !location.pathname.startsWith('/admin/workflow/workflows/'); const notInBlockTemplate = !location.pathname.startsWith('/block-templates/'); - return isPage && notInPopup && notInSetting && notInBlockTemplate; + return isPage && notInPopup && notInSetting && notInWorkflow && notInBlockTemplate; }, [location.pathname, fieldSchema]); return isPageBlock; diff --git a/packages/plugins/@nocobase/plugin-block-template/src/client/index.tsx b/packages/plugins/@nocobase/plugin-block-template/src/client/index.tsx index f143dc158d..cd25300a80 100644 --- a/packages/plugins/@nocobase/plugin-block-template/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-block-template/src/client/index.tsx @@ -28,6 +28,8 @@ import { import { BlockTemplateMenusProvider } from './components/BlockTemplateMenusProvider'; import { disabledDeleteSettingItem } from './settings/disabledDeleteSetting'; import { saveAsTemplateSetting } from './settings/saveAsTemplateSetting'; +import { convertToNormalBlockSettingItem } from './settings/convertToNormalBlockSetting'; + export class PluginBlockTemplateClient extends Plugin { templateInfos = new Map(); templateschemacache = {}; @@ -158,9 +160,10 @@ export class PluginBlockTemplateClient extends Plugin { deleteItemIndex !== -1 && !schemaSetting.items.find((item) => item.name === 'template-revertSettingItem') ) { - schemaSetting.items.splice(deleteItemIndex, 0, revertSettingItem); + schemaSetting.items.splice(deleteItemIndex, 0, revertSettingItem, convertToNormalBlockSettingItem); } else { schemaSetting.add('template-revertSettingItem', revertSettingItem); + schemaSetting.add('template-convertToNormalBlockSettingItem', convertToNormalBlockSettingItem); } schemaSetting.add('template-disabledDeleteItem', disabledDeleteSettingItem); } diff --git a/packages/plugins/@nocobase/plugin-block-template/src/client/settings/convertToNormalBlockSetting.ts b/packages/plugins/@nocobase/plugin-block-template/src/client/settings/convertToNormalBlockSetting.ts new file mode 100644 index 0000000000..4539c27af8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-template/src/client/settings/convertToNormalBlockSetting.ts @@ -0,0 +1,23 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { useFieldSchema } from '@formily/react'; +import { ConvertToNormalBlockSetting } from '../components/ConvertToNormalBlockSetting'; +import { tStr } from '../locale'; +import { useIsInTemplate } from '../hooks/useIsInTemplate'; + +export const convertToNormalBlockSettingItem = { + name: 'template-convertToNormalBlockSettingItem', + title: tStr('Convert to normal block'), + Component: ConvertToNormalBlockSetting, + useVisible: () => { + const fieldSchema = useFieldSchema(); + return fieldSchema?.['x-template-root-uid']; + }, +}; diff --git a/packages/plugins/@nocobase/plugin-block-template/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-block-template/src/locale/en-US.json index 6acd6419ea..9f737f2a9c 100644 --- a/packages/plugins/@nocobase/plugin-block-template/src/locale/en-US.json +++ b/packages/plugins/@nocobase/plugin-block-template/src/locale/en-US.json @@ -12,7 +12,9 @@ "Mobile": "Mobile", "Current": "Current record", "Revert to template": "Revert to template", + "Convert to normal block": "Convert to normal block", "Are you sure you want to revert all changes from the template?": "Are you sure you want to revert all changes from the template?", + "Are you sure you want to convert this template block to a normal block?": "Are you sure you want to convert this template block to a normal block?", "Templates": "Templates", "Block templates": "Block templates", "Submit": "Submit", @@ -22,6 +24,7 @@ "Saved successfully": "Saved successfully", "Block template": "Block template", "Reset successfully": "Reset successfully", + "Converted successfully": "Converted successfully", "Delete successfully": "Delete successfully", "Template block settings": "Template block settings", "Filter": "Filter", diff --git a/packages/plugins/@nocobase/plugin-block-template/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-block-template/src/locale/zh-CN.json index be27745c26..57bdbbb754 100644 --- a/packages/plugins/@nocobase/plugin-block-template/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-block-template/src/locale/zh-CN.json @@ -12,7 +12,9 @@ "Mobile": "移动端", "Current": "当前记录", "Revert to template": "恢复到模板", + "Convert to normal block": "转换成普通区块", "Are you sure you want to revert all changes from the template?": "您确定要恢复所有对模板的更改吗?", + "Are you sure you want to convert this template block to a normal block?": "您确定要将此模板区块转换为普通区块吗?", "Templates": "模板", "Block templates": "区块模板", "Submit": "提交", @@ -22,6 +24,7 @@ "Saved successfully": "保存成功", "Block template": "区块模板", "Reset successfully": "重置成功", + "Converted successfully": "转换成功", "Delete successfully": "删除成功", "Template block settings": "模板区块设置", "Filter": "筛选",