refactor: remove page
This commit is contained in:
parent
d072b32b17
commit
a5145e64f7
@ -39,7 +39,6 @@ import { useOutboundActionProps } from './hooks/useOutboundActionProps';
|
||||
import { ExtendedAssociationField } from './schema-components/association-field/Editable';
|
||||
import { AssociatedField } from './components/AssociatedField';
|
||||
import { DatePicker } from './schema-components/date-picker';
|
||||
import { Page } from './schema-components/page';
|
||||
import { SettingBlockInitializer } from './schema-initializer/SettingBlockInitializer';
|
||||
import {
|
||||
EditFormulaTitleField,
|
||||
@ -158,7 +157,6 @@ export class PluginCoreClient extends Plugin {
|
||||
GroupBlockInitializer,
|
||||
GroupBlockToolbar,
|
||||
GroupBlockProvider,
|
||||
Page,
|
||||
DatePicker,
|
||||
RemoteSelect,
|
||||
SignatureInput,
|
||||
|
@ -1,145 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRecord, useDesignable, SchemaSettingsSwitchItem } from '@nocobase/client';
|
||||
import { useIsBlockInPage } from './hooks/useIsBlockInPage';
|
||||
|
||||
const FixedBlockContext = React.createContext<{
|
||||
setFixedBlock: (value: string | false) => void;
|
||||
height: number | string;
|
||||
fixedBlockUID: boolean | string;
|
||||
fixedBlockUIDRef: React.MutableRefObject<boolean | string>;
|
||||
inFixedBlock: boolean;
|
||||
}>({
|
||||
setFixedBlock: () => {},
|
||||
height: 0,
|
||||
fixedBlockUID: false,
|
||||
fixedBlockUIDRef: { current: false },
|
||||
inFixedBlock: false,
|
||||
});
|
||||
|
||||
export const useFixedSchema = () => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { setFixedBlock, fixedBlockUID, fixedBlockUIDRef } = useFixedBlock();
|
||||
const hasSet = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fixedBlockUIDRef.current || hasSet.current) {
|
||||
setFixedBlock(field?.decoratorProps?.fixedBlock ? fieldSchema['x-uid'] : false);
|
||||
hasSet.current = true;
|
||||
}
|
||||
}, [field?.decoratorProps?.fixedBlock]);
|
||||
|
||||
return fieldSchema['x-uid'] === fixedBlockUID;
|
||||
};
|
||||
|
||||
export const useFixedBlock = () => {
|
||||
return useContext(FixedBlockContext);
|
||||
};
|
||||
|
||||
export const FixedBlockWrapper: React.FC = (props) => {
|
||||
const fixedBlock = useFixedSchema();
|
||||
const { height, fixedBlockUID } = useFixedBlock();
|
||||
const record = useRecord();
|
||||
const isPopup = Object.keys(record).length;
|
||||
if (isPopup) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
/**
|
||||
* The fixedBlockUID of false means that the page has no fixed blocks
|
||||
* isPopup means that the FixedBlock is in the popup mode
|
||||
*/
|
||||
if (!fixedBlock && fixedBlockUID) return null;
|
||||
return (
|
||||
<div
|
||||
className="nb-fixed-block"
|
||||
style={{
|
||||
height: fixedBlockUID ? `calc(100vh - ${height})` : undefined,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const FixedBlockDesignerItem = () => {
|
||||
const field = useField();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { dn } = useDesignable();
|
||||
const { inFixedBlock } = useFixedBlock();
|
||||
const { isBlockInPage } = useIsBlockInPage();
|
||||
|
||||
if (!isBlockInPage() || !inFixedBlock) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<SchemaSettingsSwitchItem
|
||||
title={t('Fix block')}
|
||||
checked={fieldSchema['x-decorator-props']?.fixedBlock}
|
||||
onChange={async (fixedBlock) => {
|
||||
const decoratorProps = {
|
||||
...fieldSchema['x-decorator-props'],
|
||||
fixedBlock,
|
||||
};
|
||||
await dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-decorator-props': decoratorProps,
|
||||
},
|
||||
});
|
||||
field.decoratorProps = fieldSchema['x-decorator-props'] = decoratorProps;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface FixedBlockProps {
|
||||
height: number | string;
|
||||
}
|
||||
|
||||
const fixedBlockCss = css`
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
.noco-card-item {
|
||||
height: 100%;
|
||||
.ant-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
.ant-card-body {
|
||||
height: 1px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const FixedBlock: React.FC<FixedBlockProps> = (props) => {
|
||||
const { height } = props;
|
||||
const [fixedBlockUID, _setFixedBlock] = useState<false | string>(false);
|
||||
const fixedBlockUIDRef = useRef(fixedBlockUID);
|
||||
const setFixedBlock = (v) => {
|
||||
fixedBlockUIDRef.current = v;
|
||||
_setFixedBlock(v);
|
||||
};
|
||||
return (
|
||||
<FixedBlockContext.Provider value={{ inFixedBlock: true, height, setFixedBlock, fixedBlockUID, fixedBlockUIDRef }}>
|
||||
<div
|
||||
className={fixedBlockUID ? fixedBlockCss : ''}
|
||||
style={{
|
||||
height: fixedBlockUID ? `calc(100vh - ${height})` : undefined,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</FixedBlockContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FixedBlock;
|
@ -1,139 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
||||
import { SchemaSettings, useDesignable, useSchemaToolbar } from '@nocobase/client';
|
||||
|
||||
function useNotDisableHeader() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return !fieldSchema['x-component-props']?.disablePageHeader;
|
||||
}
|
||||
|
||||
export const pageSettings = new SchemaSettings({
|
||||
name: 'PageSettings',
|
||||
items: [
|
||||
{
|
||||
name: 'enablePageHeader',
|
||||
type: 'switch',
|
||||
useComponentProps() {
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return {
|
||||
title: t('Enable page header'),
|
||||
checked: !fieldSchema['x-component-props']?.disablePageHeader,
|
||||
onChange(v) {
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props']['disablePageHeader'] = !v;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
['x-component-props']: fieldSchema['x-component-props'],
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
useVisible: useNotDisableHeader,
|
||||
},
|
||||
{
|
||||
name: 'displayPageTitle',
|
||||
type: 'switch',
|
||||
useVisible: useNotDisableHeader,
|
||||
useComponentProps() {
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return {
|
||||
title: t('Display page title'),
|
||||
checked: !fieldSchema['x-component-props']?.hidePageTitle,
|
||||
onChange(v) {
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props']['hidePageTitle'] = !v;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
['x-component-props']: fieldSchema['x-component-props'],
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'editPageTitle',
|
||||
type: 'modal',
|
||||
useVisible() {
|
||||
const notDisablePageHeader = useNotDisableHeader();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const notHidePageTitle = !fieldSchema['x-component-props']?.hidePageTitle;
|
||||
|
||||
return notDisablePageHeader && notHidePageTitle;
|
||||
},
|
||||
useComponentProps() {
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { title } = useSchemaToolbar();
|
||||
return {
|
||||
hide: true,
|
||||
title: t('Edit page title'),
|
||||
schema: {
|
||||
type: 'object',
|
||||
title: t('Edit page title'),
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Title'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
} as ISchema,
|
||||
initialValues: { title },
|
||||
onSubmit({ title }) {
|
||||
field.title = title;
|
||||
fieldSchema['title'] = title;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
title,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'enablePageTabs',
|
||||
type: 'switch',
|
||||
useVisible: useNotDisableHeader,
|
||||
useComponentProps() {
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return {
|
||||
title: t('Enable page tabs'),
|
||||
checked: fieldSchema['x-component-props']?.enablePageTabs,
|
||||
onChange(v) {
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props']['enablePageTabs'] = v;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
['x-component-props']: fieldSchema['x-component-props'],
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -1,235 +0,0 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { PageHeader as AntdPageHeader } from '@ant-design/pro-layout';
|
||||
import { FormLayout } from '@formily/antd-v5';
|
||||
import { Schema, SchemaOptionsContext, useFieldSchema } from '@formily/react';
|
||||
import { Button, Tabs } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import FixedBlock from './FixedBlock';
|
||||
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
||||
import { useAClStyles, useStyles } from './style';
|
||||
import {
|
||||
DndContext,
|
||||
FilterBlockProvider,
|
||||
FormDialog,
|
||||
Icon,
|
||||
SchemaComponent,
|
||||
SchemaComponentOptions,
|
||||
SortableItem,
|
||||
useCompile,
|
||||
useDesignable,
|
||||
useDocumentTitle,
|
||||
useGlobalTheme,
|
||||
useToken,
|
||||
} from '@nocobase/client';
|
||||
import { useAppSpin } from '../../layouts';
|
||||
import { useGetAriaLabelOfSchemaInitializer } from './hooks/useGetAriaLabelOfSchemaInitializer';
|
||||
import { ErrorFallback } from '../error-fallback';
|
||||
|
||||
export const Page = (props) => {
|
||||
const { children, ...others } = props;
|
||||
const { t } = useTranslation();
|
||||
const compile = useCompile();
|
||||
const { title, setTitle } = useDocumentTitle();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const dn = useDesignable();
|
||||
const { theme } = useGlobalTheme();
|
||||
const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer();
|
||||
|
||||
// react18 tab 动画会卡顿,所以第一个 tab 时,动画禁用,后面的 tab 才启用
|
||||
const [hasMounted, setHasMounted] = useState(false);
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setHasMounted(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!title) {
|
||||
setTitle(t(fieldSchema.title));
|
||||
}
|
||||
}, [fieldSchema.title, title]);
|
||||
const disablePageHeader = fieldSchema['x-component-props']?.disablePageHeader;
|
||||
const enablePageTabs = fieldSchema['x-component-props']?.enablePageTabs;
|
||||
const hidePageTitle = fieldSchema['x-component-props']?.hidePageTitle;
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const activeKey = useMemo(
|
||||
() => searchParams.get('tab') || Object.keys(fieldSchema.properties || {}).shift(),
|
||||
[fieldSchema.properties, searchParams],
|
||||
);
|
||||
const [height, setHeight] = useState(0);
|
||||
const { wrapSSR, hashId, componentCls } = useStyles();
|
||||
const aclStyles = useAClStyles();
|
||||
|
||||
const handleErrors = (error) => {
|
||||
console.error(error);
|
||||
};
|
||||
|
||||
const pageHeaderTitle = hidePageTitle ? undefined : fieldSchema.title || compile(title);
|
||||
return wrapSSR(
|
||||
<FilterBlockProvider>
|
||||
<div className={`${componentCls} ${hashId} ${aclStyles.styles}`}>
|
||||
<PageDesigner title={fieldSchema.title || title} />
|
||||
<div
|
||||
ref={(ref) => {
|
||||
setHeight(Math.floor(ref?.getBoundingClientRect().height || 0) + 1);
|
||||
}}
|
||||
>
|
||||
{!disablePageHeader && (
|
||||
<AntdPageHeader
|
||||
className={classNames('pageHeaderCss', pageHeaderTitle || enablePageTabs ? '' : 'height0')}
|
||||
ghost={false}
|
||||
// 如果标题为空的时候会导致 PageHeader 不渲染,所以这里设置一个空白字符,然后再设置高度为 0
|
||||
title={pageHeaderTitle || ' '}
|
||||
{...others}
|
||||
footer={
|
||||
enablePageTabs && (
|
||||
<DndContext>
|
||||
<Tabs
|
||||
size={'small'}
|
||||
animated={hasMounted}
|
||||
activeKey={activeKey}
|
||||
onTabClick={(activeKey) => {
|
||||
setLoading(true);
|
||||
setSearchParams([['tab', activeKey]]);
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 50);
|
||||
}}
|
||||
tabBarExtraContent={
|
||||
dn.designable && (
|
||||
<Button
|
||||
aria-label={getAriaLabel('tabs')}
|
||||
icon={<PlusOutlined />}
|
||||
className={'addTabBtn'}
|
||||
type={'dashed'}
|
||||
onClick={async () => {
|
||||
const values = await FormDialog(
|
||||
t('Add tab'),
|
||||
() => {
|
||||
return (
|
||||
<SchemaComponentOptions
|
||||
scope={options.scope}
|
||||
components={{ ...options.components }}
|
||||
>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Tab name'),
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
required: true,
|
||||
},
|
||||
icon: {
|
||||
title: t('Icon'),
|
||||
'x-component': 'IconPicker',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
},
|
||||
theme,
|
||||
).open({
|
||||
initialValues: {},
|
||||
});
|
||||
const { title, icon } = values;
|
||||
dn.insertBeforeEnd({
|
||||
type: 'void',
|
||||
title,
|
||||
'x-icon': icon,
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'BlockInitializers',
|
||||
properties: {},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('Add tab')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
items={fieldSchema.mapProperties((schema) => {
|
||||
return {
|
||||
label: (
|
||||
<SortableItem
|
||||
id={schema.name as string}
|
||||
schema={schema}
|
||||
className={classNames('nb-action-link', 'designerCss', props.className)}
|
||||
>
|
||||
{schema['x-icon'] && <Icon style={{ marginRight: 8 }} type={schema['x-icon']} />}
|
||||
<span>{schema.title || t('Unnamed')}</span>
|
||||
<PageTabDesigner schema={schema} />
|
||||
</SortableItem>
|
||||
),
|
||||
key: schema.name as string,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</DndContext>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="nb-page-wrapper">
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={handleErrors}>
|
||||
{PageContent(loading, disablePageHeader, enablePageTabs, fieldSchema, activeKey, height, props)}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</FilterBlockProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
Page.displayName = 'Page';
|
||||
|
||||
function PageContent(
|
||||
loading: boolean,
|
||||
disablePageHeader: any,
|
||||
enablePageTabs: any,
|
||||
fieldSchema: Schema<any, any, any, any, any, any, any, any, any>,
|
||||
activeKey: string,
|
||||
height: number,
|
||||
props: any,
|
||||
): React.ReactNode {
|
||||
const { token } = useToken();
|
||||
const { render } = useAppSpin();
|
||||
|
||||
if (loading) {
|
||||
return render();
|
||||
}
|
||||
|
||||
return !disablePageHeader && enablePageTabs ? (
|
||||
fieldSchema.mapProperties((schema) => {
|
||||
if (schema.name !== activeKey) return null;
|
||||
|
||||
return (
|
||||
<FixedBlock key={schema.name} height={`calc(${height}px + 46px + ${token.marginLG}px * 2)`}>
|
||||
<SchemaComponent
|
||||
schema={
|
||||
new Schema({
|
||||
properties: {
|
||||
[schema.name]: schema,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</FixedBlock>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<FixedBlock height={`calc(${height}px + 46px + ${token.marginLG}px * 2)`}>
|
||||
<div className={`pageWithFixedBlockCss nb-page-content`}>{props.children}</div>
|
||||
</FixedBlock>
|
||||
);
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
import { App } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
import { useDesignable, useSchemaToolbar, SchemaSettings } from '@nocobase/client';
|
||||
|
||||
export const pageTabSettings = new SchemaSettings({
|
||||
name: 'PageTabSettings',
|
||||
items: [
|
||||
{
|
||||
name: 'edit',
|
||||
type: 'modal',
|
||||
useComponentProps() {
|
||||
const { t } = useTranslation();
|
||||
const { schema } = useSchemaToolbar<{ schema: ISchema }>();
|
||||
const { dn } = useDesignable();
|
||||
return {
|
||||
title: t('Edit'),
|
||||
schema: {
|
||||
type: 'object',
|
||||
title: t('Edit tab'),
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Tab name'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
},
|
||||
icon: {
|
||||
title: t('Icon'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'IconPicker',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
} as ISchema,
|
||||
initialValues: { title: schema.title, icon: schema['x-icon'] },
|
||||
onSubmit: ({ title, icon }) => {
|
||||
schema.title = title;
|
||||
schema['x-icon'] = icon;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: schema['x-uid'],
|
||||
title,
|
||||
'x-icon': icon,
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
type: 'item',
|
||||
useComponentProps() {
|
||||
const { modal } = App.useApp();
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const { schema } = useSchemaToolbar();
|
||||
return {
|
||||
title: 'Delete',
|
||||
eventKey: 'remove',
|
||||
onClick() {
|
||||
modal.confirm({
|
||||
title: t('Delete block'),
|
||||
content: t('Are you sure you want to delete it?'),
|
||||
...confirm,
|
||||
onOk() {
|
||||
dn.remove(schema);
|
||||
},
|
||||
});
|
||||
},
|
||||
children: t('Delete'),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -1,56 +0,0 @@
|
||||
import { DragOutlined } from '@ant-design/icons';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { DragHandler, SchemaToolbarProvider, useDesignable, useSchemaSettingsRender } from '@nocobase/client';
|
||||
import { Space } from 'antd';
|
||||
import React from 'react';
|
||||
import { useGetAriaLabelOfDesigner } from './hooks/useGetAriaLabelOfDesigner';
|
||||
|
||||
export const PageDesigner = ({ title }) => {
|
||||
const { designable } = useDesignable();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { render } = useSchemaSettingsRender(
|
||||
fieldSchema['x-settings'] || 'PageSettings',
|
||||
fieldSchema['x-settings-props'],
|
||||
);
|
||||
if (!designable) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<SchemaToolbarProvider title={title}>
|
||||
<div className={'general-schema-designer'}>
|
||||
<div className={'general-schema-designer-icons'}>
|
||||
<Space size={2} align={'center'}>
|
||||
{render()}
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</SchemaToolbarProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageTabDesigner = ({ schema }) => {
|
||||
const { designable } = useDesignable();
|
||||
const { getAriaLabel } = useGetAriaLabelOfDesigner();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { render } = useSchemaSettingsRender(
|
||||
fieldSchema['x-settings'] || 'PageTabSettings',
|
||||
fieldSchema['x-settings-props'],
|
||||
);
|
||||
if (!designable) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<SchemaToolbarProvider schema={schema}>
|
||||
<div className={'general-schema-designer'}>
|
||||
<div className={'general-schema-designer-icons'}>
|
||||
<Space size={3} align={'center'}>
|
||||
<DragHandler>
|
||||
<DragOutlined style={{ marginRight: 0 }} role="button" aria-label={getAriaLabel('drag-handler', 'tab')} />
|
||||
</DragHandler>
|
||||
{render()}
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</SchemaToolbarProvider>
|
||||
);
|
||||
};
|
@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from 'testUtils';
|
||||
import App1 from '../demos/demo1';
|
||||
import { GlobalThemeProvider } from '@nocobase/client';
|
||||
|
||||
describe('Page', () => {
|
||||
it('should render correctly', async () => {
|
||||
render(<App1 />, {
|
||||
wrapper: GlobalThemeProvider,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/page title/i)).toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/page content/i)).toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(document.title).toBe('Page Title - NocoBase');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { DocumentTitleProvider, Page, SchemaComponent, SchemaComponentProvider, Application } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page1: {
|
||||
type: 'void',
|
||||
'x-component': 'Page',
|
||||
title: 'Page Title',
|
||||
properties: {
|
||||
content: {
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
'x-content': 'Page Content',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Root = () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Page }}>
|
||||
<DocumentTitleProvider addonAfter={'NocoBase'}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</DocumentTitleProvider>
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const app = new Application({
|
||||
providers: [Root],
|
||||
});
|
||||
|
||||
export default app.getRootComponent();
|
@ -1,28 +0,0 @@
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useCollection_deprecated } from '@nocobase/client';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* label = 'designer' + name + x-component + [x-designer] + [collectionName] + [x-collection-field] + [postfix]
|
||||
* @returns
|
||||
*/
|
||||
export const useGetAriaLabelOfDesigner = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { name: _collectionName } = useCollection_deprecated();
|
||||
const getAriaLabel = useCallback(
|
||||
(name: string, postfix?: string) => {
|
||||
if (!fieldSchema) return '';
|
||||
|
||||
const component = fieldSchema['x-component'];
|
||||
const designer = fieldSchema['x-designer'] ? `-${fieldSchema['x-designer']}` : '';
|
||||
const collectionField = fieldSchema['x-collection-field'] ? `-${fieldSchema['x-collection-field']}` : '';
|
||||
const collectionName = _collectionName ? `-${_collectionName}` : '';
|
||||
postfix = postfix ? `-${postfix}` : '';
|
||||
|
||||
return `designer-${name}-${component}${designer}${collectionName}${collectionField}${postfix}`;
|
||||
},
|
||||
[fieldSchema, _collectionName],
|
||||
);
|
||||
|
||||
return { getAriaLabel };
|
||||
};
|
@ -1,25 +0,0 @@
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useCollection_deprecated } from '@nocobase/client';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* label = 'schema-initializer' + x-component + [x-initializer] + [collectionName] + [postfix]
|
||||
* @returns
|
||||
*/
|
||||
export const useGetAriaLabelOfSchemaInitializer = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { name } = useCollection_deprecated();
|
||||
const getAriaLabel = useCallback(
|
||||
(postfix?: string) => {
|
||||
if (!fieldSchema) return '';
|
||||
const initializer = fieldSchema['x-initializer'] ? `-${fieldSchema['x-initializer']}` : '';
|
||||
const collectionName = name ? `-${name}` : '';
|
||||
postfix = postfix ? `-${postfix}` : '';
|
||||
|
||||
return `schema-initializer-${fieldSchema['x-component']}${initializer}${collectionName}${postfix}`;
|
||||
},
|
||||
[fieldSchema, name],
|
||||
);
|
||||
|
||||
return { getAriaLabel };
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
import { useActionContext } from '@nocobase/client';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* 判断当前区块是否在页面而不是在弹窗中
|
||||
*/
|
||||
export const useIsBlockInPage = () => {
|
||||
const { visible } = useActionContext();
|
||||
|
||||
const isBlockInPage = useCallback(() => {
|
||||
return !visible;
|
||||
}, [visible]);
|
||||
|
||||
return { isBlockInPage };
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
---
|
||||
group:
|
||||
title: Schema Components
|
||||
order: 3
|
||||
---
|
||||
|
||||
# Page
|
||||
|
||||
可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。
|
||||
|
||||
<code src="./demos/demo1.tsx"></code>
|
@ -1,4 +0,0 @@
|
||||
export * from './Page';
|
||||
export * from './FixedBlock';
|
||||
export * from './PageTab.Settings';
|
||||
export * from './Page.Settings';
|
@ -1,132 +0,0 @@
|
||||
import { createStyles } from 'antd-style';
|
||||
import { genStyleHook } from '../__builtins__';
|
||||
|
||||
export const useAClStyles = createStyles(({ css }) => {
|
||||
return css`
|
||||
.ant-table-cell {
|
||||
> .ant-space-horizontal {
|
||||
.ant-space-item-split:has(+ .ant-space-item:empty) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
});
|
||||
|
||||
export const useStyles = genStyleHook('nb-page', (token) => {
|
||||
const { componentCls } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
position: 'relative',
|
||||
zIndex: 20,
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
'&:hover': { '> .general-schema-designer': { display: 'block' } },
|
||||
'.ant-page-header': { zIndex: 1, position: 'relative' },
|
||||
'> .general-schema-designer': {
|
||||
position: 'absolute',
|
||||
zIndex: 999,
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
left: '0',
|
||||
right: '0',
|
||||
display: 'none',
|
||||
border: '0',
|
||||
pointerEvents: 'none',
|
||||
'> .general-schema-designer-icons': {
|
||||
zIndex: 9999,
|
||||
position: 'absolute',
|
||||
right: '2px',
|
||||
top: '2px',
|
||||
lineHeight: '16px',
|
||||
pointerEvents: 'all',
|
||||
'.ant-space-item': {
|
||||
backgroundColor: 'var(--colorSettings)',
|
||||
color: '#fff',
|
||||
lineHeight: '16px',
|
||||
width: '16px',
|
||||
paddingLeft: '1px',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'.pageHeaderCss': {
|
||||
backgroundColor: token.colorBgContainer,
|
||||
paddingInline: token.paddingLG,
|
||||
'&.ant-page-header-has-footer': {
|
||||
paddingTop: token.paddingSM,
|
||||
paddingBottom: '0',
|
||||
'.ant-page-header-heading-left': {},
|
||||
'.ant-page-header-footer': { marginBlockStart: '0' },
|
||||
},
|
||||
'.ant-tabs-nav': { marginBottom: '0' },
|
||||
'.ant-page-header-heading-title': {
|
||||
color: token.colorText,
|
||||
},
|
||||
},
|
||||
|
||||
'.height0': {
|
||||
fontSize: 0,
|
||||
height: 0,
|
||||
},
|
||||
|
||||
'.addTabBtn': {
|
||||
borderColor: 'var(--colorSettings)',
|
||||
color: 'var(--colorSettings)',
|
||||
},
|
||||
|
||||
'.designerCss': {
|
||||
position: 'relative',
|
||||
'&:hover': { '> .general-schema-designer': { display: 'block' } },
|
||||
'&.nb-action-link': {
|
||||
'> .general-schema-designer': {
|
||||
top: '-10px',
|
||||
bottom: '-10px',
|
||||
left: '-10px',
|
||||
right: '-10px',
|
||||
},
|
||||
},
|
||||
'> .general-schema-designer': {
|
||||
position: 'absolute',
|
||||
zIndex: 999,
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
left: '0',
|
||||
right: '0',
|
||||
display: 'none',
|
||||
background: 'var(--colorBgSettingsHover)',
|
||||
border: '0',
|
||||
pointerEvents: 'none',
|
||||
'> .general-schema-designer-icons': {
|
||||
position: 'absolute',
|
||||
right: '2px',
|
||||
top: '2px',
|
||||
lineHeight: '16px',
|
||||
pointerEvents: 'all',
|
||||
'.ant-space-item': {
|
||||
backgroundColor: 'var(--colorSettings)',
|
||||
color: '#fff',
|
||||
lineHeight: '16px',
|
||||
width: '16px',
|
||||
paddingLeft: '1px',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'.pageWithFixedBlockCss': {
|
||||
height: '100%',
|
||||
'> .nb-grid:not(:last-child)': {
|
||||
'> .nb-schema-initializer-button': { display: 'none' },
|
||||
},
|
||||
},
|
||||
|
||||
'.nb-page-wrapper': {
|
||||
margin: token.marginLG,
|
||||
flex: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
Loading…
Reference in New Issue
Block a user