refactor: migrate @emotion/css to antd-style (#1043)

Reviewed-on: daoyoucloud/tachybase#1043
This commit is contained in:
sealday 2024-05-23 21:01:46 +08:00
parent 63c351ea1a
commit f2a2cc228f
67 changed files with 2060 additions and 1983 deletions

View File

@ -1,8 +1,10 @@
import Icon, { RightOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { uid } from '@tachybase/schema';
import { Menu, MenuProps, theme } from 'antd';
import React, { FC, ReactNode, useMemo } from 'react';
import { uid } from '@tachybase/schema';
import Icon, { RightOutlined } from '@ant-design/icons';
import { Menu, MenuProps, theme } from 'antd';
import { createStyles } from 'antd-style';
import { useCompile } from '../../../schema-component';
import { useSchemaInitializerItem } from '../context';
import { useSchemaInitializerMenuItems } from '../hooks';
@ -18,6 +20,30 @@ export interface SchemaInitializerSubMenuProps {
children?: SchemaInitializerOptions['items'];
}
const useStyles = createStyles(({ css, token }) => {
return {
menu: css`
box-shadow: none !important;
border-inline-end: 0 !important;
.ant-menu-sub {
max-height: 50vh !important;
}
.ant-menu-item {
margin-block: 0;
}
.ant-menu-root {
margin: 0 -${token.margin}px;
.ant-menu-submenu-title,
.ant-menu-item-only-child {
margin-inline: 0;
margin-block: 0;
width: 100%;
}
}
`,
};
});
const SchemaInitializerSubMenuContext = React.createContext<{ isInMenu?: true }>({});
const SchemaInitializerMenuProvider = (props) => {
return (
@ -34,6 +60,7 @@ export const SchemaInitializerMenu: FC<MenuProps> = (props) => {
const { componentCls, hashId } = useSchemaInitializerStyles();
const { items, ...others } = props;
const { token } = theme.useToken();
const { styles } = useStyles();
const itemsWithPopupClass = useMemo(
() => items.map((item) => ({ ...item, popupClassName: `${hashId} ${componentCls}-menu-sub` })),
[componentCls, hashId, items],
@ -44,25 +71,7 @@ export const SchemaInitializerMenu: FC<MenuProps> = (props) => {
<Menu
selectable={false}
expandIcon={<RightOutlined style={{ fontSize: token.fontSizeSM, color: token.colorTextDescription }} />}
rootClassName={css`
box-shadow: none !important;
border-inline-end: 0 !important;
.ant-menu-sub {
max-height: 50vh !important;
}
.ant-menu-item {
margin-block: 0;
}
.ant-menu-root {
margin: 0 -${token.margin}px;
.ant-menu-submenu-title,
.ant-menu-item-only-child {
margin-inline: 0;
margin-block: 0;
width: 100%;
}
}
`}
rootClassName={styles.menu}
items={itemsWithPopupClass}
{...others}
/>

View File

@ -1,8 +1,9 @@
import { ISchema, observer } from '@tachybase/schema';
import { ConfigProvider, Popover, theme } from 'antd';
import React, { ComponentType, useCallback, useMemo, useState } from 'react';
import { ISchema, observer } from '@tachybase/schema';
import { ConfigProvider, Popover, theme } from 'antd';
import { createStyles } from 'antd-style';
import { css } from '@emotion/css';
import { useNiceDropdownMaxHeight } from '../../../common/useNiceDropdownHeight';
import { useFlag } from '../../../flag-provider';
import { useDesignable } from '../../../schema-component';
@ -11,6 +12,18 @@ import { SchemaInitializerContext } from '../context';
import { SchemaInitializerOptions } from '../types';
const defaultWrap = (s: ISchema) => s;
const useStyles = createStyles(({ css, token }) => {
return {
popover: css`
.ant-popover-inner {
padding: ${`${token.paddingXXS}px 0`};
.ant-menu-submenu-title {
margin-block: 0;
}
}
`,
};
});
export function withInitializer<T>(C: ComponentType<T>) {
const WithInitializer = observer(
@ -47,6 +60,7 @@ export function withInitializer<T>(C: ComponentType<T>) {
const { wrapSSR, hashId, componentCls } = useSchemaInitializerStyles();
const [visible, setVisible] = useState(false);
const { token } = theme.useToken();
const { styles } = useStyles();
const dropdownMaxHeight = useNiceDropdownMaxHeight([visible]);
const cProps = useMemo(
@ -79,14 +93,7 @@ export function withInitializer<T>(C: ComponentType<T>) {
placement={'bottomLeft'}
{...popoverProps}
arrow={false}
overlayClassName={css`
.ant-popover-inner {
padding: ${`${token.paddingXXS}px 0`};
.ant-menu-submenu-title {
margin-block: 0;
}
}
`}
overlayClassName={styles.popover}
open={visible}
onOpenChange={setVisible}
content={wrapSSR(

View File

@ -1,28 +1,31 @@
import { css } from '@emotion/css';
import { Field, GeneralField } from '@tachybase/schema';
import { RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { Field, GeneralField, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { Col, Row } from 'antd';
import { createStyles } from 'antd-style';
import merge from 'deepmerge';
import template from 'lodash/template';
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import {
DataBlockProvider,
TableFieldResource,
WithoutTableFieldResource,
useCollectionManager,
useCollectionParentRecord,
useCollectionParentRecordData,
useCollectionRecord,
useDataBlockProps,
useDataBlockRequest,
useDataBlockResource,
useDesignable,
useRecord,
WithoutTableFieldResource,
} from '../';
import { ACLCollectionProvider } from '../acl/ACLProvider';
import {
CollectionProvider_deprecated,
useCollectionManager_deprecated,
useCollection_deprecated,
useCollectionManager_deprecated,
} from '../collection-manager';
import { DataBlockCollector } from '../filter-provider/FilterProvider';
import { RecordProvider, useRecordIndex } from '../record-provider';
@ -119,9 +122,31 @@ export const useBlockRequestContext = () => {
return useContext(BlockRequestContext_deprecated);
};
const useStyles = createStyles(({ css }) => {
return {
row: css`
height: 100%;
`,
left: css`
width: 200px;
flex: 0 0 auto;
`,
right: css`
flex: 1 1 auto;
min-width: 0;
`,
rightContainer: css`
display: flex;
flex-direction: column;
height: 100%;
`,
};
});
export const RenderChildrenWithAssociationFilter: React.FC<any> = (props) => {
const fieldSchema = useFieldSchema();
const { findComponent } = useDesignable();
const { styles } = useStyles();
const field = useField();
const Component = findComponent(field.component?.[0]) || React.Fragment;
const associationFilterSchema = fieldSchema.reduceProperties((buf, s) => {
@ -134,39 +159,16 @@ export const RenderChildrenWithAssociationFilter: React.FC<any> = (props) => {
if (associationFilterSchema) {
return (
<Component {...field.componentProps}>
<Row
className={css`
height: 100%;
`}
gutter={16}
wrap={false}
>
<Col
className={css`
width: 200px;
flex: 0 0 auto;
`}
style={props.associationFilterStyle}
>
<Row className={styles.row} gutter={16} wrap={false}>
<Col className={styles.left} style={props.associationFilterStyle}>
<RecursionField
schema={fieldSchema}
onlyRenderProperties
filterProperties={(s) => s['x-component'] === 'AssociationFilter'}
/>
</Col>
<Col
className={css`
flex: 1 1 auto;
min-width: 0;
`}
>
<div
className={css`
display: flex;
flex-direction: column;
height: 100%;
`}
>
<Col className={styles.right}>
<div className={styles.rightContainer}>
<RecursionField
schema={fieldSchema}
onlyRenderProperties

View File

@ -1,9 +1,11 @@
import { DeleteOutlined, ExclamationCircleFilled } from '@ant-design/icons';
import { css } from '@emotion/css';
import { useForm } from '@tachybase/schema';
import { Button, message } from 'antd';
import React, { useState } from 'react';
import { useForm } from '@tachybase/schema';
import { DeleteOutlined, ExclamationCircleFilled } from '@ant-design/icons';
import { Button, message } from 'antd';
import { createStyles } from 'antd-style';
import { useTranslation } from 'react-i18next';
import {
useActionContext,
useCollectionManager_deprecated,
@ -54,6 +56,20 @@ export const useDestroyAction = () => {
};
};
const useStyles = createStyles(({ css }) => {
return {
modal: css`
.ant-modal-body {
margin-left: 35px;
margin-bottom: 35px;
.ant-checkbox-wrapper {
height: 25px;
}
}
`,
};
});
export const useBulkDestroyAction = () => {
const { state, setState, refresh } = useResourceActionContext();
const { resource } = useResourceContext();
@ -80,6 +96,7 @@ export const useBulkDestroyAction = () => {
export const DeleteCollectionAction = (props) => {
const { scope, getContainer, item: record, children, isBulk, useAction, ...otherProps } = props;
const { t } = useTranslation();
const { styles } = useStyles();
const [visible, setVisible] = useState(false);
const getDestroyCollectionAction = () => {
if (isBulk) {
@ -124,15 +141,7 @@ export const DeleteCollectionAction = (props) => {
'x-component-props': {
width: 520,
getContainer: '{{ getContainer }}',
className: css`
.ant-modal-body {
margin-left: 35px;
margin-bottom: 35px;
.ant-checkbox-wrapper {
height: 25px;
}
}
`,
className: styles.modal,
},
properties: {
info: {

View File

@ -1,16 +1,29 @@
import { useField, useForm } from '@tachybase/schema';
import { useAsyncData } from '../../../../async-data-provider';
import React, { useEffect } from 'react';
import { Input, SchemaComponent } from '../../../../schema-component';
import { css } from '@emotion/css';
import { Button } from 'antd';
import { Field, useField } from '@tachybase/schema';
import { EditOutlined, RightSquareOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import { createStyles } from 'antd-style';
import { useTranslation } from 'react-i18next';
import { Field } from '@tachybase/schema';
import { useAsyncData } from '../../../../async-data-provider';
import { Input } from '../../../../schema-component';
const useStyles = createStyles(({ css }) => {
return {
input: css`
position: relative;
.ant-input {
width: 100%;
}
`,
};
});
export const SQLInput = ({ disabled }) => {
const { t } = useTranslation();
const { run, loading, error } = useAsyncData();
const { styles } = useStyles();
const field = useField<Field>();
const execute = () => {
if (!field.value) {
@ -39,14 +52,7 @@ export const SQLInput = ({ disabled }) => {
}, [field, error]);
return (
<div
className={css`
position: relative;
.ant-input {
width: 100%;
}
`}
>
<div className={styles.input}>
<Input.TextArea value={field.value} disabled={disabled} onChange={(e) => (field.value = e.target.value)} />
<Button.Group>
<Button onClick={toggleEditing} ghost size="small" type="primary" icon={<EditOutlined />}>

View File

@ -1,4 +1,3 @@
import { css } from '@emotion/css';
import { PresetFields } from '../components/PresetFields';
export const defaultConfigurableProperties = {
@ -56,13 +55,6 @@ export const defaultConfigurableProperties = {
type: 'void',
'x-decorator': 'FormItem',
'x-visible': '{{ createOnly }}',
'x-decorator-props': {
className: css`
.ant-formily-item {
margin-bottom: 0;
}
`,
},
'x-component': PresetFields,
},
};

View File

@ -1,7 +1,7 @@
import type { ThemeConfig as _ThemeConfig } from 'antd';
import { AliasToken } from 'antd/es/theme/internal';
export interface CustomToken extends AliasToken {
export interface TachybaseToken {
/** 顶部导航栏主色 */
colorPrimaryHeader: string;
/** 导航栏背景色 */
@ -25,7 +25,14 @@ export interface CustomToken extends AliasToken {
colorBorderSettingsHover: string;
}
export interface CustomToken extends AliasToken, TachybaseToken {}
export interface ThemeConfig extends _ThemeConfig {
name?: string;
token?: Partial<CustomToken>;
}
declare module 'antd-style' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CustomToken extends TachybaseToken {}
}

View File

@ -7,7 +7,6 @@ import 'dayjs/plugin/localeData';
import 'dayjs/plugin/quarterOfYear';
import 'dayjs/plugin/utc';
import 'dayjs/plugin/weekday';
// 重置浏览器样式
import 'antd/dist/reset.css';
import './global.less';

View File

@ -1,10 +1,12 @@
import { DisconnectOutlined, LoadingOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import React, { FC } from 'react';
import { observer } from '@tachybase/schema';
import { getSubAppName } from '@tachybase/sdk';
import { DisconnectOutlined, LoadingOutlined } from '@ant-design/icons';
import { Button, Modal, Result, Spin } from 'antd';
import React, { FC } from 'react';
import { createStyles } from 'antd-style';
import { Navigate, useNavigate } from 'react-router-dom';
import { ACLPlugin } from '../acl';
import { useAPIClient } from '../api-client';
import { Application } from '../application';
@ -30,6 +32,17 @@ const AppSpin = () => {
);
};
const useStyles = createStyles(({ css }) => {
return {
modal: css`
top: 50%;
position: absolute;
width: 100%;
transform: translate(0, -50%);
`,
};
});
const useErrorProps = (app: Application, error: any) => {
const api = useAPIClient();
if (!error) {
@ -68,15 +81,11 @@ const useErrorProps = (app: Application, error: any) => {
const AppError: FC<{ error: Error; app: Application }> = observer(
({ app, error }) => {
const props = useErrorProps(app, error);
const { styles } = useStyles();
return (
<div>
<Result
className={css`
top: 50%;
position: absolute;
width: 100%;
transform: translate(0, -50%);
`}
className={styles.modal}
status="error"
title={app.i18n.t('App error')}
subTitle={app.i18n.t(error?.message)}
@ -201,15 +210,11 @@ const getProps = (app: Application) => {
const AppMaintaining: FC<{ app: Application; error: Error }> = observer(
({ app }) => {
const { icon, status, title, subTitle } = getProps(app);
const { styles } = useStyles();
return (
<div>
<Result
className={css`
top: 50%;
position: absolute;
width: 100%;
transform: translate(0, -50%);
`}
className={styles.modal}
icon={icon}
status={status}
title={app.i18n.t(title)}

View File

@ -1,11 +1,13 @@
import { css } from '@emotion/css';
import { SchemaOptionsContext } from '@tachybase/schema';
import { get } from 'lodash';
import React, { useContext } from 'react';
import { SchemaOptionsContext } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import { get } from 'lodash';
import { useACLRoleContext } from '../acl/ACLProvider';
import { PinnedPluginListContext } from './context';
export const PinnedPluginListProvider: React.FC<{ items: any }> = (props) => {
export const PinnedPluginListProvider = (props: { items: any; children: React.ReactNode }) => {
const { children, items } = props;
const ctx = useContext(PinnedPluginListContext);
return (
@ -15,30 +17,35 @@ export const PinnedPluginListProvider: React.FC<{ items: any }> = (props) => {
);
};
const useStyles = createStyles(({ css }) => {
return {
container: css`
display: inline-block;
.ant-btn {
border: 0;
height: 46px;
width: 46px;
border-radius: 0;
background: none;
color: rgba(255, 255, 255, 0.65);
&:hover {
background: rgba(255, 255, 255, 0.1) !important;
}
}
`,
};
});
export const PinnedPluginList = () => {
const { allowAll, snippets } = useACLRoleContext();
const getSnippetsAllow = (aclKey) => {
return allowAll || snippets?.includes(aclKey);
};
const { styles } = useStyles();
const ctx = useContext(PinnedPluginListContext);
const { components } = useContext(SchemaOptionsContext);
return (
<div
className={css`
.ant-btn {
border: 0;
height: 46px;
width: 46px;
border-radius: 0;
background: none;
color: rgba(255, 255, 255, 0.65);
&:hover {
background: rgba(255, 255, 255, 0.1) !important;
}
}
`}
style={{ display: 'inline-block' }}
>
<div className={styles.container}>
{Object.keys(ctx.items)
.sort((a, b) => ctx.items[a].order - ctx.items[b].order)
.filter((key) => getSnippetsAllow(ctx.items[key].snippet))

View File

@ -1,18 +1,18 @@
import { App, Card, Divider, Popconfirm, Space, Switch, Typography, message } from 'antd';
import classnames from 'classnames';
import React, { FC, useState } from 'react';
import { ClockCircleOutlined, DeleteOutlined, ReadOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons';
import { App, Card, Divider, message, Popconfirm, Space, Switch, Typography } from 'antd';
import classnames from 'classnames';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { DeleteOutlined, ReadOutlined, ReloadOutlined, SettingOutlined, ClockCircleOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { useAPIClient } from '../api-client';
import { useApp } from '../application';
import { PluginDetail } from './PluginDetail';
import { NPM_REGISTRY_ADDRESS } from './PluginForm/form/PluginNpmForm';
import { PluginUpgradeModal } from './PluginForm/modal/PluginUpgradeModal';
import { useStyles } from './style';
import type { IPluginData } from './types';
import { NPM_REGISTRY_ADDRESS } from './PluginForm/form/PluginNpmForm';
interface IPluginInfo extends IPluginCard {
onClick: () => void;
@ -111,28 +111,7 @@ function PluginInfo(props: IPluginInfo) {
// style={{ marginBottom: theme.marginLG }}
title={<div>{title}</div>}
hoverable
className={css`
.ant-card-actions {
li .ant-space {
gap: 2px !important;
}
li a {
.anticon {
margin-right: 3px;
/* display: none; */
}
}
li:last-child {
width: 20% !important;
}
li:first-child {
width: 80% !important;
border-inline-end: 0;
text-align: left;
padding-left: 16px;
}
}
`}
className={styles.card}
actions={[
<Space split={<Divider type="vertical" />} key={'1'}>
<a

View File

@ -1,39 +1,22 @@
export * from './PluginManagerLink';
import type { TableProps } from 'antd';
import React, { useEffect, useMemo, useState } from 'react';
import { fuzzysearch } from '@tachybase/utils/client';
import { PageHeader } from '@ant-design/pro-layout';
import { useDebounce } from 'ahooks';
import {
Button,
Card,
Col,
Divider,
Input,
List,
Result,
Row,
Space,
Spin,
Tabs,
Table,
Tag,
Radio,
Form,
Select,
} from 'antd';
import { Card, Form, Input, Radio, Result, Select, Space, Spin, Table, Tag, type TableProps } from 'antd';
import _ from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useParams } from 'react-router-dom';
import { css } from '@emotion/css';
import { useACLRoleContext } from '../acl/ACLProvider';
import { useRequest } from '../api-client';
import { i18n } from '../i18n';
import { useToken } from '../style';
import { PluginCard, SwitchAction } from './PluginCard';
import { SwitchAction } from './PluginCard';
import { useStyles } from './style';
import { IPluginData } from './types';
import { fuzzysearch } from '@tachybase/utils/client';
import { i18n } from '../i18n';
export * from './PluginManagerLink';
export interface TData {
data: IPluginData[];

View File

@ -1,11 +1,12 @@
import React from 'react';
import { ApiOutlined, SettingOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { Button, Card, Dropdown, Popover, Tooltip } from 'antd';
import React, { useState } from 'react';
import { Button, Dropdown, Tooltip } from 'antd';
import { useTranslation } from 'react-i18next';
import { Link, useNavigate } from 'react-router-dom';
import { useApp } from '../application';
import { ActionContextProvider, useCompile } from '../schema-component';
import { useCompile } from '../schema-component';
import { useToken } from '../style';
export const PluginManagerLink = () => {
@ -27,14 +28,10 @@ export const PluginManagerLink = () => {
};
export const SettingsCenterDropdown = () => {
const [visible, setVisible] = useState(false);
const compile = useCompile();
const { t } = useTranslation();
const { token } = useToken();
const navigate = useNavigate();
const app = useApp();
const settings = app.pluginSettingsManager.getList();
const [open, setOpen] = useState(false);
return (
<Dropdown
menu={{
@ -56,86 +53,7 @@ export const SettingsCenterDropdown = () => {
<Button
data-testid="plugin-settings-button"
icon={<SettingOutlined style={{ color: token.colorTextHeaderMenu }} />}
// title={t('All plugin settings')}
/>
</Dropdown>
);
return (
settings.length > 0 && (
<ActionContextProvider value={{ visible, setVisible }}>
<Popover
open={open}
onOpenChange={(open) => {
setOpen(open);
}}
arrow={false}
content={
<div style={{ maxWidth: '21rem', overflow: 'auto', maxHeight: '50vh' }}>
<Card
bordered={false}
className={css`
box-shadow: none;
`}
style={{ boxShadow: 'none' }}
>
{settings
.filter((v) => v.isTopLevel !== false)
.map((setting) => (
<Card.Grid
style={{
width: settings.length === 1 ? '100%' : settings.length === 2 ? '50%' : '33.33%',
}}
className={css`
cursor: pointer;
padding: 0 !important;
box-shadow: none !important;
&:hover {
border-radius: ${token.borderRadius}px;
background: rgba(0, 0, 0, 0.045);
}
`}
key={setting.name}
>
<a
role="button"
aria-label={setting.name}
onClick={(e) => {
e.preventDefault();
setOpen(false);
navigate(setting.path);
}}
title={compile(setting.title)}
style={{ display: 'block', color: 'inherit', minWidth: '4.5rem', padding: token.marginSM }}
href={setting.path}
>
<div style={{ fontSize: '1.2rem', textAlign: 'center', marginBottom: '0.3rem' }}>
{setting.icon || <SettingOutlined />}
</div>
<div
style={{
textAlign: 'center',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: token.fontSizeSM,
}}
>
{compile(setting.title)}
</div>
</a>
</Card.Grid>
))}
</Card>
</div>
}
>
<Button
data-testid="plugin-settings-button"
icon={<SettingOutlined style={{ color: token.colorTextHeaderMenu }} />}
// title={t('All plugin settings')}
/>
</Popover>
</ActionContextProvider>
)
);
};

View File

@ -1,12 +1,13 @@
import { PageHeader } from '@ant-design/pro-layout';
import { css } from '@emotion/css';
import { Layout, Menu, Result } from 'antd';
import _, { get } from 'lodash';
import React, { createContext, useCallback, useMemo } from 'react';
import { PageHeader } from '@ant-design/pro-layout';
import { Layout, Menu, Result } from 'antd';
import _ from 'lodash';
import { Navigate, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom';
import { useStyles } from './style';
import { ADMIN_SETTINGS_PATH, PluginSettingsPageType, useApp } from '../application';
import { useCompile } from '../schema-component';
import { useStyles } from './style';
export const SettingsCenterContext = createContext<any>({});
SettingsCenterContext.displayName = 'SettingsCenterContext';

View File

@ -1,6 +1,6 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ token }) => {
export const useStyles = createStyles(({ token, css }) => {
return {
cardActionDisabled: {
color: token.colorTextDisabled,
@ -55,5 +55,27 @@ export const useStyles = createStyles(({ token }) => {
fontWeight: 'normal',
fontSize: token.fontSize,
},
card: css`
.ant-card-actions {
li .ant-space {
gap: 2px !important;
}
li a {
.anticon {
margin-right: 3px;
/* display: none; */
}
}
li:last-child {
width: 20% !important;
}
li:first-child {
width: 80% !important;
border-inline-end: 0;
text-align: left;
padding-left: 16px;
}
}
`,
};
});

View File

@ -1,32 +1,154 @@
import { css } from '@emotion/css';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useSessionStorageState } from 'ahooks';
import { App, ConfigProvider, Divider, Layout } from 'antd';
import { createGlobalStyle } from 'antd-style';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { createGlobalStyle, createStyles } from 'antd-style';
import { Link, Outlet, useLocation, useMatch, useNavigate, useParams } from 'react-router-dom';
import {
ACLRolesCheckProvider,
CurrentAppInfoProvider,
CurrentUser,
findByUid,
findMenuItem,
NavigateIfNotSignIn,
PinnedPluginList,
RemoteCollectionManagerProvider,
RemoteSchemaTemplateManagerPlugin,
RemoteSchemaTemplateManagerProvider,
SchemaComponent,
findByUid,
findMenuItem,
useACLRoleContext,
useAdminSchemaUid,
useDocumentTitle,
useRequest,
useSystemSettings,
useToken,
} from '../../../';
import { Plugin } from '../../../application/Plugin';
import { useAppSpin } from '../../../application/hooks/useAppSpin';
import { Plugin } from '../../../application/Plugin';
import { VariablesProvider } from '../../../variables';
const useStyles = createStyles(({ css, token }) => {
return {
header: css`
.ant-menu.ant-menu-dark .ant-menu-item-selected,
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,
.ant-menu-submenu-horizontal.ant-menu-submenu-selected {
background-color: ${token.colorBgHeaderMenuActive} !important;
color: ${token.colorTextHeaderMenuActive} !important;
}
.ant-menu-submenu-horizontal.ant-menu-submenu-selected > .ant-menu-submenu-title {
color: ${token.colorTextHeaderMenuActive} !important;
}
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover {
background-color: ${token.colorBgHeaderMenuHover} !important;
color: ${token.colorTextHeaderMenuHover} !important;
}
position: fixed;
left: 0;
right: 0;
height: var(--nb-header-height);
line-height: var(--nb-header-height);
padding: 0;
z-index: 100;
background-color: ${token.colorBgHeader} !important;
.ant-menu {
background-color: transparent;
}
.ant-menu-item,
.ant-menu-submenu-horizontal {
color: ${token.colorTextHeaderMenu} !important;
}
`,
headerA: css`
position: relative;
width: 100%;
height: 100%;
display: flex;
`,
headerB: css`
position: relative;
z-index: 1;
flex: 1 1 auto;
display: flex;
height: 100%;
`,
titleContainer: css`
display: inline-flex;
flex-shrink: 0;
color: #fff;
padding: 0;
align-items: center;
padding: 0 12px 0 12px;
`,
logo: css`
object-fit: contain;
height: 28px;
`,
title: css`
color: #fff;
height: 32px;
margin: 0 0 0 12px;
font-weight: 600;
font-size: 18px;
line-height: 32px;
`,
right: css`
position: relative;
flex-shrink: 0;
height: 100%;
z-index: 10;
`,
editor: css`
flex: 1 1 auto;
width: 0;
`,
sider: css`
height: 100%;
/* position: fixed; */
position: relative;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0);
z-index: 100;
.ant-layout-sider-children {
top: var(--nb-header-height);
position: fixed;
width: 200px;
height: calc(100vh - var(--nb-header-height));
}
`,
main: css`
display: flex;
flex-direction: column;
position: relative;
overflow-y: auto;
height: 100vh;
max-height: 100vh;
> div {
position: relative;
}
.ant-layout-footer {
position: absolute;
bottom: 0;
text-align: center;
width: 100%;
z-index: 0;
padding: 0px 50px;
}
`,
mainHeader: css`
flex-shrink: 0;
height: var(--nb-header-height);
line-height: var(--nb-header-height);
background: transparent;
pointer-events: none;
`,
};
});
const filterByACL = (schema, options) => {
const { allowAll, allowMenuItemIds = [] } = options;
if (allowAll) {
@ -279,111 +401,24 @@ export const InternalAdminLayout = (props: any) => {
const result = useSystemSettings();
// const { service } = useCollectionManager_deprecated();
const params = useParams<any>();
const { token } = useToken();
const { styles } = useStyles();
return (
<Layout>
<GlobalStyleForAdminLayout />
<Layout.Header
className={css`
.ant-menu.ant-menu-dark .ant-menu-item-selected,
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,
.ant-menu-submenu-horizontal.ant-menu-submenu-selected {
background-color: ${token.colorBgHeaderMenuActive} !important;
color: ${token.colorTextHeaderMenuActive} !important;
}
.ant-menu-submenu-horizontal.ant-menu-submenu-selected > .ant-menu-submenu-title {
color: ${token.colorTextHeaderMenuActive} !important;
}
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover {
background-color: ${token.colorBgHeaderMenuHover} !important;
color: ${token.colorTextHeaderMenuHover} !important;
}
position: fixed;
left: 0;
right: 0;
height: var(--nb-header-height);
line-height: var(--nb-header-height);
padding: 0;
z-index: 100;
background-color: ${token.colorBgHeader} !important;
.ant-menu {
background-color: transparent;
}
.ant-menu-item,
.ant-menu-submenu-horizontal {
color: ${token.colorTextHeaderMenu} !important;
}
`}
>
<div
className={css`
position: relative;
width: 100%;
height: 100%;
display: flex;
`}
>
<div
className={css`
position: relative;
z-index: 1;
flex: 1 1 auto;
display: flex;
height: 100%;
`}
>
<div
className={css`
display: inline-flex;
flex-shrink: 0;
color: #fff;
padding: 0;
align-items: center;
padding: 0 12px 0 12px;
`}
>
<img
className={css`
object-fit: contain;
height: 28px;
`}
src={result?.data?.data?.logo?.url}
/>
<h1
className={css`
color: #fff;
height: 32px;
margin: 0 0 0 12px;
font-weight: 600;
font-size: 18px;
line-height: 32px;
`}
>
{result?.data?.data?.title}
</h1>
<Layout.Header className={styles.header}>
<div className={styles.headerA}>
<div className={styles.headerB}>
<div className={styles.titleContainer}>
<img className={styles.logo} src={result?.data?.data?.logo?.url} />
<h1 className={styles.title}>{result?.data?.data?.title}</h1>
</div>
<div
className={css`
flex: 1 1 auto;
width: 0;
`}
>
<div className={styles.editor}>
<SetThemeOfHeaderSubmenu>
<MenuEditor sideMenuRef={sideMenuRef} />
</SetThemeOfHeaderSubmenu>
</div>
</div>
<div
className={css`
position: relative;
flex-shrink: 0;
height: 100%;
z-index: 10;
`}
>
<div className={styles.right}>
<PinnedPluginList />
<ConfigProvider
theme={{
@ -398,59 +433,10 @@ export const InternalAdminLayout = (props: any) => {
</div>
</div>
</Layout.Header>
{params.name && (
<Layout.Sider
className={css`
height: 100%;
/* position: fixed; */
position: relative;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0);
z-index: 100;
.ant-layout-sider-children {
top: var(--nb-header-height);
position: fixed;
width: 200px;
height: calc(100vh - var(--nb-header-height));
}
`}
theme={'light'}
ref={sideMenuRef}
></Layout.Sider>
)}
<Layout.Content
className={css`
display: flex;
flex-direction: column;
position: relative;
overflow-y: auto;
height: 100vh;
max-height: 100vh;
> div {
position: relative;
}
.ant-layout-footer {
position: absolute;
bottom: 0;
text-align: center;
width: 100%;
z-index: 0;
padding: 0px 50px;
}
`}
>
<header
className={css`
flex-shrink: 0;
height: var(--nb-header-height);
line-height: var(--nb-header-height);
background: transparent;
pointer-events: none;
`}
></header>
{params.name && <Layout.Sider className={styles.sider} theme={'light'} ref={sideMenuRef}></Layout.Sider>}
<Layout.Content className={styles.main}>
<header className={styles.mainHeader}></header>
<Outlet />
{/* {service.contentLoading ? render() : <Outlet />} */}
</Layout.Content>
</Layout>
);

View File

@ -1,12 +1,30 @@
import { css } from '@emotion/css';
import { observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { Modal, ModalProps } from 'antd';
import classNames from 'classnames';
import React from 'react';
import { observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { Modal, ModalProps } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import { OpenSize, useActionContext } from '.';
import { useSetAriaLabelForModal } from './hooks/useSetAriaLabelForModal';
import { ComposedActionDrawer } from './types';
const useStyles = createStyles(({ css }) => {
return {
container: css`
&.nb-action-popup {
.ant-modal-header {
display: none;
}
.ant-modal-content {
background: var(--nb-box-bg);
}
}
`,
};
});
const openSizeWidthMap = new Map<OpenSize, string>([
['small', '40%'],
['middle', '60%'],
@ -15,6 +33,7 @@ const openSizeWidthMap = new Map<OpenSize, string>([
export const ActionModal: ComposedActionDrawer<ModalProps> = observer(
(props) => {
const { footerNodeName = 'Action.Modal.Footer', width, ...others } = props;
const { styles } = useStyles();
const { visible, setVisible, openSize = 'middle', modalProps } = useActionContext();
const actualWidth = width ?? openSizeWidthMap.get(openSize);
const schema = useFieldSchema();
@ -43,21 +62,7 @@ export const ActionModal: ComposedActionDrawer<ModalProps> = observer(
destroyOnClose
open={visible}
onCancel={() => setVisible(false, true)}
className={classNames(
others.className,
modalProps?.className,
css`
&.nb-action-popup {
.ant-modal-header {
display: none;
}
.ant-modal-content {
background: var(--nb-box-bg);
}
}
`,
)}
className={classNames(others.className, modalProps?.className, styles.container)}
footer={
footerSchema ? (
<RecursionField

View File

@ -1,7 +1,9 @@
import { css } from '@emotion/css';
import { observer, RecursionField, SchemaExpressionScopeContext, useField, useFieldSchema } from '@tachybase/schema';
import React, { useContext } from 'react';
import { observer, RecursionField, SchemaExpressionScopeContext, useField, useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import { createPortal } from 'react-dom';
import { useActionContext } from '.';
import { ComposedActionDrawer } from './types';
@ -10,11 +12,24 @@ const useScope = (key: string) => {
return scope[key];
};
const useStyles = createStyles(({ css }) => {
return {
container: css`
display: flex;
width: 100%;
.ant-btn {
margin-right: 8px;
}
`,
};
});
export const ActionPage: ComposedActionDrawer = observer(
(props: any) => {
const { footerNodeName = 'Action.Page.Footer', ...others } = props;
const { containerRefKey, visible, setVisible } = useActionContext();
const containerRef = useScope(containerRefKey);
const { styles } = useStyles();
const schema = useFieldSchema();
const field = useField();
const footerSchema = schema.reduceProperties((buf, s) => {
@ -38,17 +53,7 @@ export const ActionPage: ComposedActionDrawer = observer(
}}
/>
{footerSchema && (
<div
className={css`
display: flex;
/* justify-content: flex-end; */
/* flex-direction: row-reverse; */
width: 100%;
.ant-btn {
margin-right: 8px;
}
`}
>
<div className={styles.container}>
<RecursionField
basePath={field.address}
schema={schema}

View File

@ -1,13 +1,23 @@
import { observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { Button, Drawer, Space } from 'antd';
import classNames from 'classnames';
import React from 'react';
import { OpenSize } from '.';
import { observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { Button, Drawer, Space } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { useStyles } from './Action.Drawer.style';
import { useActionContext } from './hooks';
import { ComposedActionDrawer } from './types';
import { css } from '@emotion/css';
import { useTranslation } from 'react-i18next';
const useStyles2 = createStyles(({ css }) => {
return {
container: css`
width: 100%;
justify-content: flex-end;
`,
};
});
export const ActionSheet: ComposedActionDrawer = observer(
(props) => {
@ -17,6 +27,7 @@ export const ActionSheet: ComposedActionDrawer = observer(
const schema = useFieldSchema();
const field = useField();
const { componentCls, hashId } = useStyles();
const { styles } = useStyles2();
const footerSchema = schema.reduceProperties((buf, s) => {
if (s['x-component'] === footerNodeName) {
return s;
@ -40,12 +51,7 @@ export const ActionSheet: ComposedActionDrawer = observer(
onClose={() => setVisible(false, true)}
rootClassName={classNames(componentCls, hashId, drawerProps?.className, others.className, 'reset')}
footer={
<Space
className={css`
width: 100%;
justify-content: flex-end;
`}
>
<Space className={styles.container}>
<Button type="primary" onClick={() => setVisible(false, true)}>
{t('Close')}
</Button>

View File

@ -1,12 +1,14 @@
import { cx } from '@emotion/css';
import { RecursionField, observer, useFieldSchema } from '@tachybase/schema';
import { Space } from 'antd';
import React, { CSSProperties, useContext } from 'react';
import { observer, RecursionField, useFieldSchema } from '@tachybase/schema';
import { Space } from 'antd';
import cx from 'classnames';
import { createPortal } from 'react-dom';
import { DndContext } from '../../common';
import { useDesignable, useProps } from '../../hooks';
import { useSchemaInitializerRender } from '../../../application';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { DndContext } from '../../common';
import { useDesignable, useProps } from '../../hooks';
interface ActionBarContextForceProps {
layout?: 'one-column' | 'tow-columns';
@ -35,7 +37,7 @@ export const useActionBarContext = () => {
return useContext(ActionBarContext);
};
const Portal: React.FC = (props) => {
const Portal = (props) => {
const filedSchema = useFieldSchema();
const { container, parentComponents = ['BlockItem', 'CardItem'] } = useActionBarContext();
return (

View File

@ -1,15 +1,16 @@
import { EditOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { observer, useField, useFieldSchema } from '@tachybase/schema';
import React, { useContext, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { InternalSubTable } from './InternalSubTable';
import { Field, observer, useField, useFieldSchema } from '@tachybase/schema';
import { EditOutlined } from '@ant-design/icons';
import { Button, Drawer } from 'antd';
import { SubFormProvider, useAssociationFieldContext } from './hooks';
import { ActionContext, ActionContextProvider } from '../action/context';
import { ReadPrettyInternalViewer } from './InternalViewer';
import { useTranslation } from 'react-i18next';
import { useCollectionManager } from '../../../data-source';
import { FlagProvider } from '../../../flag-provider';
import { ActionContext, ActionContextProvider } from '../action/context';
import { SubFormProvider, useAssociationFieldContext } from './hooks';
import { InternalSubTable } from './InternalSubTable';
import { ReadPrettyInternalViewer } from './InternalViewer';
export const InternaDrawerSubTable = observer(
(props) => {
@ -26,7 +27,7 @@ export const InternaDrawerSubTable = observer(
enableLink: true,
};
const field = useField();
const field = useField<Field>();
const fieldSchema = useFieldSchema();
const cm = useCollectionManager();
@ -39,11 +40,7 @@ export const InternaDrawerSubTable = observer(
setVisible(true);
}}
>
<div
className={css`
max-width: 95%;
`}
>
<div style={{ maxWidth: '95%' }}>
<ReadPrettyInternalViewer {...titleProps} />
</div>
<EditOutlined style={{ display: 'inline-flex', marginLeft: '5px' }} />

View File

@ -1,12 +1,39 @@
import { css, cx } from '@emotion/css';
import { FormLayout } from '@tachybase/components';
import { RecursionField, useField, useFieldSchema, observer } from '@tachybase/schema';
import React, { useEffect } from 'react';
import { FormLayout } from '@tachybase/components';
import { observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import cx from 'classnames';
import { ACLCollectionProvider, useACLActionParamsContext } from '../../../acl';
import { CollectionProvider_deprecated } from '../../../collection-manager';
import { useAssociationFieldContext, useInsertSchema } from './hooks';
import { ACLCollectionProvider, useACLActionParamsContext } from '../../../acl';
import schema from './schema';
const useStyles = createStyles(({ css }) => {
return {
body: css`
& .ant-formily-item-layout-vertical {
margin-bottom: 10px;
}
.ant-card-body {
padding: 15px 20px 5px;
}
.ant-divider-horizontal {
margin: 10px 0;
}
`,
showTitle: css`
.ant-card-body {
padding: 0px 20px 20px 0px;
}
> .ant-card-bordered {
border: none;
}
`,
};
});
export const InternalNester = observer(
() => {
const field = useField();
@ -15,6 +42,8 @@ export const InternalNester = observer(
const { options: collectionField } = useAssociationFieldContext();
const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true;
const { actionName } = useACLActionParamsContext();
const { styles } = useStyles();
useEffect(() => {
insertNester(schema.Nester);
}, []);
@ -23,29 +52,9 @@ export const InternalNester = observer(
<ACLCollectionProvider actionPath={`${collectionField.target}:${actionName}`}>
<FormLayout layout={'vertical'}>
<div
className={cx(
css`
& .ant-formily-item-layout-vertical {
margin-bottom: 10px;
}
.ant-card-body {
padding: 15px 20px 5px;
}
.ant-divider-horizontal {
margin: 10px 0;
}
`,
{
[css`
.ant-card-body {
padding: 0px 20px 20px 0px;
}
> .ant-card-bordered {
border: none;
}
`]: showTitle === false,
},
)}
className={cx(styles.body, {
[styles.showTitle]: showTitle === false,
})}
>
<RecursionField
onlyRenderProperties

View File

@ -1,18 +1,47 @@
import { EditOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { observer, useFieldSchema } from '@tachybase/schema';
import React, { useContext, useRef, useState } from 'react';
import { observer, useFieldSchema } from '@tachybase/schema';
import { EditOutlined } from '@ant-design/icons';
import { createStyles } from 'antd-style';
import { useTranslation } from 'react-i18next';
import { ActionContext, ActionContextProvider } from '../action/context';
import { useGetAriaLabelOfPopover } from '../action/hooks/useGetAriaLabelOfPopover';
import { useSetAriaLabelForPopover } from '../action/hooks/useSetAriaLabelForPopover';
import { StablePopover } from '../popover';
import { useAssociationFieldContext } from './hooks';
import { InternalNester } from './InternalNester';
import { ReadPrettyInternalViewer } from './InternalViewer';
import { useAssociationFieldContext } from './hooks';
const useStyles = createStyles(({ css }) => {
return {
pretty: css`
max-width: 95%;
`,
mask: css`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent;
z-index: 9999;
`,
container: css`
min-width: 600px;
max-width: 800px;
max-height: 440px;
overflow: auto;
.ant-card {
border: 0px;
}
`,
};
});
export const InternaPopoverNester = observer(
(props) => {
const { styles } = useStyles();
const { options } = useAssociationFieldContext();
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
@ -24,18 +53,7 @@ export const InternaPopoverNester = observer(
shouldMountElement: true,
};
const content = (
<div
ref={ref}
style={{ minWidth: '600px', maxWidth: '800px', maxHeight: '440px', overflow: 'auto' }}
className={css`
min-width: 600px;
max-height: 440px;
overflow: auto;
.ant-card {
border: 0px;
}
`}
>
<div ref={ref} className={styles.container}>
<InternalNester {...nesterProps} />
</div>
);
@ -62,11 +80,7 @@ export const InternaPopoverNester = observer(
title={t(options?.uiSchema?.rawTitle)}
>
<span style={{ cursor: 'pointer', display: 'flex' }}>
<div
className={css`
max-width: 95%;
`}
>
<div className={styles.pretty}>
<ReadPrettyInternalViewer {...props} />
</div>
<EditOutlined style={{ display: 'inline-flex', margin: '5px' }} />
@ -77,15 +91,7 @@ export const InternaPopoverNester = observer(
role="button"
aria-label={getAriaLabel('mask')}
onClick={() => setVisible(false)}
className={css`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent;
z-index: 9999;
`}
className={styles.mask}
/>
)}
</ActionContextProvider>

View File

@ -1,7 +1,9 @@
import { css } from '@emotion/css';
import { FormLayout } from '@tachybase/components';
import { RecursionField, SchemaOptionsContext, observer, useField, useFieldSchema } from '@tachybase/schema';
import React, { useEffect } from 'react';
import { FormLayout } from '@tachybase/components';
import { observer, RecursionField, SchemaOptionsContext, useField, useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import { ACLCollectionProvider, useACLActionParamsContext } from '../../../acl';
import { CollectionProvider_deprecated } from '../../../collection-manager';
import { FormItem, useSchemaOptionsContext } from '../../../schema-component';
@ -9,8 +11,28 @@ import Select from '../select/Select';
import { useAssociationFieldContext, useInsertSchema } from './hooks';
import schema from './schema';
const useStyles = createStyles(({ css }) => {
return {
container: css`
.ant-formily-item-bordered-none {
.ant-input-number-group-addon {
border: none !important;
background: none;
}
.ant-checkbox-wrapper {
margin-left: 8px;
}
.ant-table {
margin: 0px !important;
}
}
`,
};
});
export const InternalSubTable = observer(
() => {
const { styles } = useStyles();
const field: any = useField();
const fieldSchema = useFieldSchema();
const insert = useInsertSchema('SubTable');
@ -36,24 +58,7 @@ export const InternalSubTable = observer(
return (
<CollectionProvider_deprecated name={options.target}>
<ACLCollectionProvider actionPath={`${options.target}:${actionName || 'view'}`}>
<FormLayout
className={css`
.ant-formily-item-bordered-none {
.ant-input-number-group-addon {
border: none !important;
background: none;
}
.ant-checkbox-wrapper {
margin-left: 8px;
}
.ant-table {
margin: 0px !important;
}
}
`}
layout={'vertical'}
bordered={false}
>
<FormLayout className={styles.container} layout={'vertical'} bordered={false}>
<SchemaOptionsContext.Provider
value={{
scope: option.scope,

View File

@ -1,6 +1,6 @@
import { observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { toArr } from '@tachybase/schema';
import React, { Fragment, useRef, useState } from 'react';
import { observer, RecursionField, toArr, useField, useFieldSchema } from '@tachybase/schema';
import { useDesignable } from '../../';
import { BlockAssociationContext, WithoutTableFieldResource } from '../../../block-provider';
import { CollectionProvider_deprecated, useCollectionManager_deprecated } from '../../../collection-manager';
@ -27,7 +27,7 @@ const toValue = (value, placeholder) => {
export function isObject(value) {
return typeof value === 'object' && value !== null;
}
export const ReadPrettyInternalViewer: React.FC = observer(
export const ReadPrettyInternalViewer = observer(
(props: any) => {
const fieldSchema = useFieldSchema();
const recordCtx = useRecord();

View File

@ -1,31 +1,57 @@
import { CloseOutlined, PlusOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { ArrayField } from '@tachybase/schema';
import { spliceArrayState } from '@tachybase/schema';
import { RecursionField, observer, useFieldSchema } from '@tachybase/schema';
import { action } from '@tachybase/schema';
import { each } from '@tachybase/schema';
import { Button, Card, Divider, Tooltip } from 'antd';
import _ from 'lodash';
import React, { useCallback, useContext } from 'react';
import {
action,
ArrayField,
each,
observer,
RecursionField,
spliceArrayState,
useFieldSchema,
} from '@tachybase/schema';
import { CloseOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Card, Divider, Tooltip } from 'antd';
import { createStyles } from 'antd-style';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { FormActiveFieldsProvider } from '../../../block-provider';
import { useCollection } from '../../../data-source';
import {
useCollectionRecord,
useCollectionRecordData,
} from '../../../data-source/collection-record/CollectionRecordProvider';
import { isNewRecord, markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
import { FlagProvider } from '../../../flag-provider';
import { RecordIndexProvider, RecordProvider } from '../../../record-provider';
import { isPatternDisabled, isSystemField } from '../../../schema-settings';
import {
DefaultValueProvider,
IsAllowToSetDefaultValueParams,
interfacesOfUnsupportedDefaultValue,
IsAllowToSetDefaultValueParams,
} from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
import { AssociationFieldContext } from './context';
import { SubFormProvider, useAssociationFieldContext } from './hooks';
import { isNewRecord, markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
import { useCollection } from '../../../data-source';
const useStyles = createStyles(({ css }) => {
return {
input: css`
position: relative;
.ant-input {
width: 100%;
}
`,
card: css`
> .ant-card-body > .ant-divider:last-child {
display: none;
}
`,
button: css`
border: 1px solid #f0f0f0 !important;
box-shadow: none;
`,
};
});
export const Nester = (props) => {
const { options } = useContext(AssociationFieldContext);
@ -133,17 +159,10 @@ const ToManyNester = observer(
!isSystemField(collectionField, getInterface)
);
}, []);
const { styles } = useStyles();
return field.value.length > 0 ? (
<Card
bordered={true}
style={{ position: 'relative' }}
className={css`
> .ant-card-body > .ant-divider:last-child {
display: none;
}
`}
>
<Card bordered={true} style={{ position: 'relative' }} className={styles.card}>
{field.value.map((value, index) => {
let allowed = allowDissociate;
if (!allowDissociate) {
@ -221,10 +240,7 @@ const ToManyNester = observer(
<Tooltip key={'add'} title={t('Add new')}>
<Button
type={'default'}
className={css`
border: 1px solid #f0f0f0 !important;
box-shadow: none;
`}
className={styles.button}
block
icon={<PlusOutlined />}
onClick={() => {

View File

@ -1,13 +1,19 @@
import { css } from '@emotion/css';
import { ArrayField } from '@tachybase/schema';
import { exchangeArrayState } from '@tachybase/schema';
import { observer, RecursionField, useFieldSchema } from '@tachybase/schema';
import { action } from '@tachybase/schema';
import { isArr } from '@tachybase/schema';
import { Button } from 'antd';
import { unionBy, uniqBy } from 'lodash';
import React, { useContext, useMemo, useState } from 'react';
import {
action,
ArrayField,
exchangeArrayState,
isArr,
observer,
RecursionField,
useFieldSchema,
} from '@tachybase/schema';
import { Button } from 'antd';
import { createStyles } from 'antd-style';
import { unionBy, uniqBy } from 'lodash';
import { useTranslation } from 'react-i18next';
import {
FormProvider,
RecordPickerContext,
@ -20,6 +26,7 @@ import { FormActiveFieldsProvider } from '../../../block-provider/hooks/useFormA
import { TableSelectorParamsProvider } from '../../../block-provider/TableSelectorProvider';
import { CollectionProvider_deprecated } from '../../../collection-manager';
import { CollectionRecordProvider, useCollectionRecord } from '../../../data-source';
import { markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
import { FlagProvider } from '../../../flag-provider';
import { useCompile } from '../../hooks';
import { ActionContextProvider } from '../action';
@ -27,11 +34,69 @@ import { Table } from '../table-v2/Table';
import { useAssociationFieldContext, useFieldNames } from './hooks';
import { useTableSelectorProps } from './InternalPicker';
import { getLabelFormatValue, useLabelUiSchema } from './util';
import { markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
const useStyles = createStyles(({ css }) => {
return {
addNew: css`
display: block;
border-radius: 0px;
border-right: 1px solid rgba(0, 0, 0, 0.06);
`,
select: css`
display: block;
border-radius: 0px;
`,
table: css`
.ant-formily-item.ant-formily-item-feedback-layout-loose {
margin-bottom: 0px !important;
}
.ant-formily-editable {
vertical-align: sub;
}
.ant-table-footer {
display: flex;
}
`,
container: css`
.ant-table-footer {
padding: 0 !important;
}
.ant-formily-item-error-help {
display: none;
}
.ant-description-textarea {
line-height: 34px;
}
.ant-table-cell .ant-formily-item-error-help {
display: block;
position: absolute;
font-size: 12px;
top: 100%;
background: #fff;
width: 100%;
margin-top: -15px;
padding: 3px;
z-index: 1;
border-radius: 3px;
box-shadow: 0 0 10px #eee;
animation: none;
transform: translateY(0);
opacity: 1;
}
`,
input: css`
position: relative;
.ant-input {
width: 100%;
}
`,
};
});
export const SubTable: any = observer(
(props: any) => {
const { openSize } = props;
const { styles } = useStyles();
const { field, options: collectionField } = useAssociationFieldContext<ArrayField>();
const { t } = useTranslation();
const [visibleSelector, setVisibleSelector] = useState(false);
@ -108,50 +173,12 @@ export const SubTable: any = observer(
return filter;
};
return (
<div
className={css`
.ant-table-footer {
padding: 0 !important;
}
.ant-formily-item-error-help {
display: none;
}
.ant-description-textarea {
line-height: 34px;
}
.ant-table-cell .ant-formily-item-error-help {
display: block;
position: absolute;
font-size: 12px;
top: 100%;
background: #fff;
width: 100%;
margin-top: -15px;
padding: 3px;
z-index: 1;
border-radius: 3px;
box-shadow: 0 0 10px #eee;
animation: none;
transform: translateY(0);
opacity: 1;
}
`}
>
<div className={styles.container}>
<FlagProvider isInSubTable>
<CollectionRecordProvider record={null} parentRecord={recordV2}>
<FormActiveFieldsProvider name="nester">
<Table
className={css`
.ant-formily-item.ant-formily-item-feedback-layout-loose {
margin-bottom: 0px !important;
}
.ant-formily-editable {
vertical-align: sub;
}
.ant-table-footer {
display: flex;
}
`}
className={styles.table}
bordered
size={'small'}
field={field}
@ -167,11 +194,7 @@ export const SubTable: any = observer(
<Button
type={'text'}
block
className={css`
display: block;
border-radius: 0px;
border-right: 1px solid rgba(0, 0, 0, 0.06);
`}
className={styles.addNew}
onClick={() => {
field.value = field.value || [];
field.value.push(markRecordAsNew({}));
@ -184,10 +207,7 @@ export const SubTable: any = observer(
<Button
type={'text'}
block
className={css`
display: block;
border-radius: 0px;
`}
className={styles.select}
onClick={() => {
setVisibleSelector(true);
}}

View File

@ -1,86 +1,87 @@
import { css } from '@emotion/css';
import { useFieldSchema } from '@tachybase/schema';
import cls from 'classnames';
import React from 'react';
import { useCollection_deprecated } from '../../../collection-manager';
import { DndContext, SortableItem } from '../../common';
import { useDesigner } from '../../hooks';
import { useToken } from '../__builtins__';
import { AssociationFilterBlockDesigner } from './AssociationFilter.BlockDesigner';
import { AssociationFilterItem } from './AssociationFilter.Item';
import { AssociationFilterItemDesigner } from './AssociationFilter.Item.Designer';
import { AssociationFilterProvider } from './AssociationFilterProvider';
import { useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import cls from 'classnames';
import { useSchemaInitializerRender } from '../../../application';
import { Plugin } from '../../../application/Plugin';
import { useCollection_deprecated } from '../../../collection-manager';
import {
associationFilterFilterBlockInitializer,
filterCollapseItemInitializer,
filterCollapseItemInitializer_deprecated,
} from '../../../modules/blocks/filter-blocks/collapse/filterCollapseItemInitializer';
import { DndContext, SortableItem } from '../../common';
import { useDesigner } from '../../hooks';
import { AssociationFilterBlockDesigner } from './AssociationFilter.BlockDesigner';
import { associationFilterInitializer } from './AssociationFilter.Initializer';
import { AssociationFilterItem } from './AssociationFilter.Item';
import { AssociationFilterItemDesigner } from './AssociationFilter.Item.Designer';
import { AssociationFilterProvider } from './AssociationFilterProvider';
const useStyles = createStyles(({ css, token }) => {
return {
container: css`
height: 100%;
overflow-y: auto;
position: relative;
border-radius: ${token.borderRadiusLG}px;
&:hover {
> .general-schema-designer {
display: block;
}
}
&.nb-form-item:hover {
> .general-schema-designer {
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -5px !important;
bottom: -5px !important;
left: -5px !important;
right: -5px !important;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
border: 2px solid var(--colorBorderSettingsHover);
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
};
});
export const AssociationFilter = (props) => {
const { token } = useToken();
const Designer = useDesigner();
const filedSchema = useFieldSchema();
const { styles } = useStyles();
const { render } = useSchemaInitializerRender(filedSchema['x-initializer'], filedSchema['x-initializer-props']);
return (
<DndContext>
<SortableItem
className={cls(
'nb-block-item',
props.className,
css`
height: 100%;
overflow-y: auto;
position: relative;
border-radius: ${token.borderRadiusLG}px;
&:hover {
> .general-schema-designer {
display: block;
}
}
&.nb-form-item:hover {
> .general-schema-designer {
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -5px !important;
bottom: -5px !important;
left: -5px !important;
right: -5px !important;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
border: 2px solid var(--colorBorderSettingsHover);
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
)}
>
<SortableItem className={cls('nb-block-item', props.className, styles.container)}>
<Designer />
{props.children}
{render()}

View File

@ -1,15 +1,67 @@
import { css } from '@emotion/css';
import cls from 'classnames';
import React from 'react';
import { useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import cls from 'classnames';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { SortableItem } from '../../common';
import { useDesigner, useProps } from '../../hooks';
import { useGetAriaLabelOfBlockItem } from './hooks/useGetAriaLabelOfBlockItem';
import { useFieldSchema } from '@tachybase/schema';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
const useStyles = createStyles(({ css }) => {
return {
container: css`
position: relative;
&:hover {
> .general-schema-designer {
display: block;
}
}
&.nb-form-item:hover {
> .general-schema-designer {
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -5px !important;
bottom: -5px !important;
left: -5px !important;
right: -5px !important;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
border: 2px solid var(--colorBorderSettingsHover);
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
};
});
export const BlockItem: React.FC<any> = withDynamicSchemaProps((props) => {
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { className, children } = useProps(props);
const { styles } = useStyles();
const Designer = useDesigner();
const fieldSchema = useFieldSchema();
@ -19,54 +71,7 @@ export const BlockItem: React.FC<any> = withDynamicSchemaProps((props) => {
<SortableItem
role="button"
aria-label={getAriaLabel()}
className={cls(
'nb-block-item',
className,
css`
position: relative;
&:hover {
> .general-schema-designer {
display: block;
}
}
&.nb-form-item:hover {
> .general-schema-designer {
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -5px !important;
bottom: -5px !important;
left: -5px !important;
right: -5px !important;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
border: 2px solid var(--colorBorderSettingsHover);
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
)}
className={cls('nb-block-item', className, styles.container)}
>
<Designer {...fieldSchema['x-toolbar-props']} />
{children}

View File

@ -1,9 +1,21 @@
import { css } from '@emotion/css';
import React from 'react';
import { usePrefixCls } from '@tachybase/components';
import { connect, mapProps, mapReadPretty } from '@tachybase/schema';
import { ColorPicker as AntdColorPicker } from 'antd';
import { createStyles } from 'antd-style';
import cls from 'classnames';
import React from 'react';
const useStyles = createStyles(({ css }) => {
return {
color: css`
display: inline-flex;
.ant-color-picker-trigger-disabled {
cursor: default;
}
`,
};
});
export const ColorPicker = connect(
(props) => {
@ -48,20 +60,12 @@ export const ColorPicker = connect(
}),
mapReadPretty((props) => {
const prefixCls = usePrefixCls('description-color-picker', props);
const { styles } = useStyles();
return (
<div
role="button"
aria-label="color-picker-read-pretty"
className={cls(
prefixCls,
css`
display: inline-flex;
.ant-color-picker-trigger-disabled {
cursor: default;
}
`,
props.className,
)}
className={cls(prefixCls, styles.color, props.className)}
>
<AntdColorPicker disabled value={props.value} size="small" {...props} />
</div>

View File

@ -1,36 +1,44 @@
import { css } from '@emotion/css';
import React, { useMemo } from 'react';
import { connect, mapReadPretty } from '@tachybase/schema';
import { error } from '@tachybase/utils/client';
import { createStyles } from 'antd-style';
import cronstrue from 'cronstrue';
import React, { useMemo } from 'react';
import { CronProps, Cron as ReactCron } from 'react-js-cron';
import 'react-js-cron/dist/styles.css';
import { useAPIClient } from '../../../api-client';
import 'react-js-cron/dist/styles.css';
const useStyles = createStyles(({ css }) => {
return {
input: css`
.react-js-cron {
padding: 0.5em 0.5em 0 0.5em;
border: 1px dashed #ccc;
.react-js-cron-field {
flex-shrink: 0;
margin-bottom: 0.5em;
> span {
flex-shrink: 0;
margin: 0 0.5em 0 0;
}
> .react-js-cron-select {
margin: 0 0.5em 0 0;
}
}
}
`,
};
});
const Input = (props: Omit<CronProps, 'setValue'> & { onChange: (value: string) => void }) => {
const { onChange, ...rest } = props;
const { styles } = useStyles();
return (
<fieldset
className={css`
.react-js-cron {
padding: 0.5em 0.5em 0 0.5em;
border: 1px dashed #ccc;
.react-js-cron-field {
flex-shrink: 0;
margin-bottom: 0.5em;
> span {
flex-shrink: 0;
margin: 0 0.5em 0 0;
}
> .react-js-cron-select {
margin: 0 0.5em 0 0;
}
}
}
`}
>
<fieldset className={styles.input}>
<ReactCron setValue={onChange} locale={window['cronLocale']} {...rest} />
</fieldset>
);

View File

@ -1,54 +1,57 @@
import { css } from '@emotion/css';
import React, { createRef, forwardRef } from 'react';
import { useFieldSchema } from '@tachybase/schema';
import { Button } from 'antd';
import React, { forwardRef, createRef } from 'react';
import { createStyles } from 'antd-style';
import { composeRef } from 'rc-util/lib/ref';
import { useCompile } from '../../hooks';
import { useTableBlockContext, useTableSelectorContext } from '../../../block-provider';
import { Icon } from '../../../icon';
import { useCompile } from '../../hooks';
const actionDesignerCss = css`
position: relative;
&:hover {
.general-schema-designer {
display: block;
}
}
.general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
const useStyles = createStyles(({ css }) => {
return {
designer: css`
position: relative;
&:hover {
.general-schema-designer {
display: block;
}
}
}
}
`;
.general-schema-designer {
position: absolute;
z-index: 999;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
};
});
const InternalExpandAction = (props, ref) => {
const schema = useFieldSchema();
const { styles } = useStyles();
const ctxSelector = useTableSelectorContext();
const ctxBlock = useTableBlockContext();
const isTableSelector = schema.parent?.parent?.['x-decorator'] === 'TableSelectorProvider';
@ -59,7 +62,7 @@ const InternalExpandAction = (props, ref) => {
const buttonRef = composeRef(ref, internalRef);
return (
//@ts-ignore
<div className={actionDesignerCss} ref={buttonRef as React.Ref<HTMLButtonElement>}>
<div className={styles.designer} ref={buttonRef as React.Ref<HTMLButtonElement>}>
{ctx?.params['tree'] && (
<Button
onClick={() => {

View File

@ -1,22 +1,38 @@
import { css } from '@emotion/css';
import { createForm, Field, Form } from '@tachybase/schema';
import { observer, useField, useFieldSchema, useForm } from '@tachybase/schema';
import { flatten, unflatten } from '@tachybase/utils/client';
import { Button, Space } from 'antd';
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
import { createForm, Field, Form, observer, useField, useFieldSchema, useForm } from '@tachybase/schema';
import { flatten, unflatten } from '@tachybase/utils/client';
import { Button, Space } from 'antd';
import { createStyles } from 'antd-style';
import { useTranslation } from 'react-i18next';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { FormProvider, SchemaComponent } from '../../core';
import { useDesignable } from '../../hooks';
import { useProps } from '../../hooks/useProps';
import { Action } from '../action';
import { StablePopover } from '../popover';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export const FilterActionContext = createContext<any>(null);
FilterActionContext.displayName = 'FilterActionContext';
const useStyles = createStyles(({ css }) => {
return {
container: css`
display: flex;
justify-content: flex-end;
width: 100%;
`,
button: css`
border-color: var(--colorSettings);
color: var(--colorSettings);
`,
};
});
export const FilterAction = withDynamicSchemaProps(
observer((props: any) => {
const { styles } = useStyles();
const { t } = useTranslation();
const field = useField<Field>();
const [visible, setVisible] = useState(false);
@ -55,13 +71,7 @@ export const FilterAction = withDynamicSchemaProps(
},
}}
/>
<div
className={css`
display: flex;
justify-content: flex-end;
width: 100%;
`}
>
<div className={styles.container}>
<Space>
<SaveConditions />
<Button
@ -104,16 +114,14 @@ const SaveConditions = () => {
const { fieldSchema, field, designable, dn } = useContext(FilterActionContext);
const form = useForm();
const { t } = useTranslation();
const { styles } = useStyles();
if (!designable) {
return null;
}
return (
<Button
type={'dashed'}
className={css`
border-color: var(--colorSettings);
color: var(--colorSettings);
`}
className={styles.button}
onClick={() => {
const defaultValue = { ...form.values.filter };
fieldSchema.default = defaultValue;

View File

@ -1,12 +1,13 @@
import { CloseCircleOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { observer } from '@tachybase/schema';
import { Cascader, Select, Space } from 'antd';
import React, { useCallback, useContext, useMemo } from 'react';
import { observer } from '@tachybase/schema';
import { CloseCircleOutlined } from '@ant-design/icons';
import { Cascader, Select, Space } from 'antd';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../..';
import { DynamicComponent } from './DynamicComponent';
import { RemoveConditionContext } from './context';
import { DynamicComponent } from './DynamicComponent';
import { useValues } from './useValues';
export const FilterItem = observer(
@ -55,12 +56,8 @@ export const FilterItem = observer(
<div style={style} className="nc-filter-item">
<Space>
<Cascader
// @ts-ignore
role="button"
data-testid="select-filter-field"
className={css`
width: 160px;
`}
style={{ width: '160px' }}
fieldNames={fieldNames}
changeOnSelect={false}
value={dataIndex}
@ -69,12 +66,8 @@ export const FilterItem = observer(
placeholder={t('Select field')}
/>
<Select
// @ts-ignore
role="button"
data-testid="select-filter-operator"
className={css`
min-width: 110px;
`}
style={{ minWidth: '110px' }}
popupMatchSelectWidth={false}
value={operator?.value}
options={compile(operators)}

View File

@ -1,8 +1,9 @@
import { css } from '@emotion/css';
import { useFieldSchema, useForm } from '@tachybase/schema';
import { Button } from 'antd';
import React from 'react';
import { useFieldSchema, useForm } from '@tachybase/schema';
import { Button } from 'antd';
import { useTranslation } from 'react-i18next';
import { useDesignable } from '../../hooks';
export const SaveDefaultValue = (props) => {
@ -15,10 +16,10 @@ export const SaveDefaultValue = (props) => {
}
return (
<Button
className={css`
border-color: var(--colorSettings);
color: var(--colorSettings);
`}
style={{
borderColor: 'var(--colorSettings)',
color: 'var(--colorSettings)',
}}
type={'dashed'}
onClick={() => {
const filterSchema = fieldSchema?.parent?.parent?.parent?.properties?.filter;
@ -34,7 +35,6 @@ export const SaveDefaultValue = (props) => {
});
dn.refresh();
filterSchema.default = defaultValue;
console.log('filterSchema', defaultValue);
}}
>
{t('Save conditions')}

View File

@ -1,25 +1,47 @@
import { css, cx } from '@emotion/css';
import { FormItem as Item } from '@tachybase/components';
import { Field } from '@tachybase/schema';
import { observer, useField, useFieldSchema } from '@tachybase/schema';
import React, { useEffect, useMemo } from 'react';
import { FormItem as Item } from '@tachybase/components';
import { Field, observer, useField, useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import cx from 'classnames';
import { ACLCollectionFieldProvider } from '../../../acl/ACLProvider';
import { useApp } from '../../../application';
import { useFormActiveFields } from '../../../block-provider/hooks/useFormActiveFields';
import { Collection_deprecated } from '../../../collection-manager';
import { CollectionFieldProvider, useContextConfigSetting } from '../../../data-source';
import { GeneralSchemaDesigner } from '../../../schema-settings';
import { useVariables } from '../../../variables';
import useContextVariable from '../../../variables/hooks/useContextVariable';
import { BlockItem } from '../block-item';
import { HTMLEncode } from '../input/shared';
import { FilterFormDesigner } from './FormItem.FilterFormDesigner';
import { useEnsureOperatorsValid } from './SchemaSettingOptions';
import useLazyLoadDisplayAssociationFieldsOfForm from './hooks/useLazyLoadDisplayAssociationFieldsOfForm';
import useParseDefaultValue from './hooks/useParseDefaultValue';
import { CollectionFieldProvider, useContextConfigSetting } from '../../../data-source';
const useStyles = createStyles(({ css }) => {
return {
space: css`
& .ant-space {
flex-wrap: wrap;
}
`,
hide: css`
> .ant-formily-item-label {
display: none;
}
`,
row: css`
display: flex;
flex-direction: row;
align-items: baseline;
`,
};
});
export const FormItem: any = observer(
(props: any) => {
const { styles } = useStyles();
const { layoutDirection: selfLayoutDirection } = props;
const field = useField<Field>();
const schema = useFieldSchema();
@ -57,24 +79,12 @@ export const FormItem: any = observer(
}, [field.description]);
const className = useMemo(() => {
return cx(
css`
& .ant-space {
flex-wrap: wrap;
}
`,
styles.space,
{
[css`
> .ant-formily-item-label {
display: none;
}
`]: showTitle === false,
[styles.hide]: showTitle === false,
},
{
[css`
display: flex;
flex-direction: row;
align-items: baseline;
`]: finishLayoutDirection === 'row',
[styles.row]: finishLayoutDirection === 'row',
},
);
}, [showTitle]);

View File

@ -1,14 +1,29 @@
import { css } from '@emotion/css';
import { FormLayout } from '@tachybase/components';
import { createForm, Field, Form as FormilyForm, onFieldInit, onFormInputChange } from '@tachybase/schema';
import { FieldContext, FormContext, observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { reaction } from '@tachybase/schema';
import { uid } from '@tachybase/schema';
import { getValuesByPath } from '@tachybase/utils/client';
import { ConfigProvider, Spin } from 'antd';
import React, { useEffect, useMemo } from 'react';
import { FormLayout } from '@tachybase/components';
import {
createForm,
Field,
FieldContext,
FormContext,
Form as FormilyForm,
observer,
onFieldInit,
onFormInputChange,
reaction,
RecursionField,
uid,
useField,
useFieldSchema,
} from '@tachybase/schema';
import { getValuesByPath } from '@tachybase/utils/client';
import { ConfigProvider, Spin } from 'antd';
import { createStyles } from 'antd-style';
import { useActionContext } from '..';
import { useAttach, useComponent } from '../..';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider';
import { ActionType } from '../../../schema-settings/LinkageRules/type';
import { useLocalVariables, useVariables } from '../../../variables';
import { VariableOption, VariablesContextType } from '../../../variables/types';
@ -18,13 +33,21 @@ import { isVariable, REGEX_OF_VARIABLE } from '../../../variables/utils/isVariab
import { getInnermostKeyAndValue, getTargetField } from '../../common/utils/uitls';
import { useProps } from '../../hooks/useProps';
import { collectFieldStateOfLinkageRules, getTempFieldState } from './utils';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider';
export interface FormProps {
[key: string]: any;
}
const useStyles = createStyles(({ css }) => {
return {
container: css`
.ant-formily-item-feedback-layout-loose {
margin-bottom: 12px;
}
`,
};
});
const FormComponent: React.FC<FormProps> = (props) => {
const { form, children, ...others } = props;
const field = useField();
@ -203,6 +226,7 @@ export const Form: React.FC<FormProps> & {
} = withDynamicSchemaProps(
observer((props) => {
const field = useField<Field>();
const { styles } = useStyles();
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { form, disabled, ...others } = useProps(props);
@ -210,14 +234,7 @@ export const Form: React.FC<FormProps> & {
const formDisabled = disabled || field.disabled;
return (
<ConfigProvider componentDisabled={formDisabled}>
<form
onSubmit={(e) => e.preventDefault()}
className={css`
.ant-formily-item-feedback-layout-loose {
margin-bottom: 12px;
}
`}
>
<form onSubmit={(e) => e.preventDefault()} className={styles.container}>
<Spin spinning={field.loading || false}>
{form ? (
<WithForm form={form} {...others} disabled={formDisabled} />

View File

@ -1,23 +1,39 @@
import { css } from '@emotion/css';
import { useCollection } from '@tachybase/client';
import { ObjectField } from '@tachybase/schema';
import { useField, useFieldSchema } from '@tachybase/schema';
import { Card } from 'antd';
import React from 'react';
import { useCollection } from '@tachybase/client';
import { ObjectField, useField, useFieldSchema } from '@tachybase/schema';
import { Card } from 'antd';
import { createStyles } from 'antd-style';
import { useNavigate } from 'react-router-dom';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider';
import { RecordProvider } from '../../../record-provider';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { useNavigate } from 'react-router-dom';
import { useGridCardDetailUrl } from './hooks';
const itemCss = css`
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
justify-content: space-between;
gap: 8px;
`;
const useStyles = createStyles(({ css }) => {
return {
itemCss: css`
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
justify-content: space-between;
gap: 8px;
`,
card: css`
/* background-color: re/d; */
height: 100%;
> .ant-card-body {
padding: 24px 24px 0px;
height: 100%;
}
.nb-action-bar {
padding: 5px 0;
}
`,
};
});
export const GridCardItem = withDynamicSchemaProps((props) => {
const collection = useCollection();
@ -25,6 +41,7 @@ export const GridCardItem = withDynamicSchemaProps((props) => {
const fieldSchema = useFieldSchema();
const parentRecordData = useCollectionParentRecordData();
const navigate = useNavigate();
const { styles } = useStyles();
const detailUrl = useGridCardDetailUrl({ collection, field, fieldSchema });
// XXX: 实现的有些丑陋, 需要想想有没有更好的办法
const handleClick = () => {
@ -37,23 +54,8 @@ export const GridCardItem = withDynamicSchemaProps((props) => {
}
};
return (
<Card
role="button"
aria-label="grid-card-item"
className={css`
/* background-color: re/d; */
height: 100%;
> .ant-card-body {
padding: 24px 24px 0px;
height: 100%;
}
.nb-action-bar {
padding: 5px 0;
}
`}
onClick={handleClick}
>
<div className={itemCss}>
<Card role="button" aria-label="grid-card-item" className={styles.card} onClick={handleClick}>
<div className={styles.itemCss}>
{/* @ts-ignore */}
<RecordProvider record={field.value} parent={parentRecordData}>
{props.children}

View File

@ -1,9 +1,13 @@
import { css, cx } from '@emotion/css';
import { ArrayField } from '@tachybase/schema';
import { RecursionField, Schema, useField, useFieldSchema } from '@tachybase/schema';
import { List as AntdList, Col, PaginationProps } from 'antd';
import React, { useCallback, useEffect, useState } from 'react';
import { ArrayField, RecursionField, Schema, useField, useFieldSchema } from '@tachybase/schema';
import { List as AntdList, Col, PaginationProps } from 'antd';
import { createStyles } from 'antd-style';
import cx from 'classnames';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { SortableItem } from '../../common';
import { InfiniteScroll } from '../../common/infinite-scroll/infinite-scroll';
import { SchemaComponentOptions } from '../../core';
import { useDesigner, useProps } from '../../hooks';
import { GridCardBlockProvider, useGridCardBlockContext, useGridCardItemProps } from './GridCard.Decorator';
@ -11,8 +15,6 @@ import { GridCardDesigner } from './GridCard.Designer';
import { GridCardItem } from './GridCard.Item';
import { useGridCardActionBarProps } from './hooks';
import { defaultColumnCount, pageSizeOptions } from './options';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { InfiniteScroll } from '../../common/infinite-scroll/infinite-scroll';
const rowGutter = {
md: 12,
@ -20,46 +22,51 @@ const rowGutter = {
xs: 5,
};
const designerCss = css`
width: 100%;
&:hover {
> .general-schema-designer {
display: block;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
const useStyles = createStyles(({ css }) => {
return {
designer: css`
width: 100%;
&:hover {
> .general-schema-designer {
display: block;
}
}
}
}
`;
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
};
});
const InternalGridCard = (props) => {
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { columnCount: columnCountProp, pagination } = useProps(props);
const { styles } = useStyles();
const { service, columnCount: _columnCount = defaultColumnCount, needInfiniteScroll } = useGridCardBlockContext();
const columnCount = columnCountProp || _columnCount;
const { run, params } = service;
@ -132,7 +139,7 @@ const InternalGridCard = (props) => {
useGridCardActionBarProps,
}}
>
<SortableItem className={cx('nb-card-list', designerCss)}>
<SortableItem className={cx('nb-card-list', styles.designer)}>
<AntdList
pagination={
!meta || meta.count <= meta.pageSize || needInfiniteScroll

View File

@ -1,7 +1,8 @@
import React, { useMemo } from 'react';
import { Icon, icons } from '../../../icon';
import { Tooltip } from 'antd';
import { css } from '@emotion/css';
import { Icon, icons } from '../../../icon';
export const IconFilterList = (props) => {
const { currentKey, filterKey, onChange, changePop } = props;
@ -24,10 +25,12 @@ export const IconFilterList = (props) => {
{[...iconKeysByFilter.keys()].map((key) => (
<span
key={key}
className={css`
background-color: ${currentKey == key ? '#9bb0d393' : ''};
`}
style={{ fontSize: 18, marginRight: 10, cursor: 'pointer' }}
style={{
fontSize: 18,
marginRight: 10,
cursor: 'pointer',
backgroundColor: currentKey == key ? '#9bb0d393' : '',
}}
>
<Tooltip
destroyTooltipOnHide

View File

@ -1,9 +1,8 @@
import { Field } from '@tachybase/schema';
import { useField } from '@tachybase/schema';
import React, { Ref, useEffect, useState } from 'react';
import { Field, useField } from '@tachybase/schema';
import { Input } from 'antd';
import { TextAreaProps } from 'antd/es/input';
import React, { useState, useEffect, Ref } from 'react';
import { cx, css } from '@emotion/css';
export type JSONTextAreaProps = TextAreaProps & { value?: string; space?: number };
@ -25,13 +24,11 @@ export const Json = React.forwardRef<typeof Input.TextArea, JSONTextAreaProps>(
return (
<Input.TextArea
{...props}
className={cx(
css`
font-size: 80%;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
`,
props.className,
)}
style={{
fontSize: '80%',
fontFamily: "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace",
...props.style,
}}
ref={ref}
value={text}
onChange={(ev) => {

View File

@ -1,12 +1,13 @@
import { css, cx } from '@emotion/css';
import React from 'react';
import { usePrefixCls } from '@tachybase/components';
import { Typography } from 'antd';
import { InputProps, TextAreaProps } from 'antd/es/input';
import cls from 'classnames';
import React from 'react';
import { useCompile } from '../..';
import { EllipsisWithTooltip } from './EllipsisWithTooltip';
import { HTMLEncode } from './shared';
import { usePrefixCls } from '@tachybase/components';
export const ReadPretty = () => null;
@ -121,26 +122,6 @@ const _URL = (props: InputProps) => {
);
};
const _JSON = (props: TextAreaProps & { space: number }) => {
const prefixCls = usePrefixCls('json', props);
return (
<pre
className={cx(
prefixCls,
props.className,
css`
margin-bottom: 0;
line-height: 1.5;
font-size: 90%;
`,
)}
style={props.style}
>
{props.value != null ? JSON.stringify(props.value, null, props.space ?? 2) : ''}
</pre>
);
};
ReadPretty.Input = _Input;
ReadPretty.TextArea = _TextArea;
ReadPretty.URL = _URL;

View File

@ -1,17 +1,32 @@
import { css, cx } from '@emotion/css';
import { FormLayout } from '@tachybase/components';
import { createForm } from '@tachybase/schema';
import { FormContext, useField } from '@tachybase/schema';
import _ from 'lodash';
import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider';
import { FormLayout } from '@tachybase/components';
import { createForm, FormContext, useField } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import _ from 'lodash';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider';
const useStyles = createStyles(({ css }) => {
return {
container: css`
.ant-description-input {
line-height: 34px;
}
.ant-formily-item-feedback-layout-loose {
margin-bottom: 12px;
}
`,
};
});
export const ListBlockContext = createContext<any>({});
ListBlockContext.displayName = 'ListBlockContext';
const InternalListBlockProvider = (props) => {
const { resource, service } = useBlockRequestContext();
const { styles } = useStyles();
const field = useField();
const form = useMemo(() => {
@ -35,18 +50,7 @@ const InternalListBlockProvider = (props) => {
>
<FormContext.Provider value={form}>
<FormLayout layout={'vertical'}>
<div
className={cx(css`
.ant-description-input {
line-height: 34px;
}
.ant-formily-item-feedback-layout-loose {
margin-bottom: 12px;
}
`)}
>
{props.children}
</div>
<div className={styles.container}>{props.children}</div>
</FormLayout>
</FormContext.Provider>
</ListBlockContext.Provider>

View File

@ -1,30 +1,32 @@
import { ObjectField } from '@tachybase/schema';
import { useField } from '@tachybase/schema';
import classnames from 'classnames';
import React from 'react';
import { css, cx } from '@emotion/css';
import { useDesignable } from '../../hooks';
import { ObjectField, useField } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import classnames from 'classnames';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider';
import { RecordProvider } from '../../../record-provider';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { useDesignable } from '../../hooks';
const useStyles = createStyles(({ css }, props: { designable: boolean }) => {
return {
item: css`
.nb-action-bar {
gap: 20px !important;
margin-top: ${props.designable ? '20px' : '0px'};
}
`,
};
});
export const ListItem = withDynamicSchemaProps((props) => {
const field = useField<ObjectField>();
const { designable } = useDesignable();
const { styles } = useStyles({ designable });
const parentRecordData = useCollectionParentRecordData();
return (
<div
className={cx(classnames(props.className), [
'itemCss',
css`
.nb-action-bar {
gap: 20px !important;
margin-top: ${designable ? '20px' : '0px'};
}
`,
])}
>
<div className={classnames(props.className, ['itemCss', styles.item])}>
<RecordProvider record={field.value} parent={parentRecordData}>
{props.children}
</RecordProvider>

View File

@ -1,17 +1,18 @@
import { cx } from '@emotion/css';
import { ArrayField } from '@tachybase/schema';
import { RecursionField, Schema, useField, useFieldSchema } from '@tachybase/schema';
import { List as AntdList, PaginationProps } from 'antd';
import React, { useCallback, useState } from 'react';
import { ArrayField, RecursionField, Schema, useField, useFieldSchema } from '@tachybase/schema';
import { List as AntdList, PaginationProps } from 'antd';
import cx from 'classnames';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { SortableItem } from '../../common';
import { SchemaComponentOptions } from '../../core';
import { useDesigner } from '../../hooks';
import { useListActionBarProps } from './hooks';
import { ListBlockProvider, useListBlockContext, useListItemProps } from './List.Decorator';
import { ListDesigner } from './List.Designer';
import { ListItem } from './List.Item';
import useStyles from './List.style';
import { useListActionBarProps } from './hooks';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
const InternalList = (props) => {
const { service } = useListBlockContext();

View File

@ -1,8 +1,11 @@
import { TreeSelect } from '@tachybase/components';
import { Field, onFieldChange } from '@tachybase/schema';
import { ISchema, Schema, useField, useFieldSchema } from '@tachybase/schema';
import React from 'react';
import { TreeSelect } from '@tachybase/components';
import { Field, ISchema, onFieldChange, Schema, useField, useFieldSchema } from '@tachybase/schema';
import { message } from 'antd';
import { saveAs } from 'file-saver';
import { useTranslation } from 'react-i18next';
import { findByUid } from '.';
import { createDesignable } from '../..';
import {
@ -15,8 +18,6 @@ import {
useAPIClient,
useDesignable,
} from '../../../';
import { message } from 'antd';
import { saveAs } from 'file-saver';
const toItems = (properties = {}) => {
const items = [];

View File

@ -0,0 +1,160 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css }) => {
return {
subMenuDesignerCss: css`
position: relative;
display: inline-block;
margin-left: -24px;
margin-right: -34px;
padding: 0 34px 0 24px;
width: calc(100% + 58px);
height: 100%;
overflow: hidden;
text-overflow: ellipsis;
&: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;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
designerCss: css`
position: relative;
display: flex;
justify-content: center;
align-items: center;
margin-left: -20px;
margin-right: -20px;
padding: 0 20px;
width: calc(100% + 40px);
height: 100%;
&: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;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
headerMenuClass: css`
.ant-menu-item:hover {
> .ant-menu-title-content > div {
.general-schema-designer {
display: block;
}
}
}
`,
sideMenuClass: css`
height: 100%;
overflow-y: auto;
overflow-x: hidden;
.ant-menu-item {
> .ant-menu-title-content {
height: 100%;
margin-left: -24px;
margin-right: -16px;
padding: 0 16px 0 24px;
> div {
> .general-schema-designer {
right: 6px !important;
}
}
}
}
.ant-menu-submenu-title {
.ant-menu-title-content {
height: 100%;
margin-left: -24px;
margin-right: -34px;
padding: 0 34px 0 24px;
> div {
> .general-schema-designer {
right: 6px !important;
}
> span.anticon {
margin-right: 10px;
}
}
}
}
`,
menuItemClass: css`
:active {
background: inherit;
}
`,
};
});

View File

@ -1,182 +1,29 @@
import { css } from '@emotion/css';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import {
FieldContext,
observer,
RecursionField,
SchemaContext,
SchemaExpressionScopeContext,
uid,
useField,
useFieldSchema,
} from '@tachybase/schema';
import { uid } from '@tachybase/schema';
import { error } from '@tachybase/utils/client';
import { Menu as AntdMenu, MenuProps } from 'antd';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { createDesignable, DndContext, SortableItem, useDesignable, useDesigner } from '../..';
import { Icon, useAPIClient, useSchemaInitializerRender } from '../../../';
import { useCollectMenuItems, useMenuItem } from '../../../hooks/useMenuItem';
import { useProps } from '../../hooks/useProps';
import { useMenuTranslation } from './locale';
import { MenuDesigner } from './Menu.Designer';
import { useStyles } from './Menu.styles';
import { findKeysByUid, findMenuItem } from './util';
const subMenuDesignerCss = css`
position: relative;
display: inline-block;
margin-left: -24px;
margin-right: -34px;
padding: 0 34px 0 24px;
width: calc(100% + 58px);
height: 100%;
overflow: hidden;
text-overflow: ellipsis;
&: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;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`;
const designerCss = css`
position: relative;
display: flex;
justify-content: center;
align-items: center;
margin-left: -20px;
margin-right: -20px;
padding: 0 20px;
width: calc(100% + 40px);
height: 100%;
&: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;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`;
const headerMenuClass = css`
.ant-menu-item:hover {
> .ant-menu-title-content > div {
.general-schema-designer {
display: block;
}
}
}
`;
const sideMenuClass = css`
height: 100%;
overflow-y: auto;
overflow-x: hidden;
.ant-menu-item {
> .ant-menu-title-content {
height: 100%;
margin-left: -24px;
margin-right: -16px;
padding: 0 16px 0 24px;
> div {
> .general-schema-designer {
right: 6px !important;
}
}
}
}
.ant-menu-submenu-title {
.ant-menu-title-content {
height: 100%;
margin-left: -24px;
margin-right: -34px;
padding: 0 34px 0 24px;
> div {
> .general-schema-designer {
right: 6px !important;
}
> span.anticon {
margin-right: 10px;
}
}
}
}
`;
const menuItemClass = css`
:active {
background: inherit;
}
`;
type ComposedMenu = React.FC<any> & {
Item?: React.FC<any>;
URL?: React.FC<any>;
@ -199,6 +46,7 @@ const HeaderMenu = ({
children,
}) => {
const { Component, getMenuItems } = useMenuItem();
const { styles } = useStyles();
const items = useMemo(() => {
const designerBtn = {
key: 'x-designer-button',
@ -225,7 +73,7 @@ const HeaderMenu = ({
<Component />
<AntdMenu
{...others}
className={headerMenuClass}
className={styles.headerMenuClass}
onSelect={(info: any) => {
const s = schema.properties?.[info.key];
@ -286,6 +134,7 @@ const SideMenu = ({
designable,
}) => {
const { Component, getMenuItems } = useMenuItem();
const { styles } = useStyles();
// fix https://nocobase.height.app/T-3331/description
// 使用 ref 用来防止闭包问题
@ -340,7 +189,7 @@ const SideMenu = ({
onSelect={(info) => {
onSelect?.(info);
}}
className={sideMenuClass}
className={styles.sideMenuClass}
items={items as MenuProps['items']}
/>
</MenuModeContext.Provider>,
@ -488,12 +337,13 @@ Menu.Item = observer(
const { pushMenuItem } = useCollectMenuItems();
const { icon, children, ...others } = props;
const schema = useFieldSchema();
const { styles } = useStyles();
const field = useField();
const Designer = useContext(MenuItemDesignerContext);
const item = useMemo(() => {
return {
...others,
className: menuItemClass,
className: styles.menuItemClass,
key: schema.name,
eventKey: schema.name,
schema,
@ -503,7 +353,7 @@ Menu.Item = observer(
<SortableItem
role="button"
aria-label={t(field.title)}
className={designerCss}
className={styles.designerCss}
removeParentsIfNoChildren={false}
>
<Icon type={icon} />
@ -545,6 +395,7 @@ Menu.URL = observer(
const schema = useFieldSchema();
const field = useField();
const Designer = useContext(MenuItemDesignerContext);
const { styles } = useStyles();
if (!pushMenuItem) {
error('Menu.URL must be wrapped by GetMenuItemsContext.Provider');
@ -554,7 +405,7 @@ Menu.URL = observer(
const item = useMemo(() => {
return {
...others,
className: menuItemClass,
className: styles.menuItemClass,
key: schema.name,
eventKey: schema.name,
schema,
@ -564,7 +415,11 @@ Menu.URL = observer(
label: (
<SchemaContext.Provider value={schema}>
<FieldContext.Provider value={field}>
<SortableItem className={designerCss} removeParentsIfNoChildren={false} aria-label={t(field.title)}>
<SortableItem
className={styles.designerCss}
removeParentsIfNoChildren={false}
aria-label={t(field.title)}
>
<Icon type={icon} />
<span
style={{
@ -601,17 +456,18 @@ Menu.SubMenu = observer(
const field = useField();
const mode = useContext(MenuModeContext);
const Designer = useContext(MenuItemDesignerContext);
const { styles } = useStyles();
const submenu = useMemo(() => {
return {
...others,
className: menuItemClass,
className: styles.menuItemClass,
key: schema.name,
eventKey: schema.name,
label: (
<SchemaContext.Provider value={schema}>
<FieldContext.Provider value={field}>
<SortableItem
className={subMenuDesignerCss}
className={styles.subMenuDesignerCss}
removeParentsIfNoChildren={false}
aria-label={t(field.title)}
>

View File

@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import { useField, useFieldSchema } from '@tachybase/schema';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { useRecord } from '../../../record-provider';
import { useField, useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
const FixedBlockContext = React.createContext<{
setFixedBlock: (value: string | false) => void;
@ -37,7 +37,7 @@ export const useFixedBlock = () => {
return useContext(FixedBlockContext);
};
export const FixedBlockWrapper: React.FC = (props) => {
export const FixedBlockWrapper = (props) => {
const fixedBlock = useFixedSchema();
const { height, fixedBlockUID } = useFixedBlock();
/**
@ -59,30 +59,36 @@ export const FixedBlockWrapper: React.FC = (props) => {
interface FixedBlockProps {
height: number | string;
children: React.ReactNode;
}
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 useStyles = createStyles(({ css }) => {
return {
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 FixedBlock = (props: FixedBlockProps) => {
const { height } = props;
const { styles } = useStyles();
const [fixedBlockUID, _setFixedBlock] = useState<false | string>(false);
const fixedBlockUIDRef = useRef(fixedBlockUID);
const setFixedBlock = (v) => {
@ -92,7 +98,7 @@ const FixedBlock: React.FC<FixedBlockProps> = (props) => {
return (
<FixedBlockContext.Provider value={{ inFixedBlock: true, height, setFixedBlock, fixedBlockUID, fixedBlockUIDRef }}>
<div
className={fixedBlockUID ? fixedBlockCss : ''}
className={fixedBlockUID ? styles.fixedBlockCss : ''}
style={{
height: fixedBlockUID ? `calc(100vh - ${height})` : undefined,
}}

View File

@ -1,16 +1,29 @@
import { css } from '@emotion/css';
import { FormItem } from '@tachybase/components';
import { Field, createForm } from '@tachybase/schema';
import { FormContext, RecursionField, observer, useField, useFieldSchema } from '@tachybase/schema';
import React, { useMemo, useRef } from 'react';
import { FormItem } from '@tachybase/components';
import { createForm, Field, FormContext, observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import { useCollectionManager_deprecated } from '../../../collection-manager';
import { StablePopover } from '../popover';
const useStyles = createStyles(({ css }) => {
return {
overlay: css`
padding-top: 0;
.ant-popover-arrow {
display: none;
}
`,
};
});
export const Editable = observer(
(props) => {
const field: any = useField();
const containerRef = useRef(null);
const fieldSchema = useFieldSchema();
const { styles } = useStyles();
const value = field.value;
const schema: any = {
name: fieldSchema.name,
@ -51,12 +64,7 @@ export const Editable = observer(
}
trigger="click"
placement={'bottomLeft'}
overlayClassName={css`
padding-top: 0;
.ant-popover-arrow {
display: none;
}
`}
overlayClassName={styles.overlay}
>
<div style={{ minHeight: 30, padding: '0 8px' }}>
<FormContext.Provider value={form}>

View File

@ -1,59 +1,64 @@
import { css } from '@emotion/css';
import { observer } from '@tachybase/schema';
import React from 'react';
import { observer } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import { SortableItem, useDesigner } from '../..';
import { useFlag } from '../../../flag-provider/hooks/useFlag';
export const designerCss = ({ margin = '-18px -16px', padding = '18px 16px' } = {}) => css`
position: relative;
margin: ${margin};
padding: ${padding};
export const useStyles = createStyles(({ css }, { margin = '-18px -16px', padding = '18px 16px' }) => {
return {
designer: css`
position: relative;
margin: ${margin};
padding: ${padding};
&:hover {
> .general-schema-designer {
display: block;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
&:hover {
> .general-schema-designer {
display: block;
}
}
}
}
`;
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
};
});
export const TableColumnActionBar = observer(
(props) => {
const Designer = useDesigner();
const { isInSubTable } = useFlag() || {};
const { styles } = useStyles({
margin: isInSubTable ? '-12px -8px' : '-18px -16px',
padding: isInSubTable ? '12px 8px' : '18px 16px',
});
return (
<SortableItem
className={designerCss({
margin: isInSubTable ? '-12px -8px' : '-18px -16px',
padding: isInSubTable ? '12px 8px' : '18px 16px',
})}
>
<SortableItem className={styles.designer}>
<Designer />
{props.children}
</SortableItem>

View File

@ -1,15 +1,16 @@
import { useField, useFieldSchema } from '@tachybase/schema';
import React, { useLayoutEffect } from 'react';
import { useField, useFieldSchema } from '@tachybase/schema';
import {
CollectionFieldContext,
SortableItem,
useCollection_deprecated,
useCollectionManager_deprecated,
useCompile,
useDesigner,
CollectionFieldContext,
useFlag,
} from '../../../';
import { designerCss } from './Table.Column.ActionBar';
import { useStyles } from './Table.Column.ActionBar';
import { isCollectionFieldComponent } from './utils';
export const useColumnSchema = () => {
@ -33,10 +34,14 @@ export const useColumnSchema = () => {
export const TableColumnDecorator = (props) => {
const Designer = useDesigner();
const { isInSubTable } = useFlag() || {};
const { styles } = useStyles({
margin: isInSubTable ? '-12px -8px' : '-18px -16px',
padding: isInSubTable ? '12px 8px' : '18px 16px',
});
const field = useField();
const { fieldSchema, uiSchema, collectionField } = useColumnSchema();
const compile = useCompile();
const { isInSubTable } = useFlag() || {};
useLayoutEffect(() => {
if (field.title) {
return;
@ -49,12 +54,7 @@ export const TableColumnDecorator = (props) => {
}
}, [uiSchema?.title]);
return (
<SortableItem
className={designerCss({
margin: isInSubTable ? '-12px -8px' : '-18px -16px',
padding: isInSubTable ? '12px 8px' : '18px 16px',
})}
>
<SortableItem className={styles.designer}>
<CollectionFieldContext.Provider value={collectionField}>
<Designer fieldSchema={fieldSchema} uiSchema={uiSchema} collectionField={collectionField} />
{/* <RecursionField name={columnSchema.name} schema={columnSchema}/> */}

View File

@ -0,0 +1,117 @@
import { TinyColor } from '@ctrl/tinycolor';
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, token }) => {
const borderColor = new TinyColor(token.colorSettings).setAlpha(0.6).toHex8String();
return {
container: css`
height: 100%;
overflow: hidden;
.ant-table-thead {
.ant-table-cell {
text-align: center;
}
}
.ant-table-wrapper {
height: 100%;
.ant-spin-nested-loading {
height: 100%;
.ant-spin-container {
height: 100%;
display: flex;
flex-direction: column;
}
}
}
.ant-table {
overflow-x: auto;
overflow-y: hidden;
}
`,
toolbar: css`
// 扩大 SchemaToolbar 的面积
.nb-action-link {
margin: -${token.paddingContentVerticalLG}px -${token.marginSM}px;
padding: ${token.paddingContentVerticalLG}px ${token.marginSM}px;
}
`,
topActive: css`
& > td {
border-top: 2px solid ${borderColor} !important;
}
`,
bottomActive: css`
& > td {
border-bottom: 2px solid ${borderColor} !important;
}
`,
highlightRow: css`
& > td {
background-color: ${token.controlItemBgActiveHover} !important;
}
&:hover > td {
background-color: ${token.controlItemBgActiveHover} !important;
}
`,
headerCellDesigner: css`
max-width: 300px;
white-space: nowrap;
&:hover .general-schema-designer {
display: block;
}
`,
bodyCell: css`
max-width: 300px;
white-space: nowrap;
.nb-read-pretty-input-number {
text-align: right;
}
.ant-color-picker-trigger {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
`,
rowSelect: css`
position: relative;
display: flex;
float: left;
align-items: center;
justify-content: space-evenly;
padding-right: 8px;
.nb-table-index {
opacity: 0;
}
&:not(.checked) {
.nb-table-index {
opacity: 1;
}
}
`,
rowSelectHover: css`
&:hover {
.nb-table-index {
opacity: 0;
}
.nb-origin-node {
display: block;
}
}
`,
cellChecked: css`
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
`,
cellCheckedNode: css`
position: absolute;
right: 50%;
transform: translateX(50%);
&:not(.checked) {
display: none;
}
`,
};
});

View File

@ -1,19 +1,26 @@
import { DeleteOutlined, MenuOutlined, CopyOutlined } from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import { SortableContext, SortableContextProps, useSortable } from '@dnd-kit/sortable';
import { css } from '@emotion/css';
import { ArrayField } from '@tachybase/schema';
import { spliceArrayState } from '@tachybase/schema';
import { RecursionField, Schema, observer, useField, useFieldSchema } from '@tachybase/schema';
import { action } from '@tachybase/schema';
import { uid } from '@tachybase/schema';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
action,
ArrayField,
observer,
RecursionField,
Schema,
spliceArrayState,
uid,
useField,
useFieldSchema,
} from '@tachybase/schema';
import { isPortalInBody } from '@tachybase/utils/client';
import { CopyOutlined, DeleteOutlined, MenuOutlined } from '@ant-design/icons';
import { SortableContext, SortableContextProps, useSortable } from '@dnd-kit/sortable';
import { useMemoizedFn } from 'ahooks';
import { Table as AntdTable, TableColumnProps } from 'antd';
import { default as classNames, default as cls } from 'classnames';
import _, { each } from 'lodash';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useToken } from '../__builtins__';
import { DndContext, useDesignable, useTableSize } from '../..';
import {
RecordIndexProvider,
@ -25,13 +32,13 @@ import {
useTableBlockContext,
useTableSelectorContext,
} from '../../../';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
import { useToken } from '../__builtins__';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { isNewRecord, markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
import { SubFormProvider } from '../association-field/hooks';
import { ColumnFieldProvider } from './components/ColumnFieldProvider';
import { useStyles } from './Table.styles';
import { extractIndex, isCollectionFieldComponent, isColumnComponent } from './utils';
import { isNewRecord, markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
const useArrayField = (props) => {
const field = useField<ArrayField>();
@ -40,6 +47,7 @@ const useArrayField = (props) => {
const useTableColumns = (props: { showDel?: boolean; isSubTable?: boolean }) => {
const { token } = useToken();
const { styles } = useStyles();
const field = useArrayField(props);
const schema = useFieldSchema();
const { schemaInWhitelist } = useACLFieldWhitelist();
@ -77,16 +85,7 @@ const useTableColumns = (props: { showDel?: boolean; isSubTable?: boolean }) =>
<RecordIndexProvider index={record.__index || index}>
<RecordProvider isNew={isNewRecord(record)} record={record} parent={parentRecordData}>
<ColumnFieldProvider schema={s} basePath={field.address.concat(record.__index || index)}>
<span
role="button"
className={css`
// 扩大 SchemaToolbar 的面积
.nb-action-link {
margin: -${token.paddingContentVerticalLG}px -${token.marginSM}px;
padding: ${token.paddingContentVerticalLG}px ${token.marginSM}px;
}
`}
>
<span role="button" className={styles.toolbar}>
<RecursionField
basePath={field.address.concat(record.__index || index)}
schema={s}
@ -173,32 +172,16 @@ const useTableColumns = (props: { showDel?: boolean; isSubTable?: boolean }) =>
};
const SortableRow = (props) => {
const { token } = useToken();
const { styles } = useStyles();
const id = props['data-row-key']?.toString();
const { setNodeRef, isOver, active, over } = useSortable({
id,
});
const classObj = useMemo(() => {
const borderColor = new TinyColor(token.colorSettings).setAlpha(0.6).toHex8String();
return {
topActiveClass: css`
& > td {
border-top: 2px solid ${borderColor} !important;
}
`,
bottomActiveClass: css`
& > td {
border-bottom: 2px solid ${borderColor} !important;
}
`,
};
}, [token.colorSettings]);
const className =
(active?.data.current?.sortable.index ?? -1) > (over?.data.current?.sortable?.index ?? -1)
? classObj.topActiveClass
: classObj.bottomActiveClass;
? styles.topActive
: styles.bottomActive;
return (
<tr
@ -263,6 +246,7 @@ export const Table: any = withDynamicSchemaProps(
isSubTable?: boolean;
}) => {
const { token } = useToken();
const { styles } = useStyles();
const { pagination: pagination1, useProps, ...others1 } = props;
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
@ -309,14 +293,7 @@ export const Table: any = withDynamicSchemaProps(
},
};
};
highlightRow = css`
& > td {
background-color: ${token.controlItemBgActiveHover} !important;
}
&:hover > td {
background-color: ${token.controlItemBgActiveHover} !important;
}
`;
highlightRow = styles.highlightRow;
}
useEffect(() => {
@ -338,21 +315,7 @@ export const Table: any = withDynamicSchemaProps(
);
},
cell: (props) => {
return (
<th
{...props}
className={cls(
props.className,
css`
max-width: 300px;
white-space: nowrap;
&:hover .general-schema-designer {
display: block;
}
`,
)}
/>
);
return <th {...props} className={cls(props.className, styles.headerCellDesigner)} />;
},
},
body: {
@ -379,26 +342,7 @@ export const Table: any = withDynamicSchemaProps(
row: (props) => {
return <SortableRow {...props}></SortableRow>;
},
cell: (props) => (
<td
{...props}
className={classNames(
props.className,
css`
max-width: 300px;
white-space: nowrap;
.nb-read-pretty-input-number {
text-align: right;
}
.ant-color-picker-trigger {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
`,
)}
/>
),
cell: (props) => <td {...props} className={classNames(props.className, styles.bodyCell)} />,
},
};
}, [field, onRowDragEnd, dragSort]);
@ -470,67 +414,16 @@ export const Table: any = withDynamicSchemaProps(
<div
role="button"
aria-label={`table-index-${index}`}
className={classNames(
checked ? 'checked' : null,
css`
position: relative;
display: flex;
float: left;
align-items: center;
justify-content: space-evenly;
padding-right: 8px;
.nb-table-index {
opacity: 0;
}
&:not(.checked) {
.nb-table-index {
opacity: 1;
}
}
`,
{
[css`
&:hover {
.nb-table-index {
opacity: 0;
}
.nb-origin-node {
display: block;
}
}
`]: isRowSelect,
},
)}
className={classNames(checked ? 'checked' : null, styles.rowSelect, {
[styles.rowSelectHover]: isRowSelect,
})}
>
<div
className={classNames(
checked ? 'checked' : null,
css`
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
`,
)}
>
<div className={classNames(checked ? 'checked' : null, styles.cellChecked)}>
{dragSort && <SortHandle id={getRowKey(record)} />}
{showIndex && <TableIndex index={index} />}
</div>
{isRowSelect && (
<div
className={classNames(
'nb-origin-node',
checked ? 'checked' : null,
css`
position: absolute;
right: 50%;
transform: translateX(50%);
&:not(.checked) {
display: none;
}
`,
)}
>
<div className={classNames('nb-origin-node', checked ? 'checked' : null, styles.cellCheckedNode)}>
{originNode}
</div>
)}
@ -541,7 +434,7 @@ export const Table: any = withDynamicSchemaProps(
}
: undefined,
};
const SortableWrapper = useCallback<React.FC>(
const SortableWrapper = useCallback(
({ children }) => {
return dragSort
? React.createElement<Omit<SortableContextProps, 'children'>>(
@ -570,32 +463,7 @@ export const Table: any = withDynamicSchemaProps(
};
}, [fixedBlock, tableHeight]);
return (
<div
className={css`
height: 100%;
overflow: hidden;
.ant-table-thead {
.ant-table-cell {
text-align: center;
}
}
.ant-table-wrapper {
height: 100%;
.ant-spin-nested-loading {
height: 100%;
.ant-spin-container {
height: 100%;
display: flex;
flex-direction: column;
}
}
}
.ant-table {
overflow-x: auto;
overflow-y: hidden;
}
`}
>
<div className={styles.container}>
<SortableWrapper>
<AntdTable
ref={tableSizeRefCallback}

View File

@ -1,19 +1,23 @@
import { MenuOutlined } from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import { css } from '@emotion/css';
import { ArrayField, Field } from '@tachybase/schema';
import React, { useContext, useMemo, useState } from 'react';
import {
ArrayField,
Field,
observer,
RecursionField,
Schema,
SchemaExpressionScopeContext,
observer,
useField,
useFieldSchema,
} from '@tachybase/schema';
import { MenuOutlined } from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import { Table, TableColumnProps } from 'antd';
import { createStyles } from 'antd-style';
import { default as classNames, default as cls } from 'classnames';
import React, { useContext, useState } from 'react';
import ReactDragListView from 'react-drag-listview';
import { useToken } from '../__builtins__';
import { DndContext } from '../..';
import {
RecordIndexProvider,
@ -23,7 +27,68 @@ import {
useRequest,
useSchemaInitializerRender,
} from '../../../';
import { useToken } from '../__builtins__';
const useStyles = createStyles(({ css, token }) => {
return {
drag: css`
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
`,
node: css`
position: absolute;
right: 50%;
transform: translateX(50%);
&:not(.checked) {
display: none;
}
`,
headerCell: css`
&:hover .general-schema-designer {
display: block;
}
`,
bodyCell: css`
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,
cell: css`
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
padding-right: 8px;
.nb-table-index {
opacity: 0;
}
&:not(.checked) {
.nb-table-index {
opacity: 1;
}
}
&:hover {
.nb-table-index {
opacity: 0;
}
.nb-origin-node {
display: block;
}
}
`,
table: css`
.ant-table {
overflow-x: auto;
overflow-y: hidden;
}
`,
line: css`
border-bottom: 2px solid ${new TinyColor(token.colorSettings).setAlpha(0.6).toHex8String()} !important;
`,
};
});
const isColumnComponent = (schema: Schema) => {
return schema['x-component']?.endsWith('.Column') > -1;
@ -36,6 +101,7 @@ const useTableColumns = () => {
const scope = useContext(SchemaExpressionScopeContext);
const parentRecordData = useCollectionParentRecordData();
const recordData = useCollectionRecordData();
const { styles } = useStyles();
const columns = schema
.reduceProperties((buf, s) => {
@ -73,59 +139,6 @@ const useTableColumns = () => {
});
};
export const components = {
header: {
wrapper: (props) => {
return (
<DndContext>
<thead {...props} />
</DndContext>
);
},
cell: (props) => {
return (
<th
{...props}
className={cls(
props.className,
css`
&:hover .general-schema-designer {
display: block;
}
`,
)}
/>
);
},
},
body: {
wrapper: (props) => {
return (
<DndContext>
<tbody {...props} />
</DndContext>
);
},
row: (props) => {
return <tr {...props} />;
},
cell: (props) => (
<td
{...props}
className={classNames(
props.className,
css`
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,
)}
/>
),
},
};
const useDef = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
return [selectedRowKeys, setSelectedRowKeys];
@ -174,6 +187,37 @@ export const TableArray: React.FC<any> = observer(
...others
} = props;
const [selectedRowKeys, setSelectedRowKeys] = useSelectedRowKeys();
const { styles } = useStyles();
const components = useMemo(
() => ({
header: {
wrapper: (props) => {
return (
<DndContext>
<thead {...props} />
</DndContext>
);
},
cell: (props) => {
return <th {...props} className={cls(props.className, styles.headerCell)} />;
},
},
body: {
wrapper: (props) => {
return (
<DndContext>
<tbody {...props} />
</DndContext>
);
},
row: (props) => {
return <tr {...props} />;
},
cell: (props) => <td {...props} className={classNames(props.className, styles.bodyCell)} />,
},
}),
[styles],
);
useDataSource({
onSuccess(data) {
field.value = data?.data || [];
@ -200,64 +244,14 @@ export const TableArray: React.FC<any> = observer(
index = index + (current - 1) * pageSize;
}
return (
<div
className={classNames(
checked ? 'checked' : null,
css`
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
padding-right: 8px;
.nb-table-index {
opacity: 0;
}
&:not(.checked) {
.nb-table-index {
opacity: 1;
}
}
&:hover {
.nb-table-index {
opacity: 0;
}
.nb-origin-node {
display: block;
}
}
`,
)}
>
<div
className={classNames(
checked ? 'checked' : null,
css`
position: relative;
display: flex;
align-items: center;
justify-content: space-evenly;
`,
)}
>
<div className={classNames(checked ? 'checked' : null, styles.cell)}>
<div className={classNames(checked ? 'checked' : null, styles.drag)}>
{dragSort && <SortHandle role="button" aria-label={`sort-handle-${record?.name || index}`} />}
{showIndex && (
<TableIndex role="button" aria-label={`table-index-${record?.name || index}`} index={index} />
)}
</div>
<div
className={classNames(
'nb-origin-node',
checked ? 'checked' : null,
css`
position: absolute;
right: 50%;
transform: translateX(50%);
&:not(.checked) {
display: none;
}
`,
)}
>
<div className={classNames('nb-origin-node', checked ? 'checked' : null, styles.node)}>
{originNode}
</div>
</div>
@ -273,14 +267,8 @@ export const TableArray: React.FC<any> = observer(
};
return (
<div
className={css`
.ant-table {
overflow-x: auto;
overflow-y: hidden;
}
`}
>
<div className={styles.table}>
{/* @ts-ignore */}
<ReactDragListView
handleSelector={'.drag-handle'}
onDragEnd={async (fromIndex, toIndex) => {
@ -289,9 +277,7 @@ export const TableArray: React.FC<any> = observer(
field.move(fromIndex, toIndex);
await move(from, to);
}}
lineClassName={css`
border-bottom: 2px solid ${new TinyColor(token.colorSettings).setAlpha(0.6).toHex8String()} !important;
`}
lineClassName={styles.line}
>
<Table
rowKey={defaultRowKey}

View File

@ -1,53 +1,56 @@
import { css } from '@emotion/css';
import { observer } from '@tachybase/schema';
import React from 'react';
import { observer } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import { SortableItem, useDesigner } from '../..';
export const designerCss = css`
position: relative;
&:hover {
> .general-schema-designer {
display: block;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -16px !important;
bottom: -16px !important;
left: -16px !important;
right: -16px !important;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
export const useStyles = createStyles(({ css }) => {
return {
designer: css`
position: relative;
&:hover {
> .general-schema-designer {
display: block;
}
}
}
}
`;
> .general-schema-designer {
position: absolute;
z-index: 999;
display: none;
background: var(--colorBgSettingsHover) !important;
border: 0 !important;
top: -16px !important;
bottom: -16px !important;
left: -16px !important;
right: -16px !important;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
};
});
export const TableColumnActionBar = observer(
(props) => {
const { styles } = useStyles();
const Designer = useDesigner();
return (
<SortableItem className={designerCss}>
<SortableItem className={styles.designer}>
<Designer />
{props.children}
</SortableItem>

View File

@ -1,7 +1,8 @@
import { useField, useFieldSchema } from '@tachybase/schema';
import React, { useLayoutEffect } from 'react';
import { useField, useFieldSchema } from '@tachybase/schema';
import { SortableItem, useCollection_deprecated, useCompile, useDesignable, useDesigner } from '../../../';
import { designerCss } from './Table.Column.ActionBar';
import { useStyles } from './Table.Column.ActionBar';
export const useColumnSchema = () => {
const { getField } = useCollection_deprecated();
@ -24,6 +25,7 @@ export const TableColumnDecorator = (props) => {
const Designer = useDesigner();
const field = useField();
const { fieldSchema, uiSchema, collectionField } = useColumnSchema();
const { styles } = useStyles();
const { refresh } = useDesignable();
const compile = useCompile();
useLayoutEffect(() => {
@ -38,7 +40,7 @@ export const TableColumnDecorator = (props) => {
}
}, [uiSchema?.title]);
return (
<SortableItem className={designerCss}>
<SortableItem className={styles.designer}>
<Designer fieldSchema={fieldSchema} uiSchema={uiSchema} collectionField={collectionField} />
{/* <RecursionField name={columnSchema.name} schema={columnSchema}/> */}
{field.title || compile(uiSchema?.title)}

View File

@ -1,14 +1,68 @@
import { css } from '@emotion/css';
import { observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { Tabs as AntdTabs, TabPaneProps, TabsProps } from 'antd';
import classNames from 'classnames';
import React, { useMemo } from 'react';
import { observer, RecursionField, useField, useFieldSchema } from '@tachybase/schema';
import { Tabs as AntdTabs, TabPaneProps, TabsProps } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import { useSchemaInitializerRender } from '../../../application';
import { Icon } from '../../../icon';
import { DndContext, SortableItem } from '../../common';
import { useDesigner } from '../../hooks/useDesigner';
import { useTabsContext } from './context';
import { TabsDesigner } from './Tabs.Designer';
import { useSchemaInitializerRender } from '../../../application';
const useStyles = createStyles(({ css }) => {
return {
designer: css`
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;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
};
});
export const Tabs: any = observer(
(props: TabsProps) => {
@ -48,60 +102,13 @@ export const Tabs: any = observer(
{ displayName: 'Tabs' },
);
const designerCss = css`
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;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`;
Tabs.TabPane = observer(
(props: TabPaneProps & { icon?: any }) => {
const { styles } = useStyles();
const Designer = useDesigner();
const field = useField();
return (
<SortableItem className={classNames('nb-action-link', designerCss, props.className)}>
<SortableItem className={classNames('nb-action-link', styles.designer, props.className)}>
{props.icon && <Icon style={{ marginRight: 2 }} type={props.icon} />} {props.tab || field.title}
<Designer />
</SortableItem>

View File

@ -1,18 +1,21 @@
import { CloseCircleFilled } from '@ant-design/icons';
import { css, cx } from '@emotion/css';
import React, { useCallback, useEffect, useMemo } from 'react';
import { useForm } from '@tachybase/schema';
import { error } from '@tachybase/utils/client';
import { CloseCircleFilled } from '@ant-design/icons';
import { Input as AntInput, Cascader, DatePicker, InputNumber, Select, Space, Tag } from 'antd';
import { createStyles } from 'antd-style';
import useAntdInputStyle from 'antd/es/input/style';
import type { DefaultOptionType } from 'antd/lib/cascader';
import cx from 'classnames';
import classNames from 'classnames';
import dayjs from 'dayjs';
import { cloneDeep } from 'lodash';
import React, { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../../hooks';
import { XButton } from './XButton';
import { useStyles } from './style';
import { XButton } from './XButton';
const JT_VALUE_RE = /^\s*{{\s*([^{}]+)\s*}}\s*$/;
@ -129,6 +132,39 @@ function getTypedConstantOption(type: string, types: true | string[], fieldNames
};
}
const useStyles2 = createStyles(({ css }, { props: { disabled: boolean } }) => {
return {
container: css`
position: relative;
line-height: 0;
&:hover {
.clear-button {
display: inline-block;
}
}
.ant-input {
overflow: auto;
white-space: nowrap;
${disabled ? '' : 'padding-right: 28px;'}
.ant-tag {
display: inline;
line-height: 19px;
margin: 0;
padding: 2px 7px;
border-radius: 10px;
white-space: nowrap;
}
}
`,
button: css`
margin-left: -1px;
`,
};
});
export function Input(props) {
const {
value = '',
@ -259,40 +295,12 @@ export function Input(props) {
}, [variable, options.length]);
const disabled = props.disabled || form.disabled;
const { styles: styles2 } = useStyles2({ disabled });
return wrapSSR(
<Space.Compact style={style} className={classNames(componentCls, hashId, className)}>
{variable ? (
<div
className={cx(
'variable',
css`
position: relative;
line-height: 0;
&:hover {
.clear-button {
display: inline-block;
}
}
.ant-input {
overflow: auto;
white-space: nowrap;
${disabled ? '' : 'padding-right: 28px;'}
.ant-tag {
display: inline;
line-height: 19px;
margin: 0;
padding: 2px 7px;
border-radius: 10px;
white-space: nowrap;
}
}
`,
)}
>
<div className={cx('variable', styles2.container)}>
<div
role="button"
aria-label="variable-tag"
@ -341,14 +349,7 @@ export function Input(props) {
fieldNames={fieldNames}
disabled={disabled}
>
{button ?? (
<XButton
className={css(`
margin-left: -1px;
`)}
type={variable ? 'primary' : 'default'}
/>
)}
{button ?? <XButton className={styles2.button} type={variable ? 'primary' : 'default'} />}
</Cascader>
</Space.Compact>,
);

View File

@ -1,21 +1,23 @@
import React from 'react';
import { createStyles } from 'antd-style';
import { Input } from '../input';
import { RawTextArea } from './RawTextArea';
import { css } from '@emotion/css';
const useStyles = createStyles(({ css }) => {
return {
button: css`
&:not(:hover) {
border-right-color: transparent;
border-top-color: transparent;
}
background-color: transparent;
`,
};
});
export function JSONInput(props) {
return (
<RawTextArea
buttonClass={css`
&:not(:hover) {
border-right-color: transparent;
border-top-color: transparent;
}
background-color: transparent;
`}
{...props}
component={Input.JSON}
/>
);
const { styles } = useStyles();
return <RawTextArea buttonClass={styles.button} {...props} component={Input.JSON} />;
}

View File

@ -1,6 +1,7 @@
import React, { useRef, useState } from 'react';
import { css } from '@emotion/css';
import { Button, Input } from 'antd';
import { createStyles } from 'antd-style';
import { cloneDeep } from 'lodash';
import { VariableSelect } from './VariableSelect';
@ -16,11 +17,38 @@ function setNativeInputValue(input, value) {
);
}
const useStyles = createStyles(({ css }) => {
return {
container: css`
position: relative;
.ant-input {
width: 100%;
}
`,
group: css`
position: absolute;
right: 0;
top: 0;
.ant-btn-sm {
font-size: 85%;
}
`,
select: css`
&:not(:hover) {
border-right-color: transparent;
border-top-color: transparent;
}
background-color: transparent;
`,
};
});
export function RawTextArea(props): JSX.Element {
const inputRef = useRef<any>(null);
const { changeOnSelect, component: Component = Input.TextArea, ...others } = props;
const scope = typeof props.scope === 'function' ? props.scope() : props.scope;
const [options, setOptions] = useState(scope ? cloneDeep(scope) : []);
const { styles } = useStyles();
function onInsert(selected) {
if (!inputRef.current) {
@ -38,36 +66,11 @@ export function RawTextArea(props): JSX.Element {
textArea.focus();
}
return (
<div
className={css`
position: relative;
.ant-input {
width: 100%;
}
`}
>
<div className={styles.container}>
<Component {...others} ref={inputRef} />
<Button.Group
className={css`
position: absolute;
right: 0;
top: 0;
.ant-btn-sm {
font-size: 85%;
}
`}
>
<Button.Group className={styles.group}>
<VariableSelect
className={
props.buttonClass ??
css`
&:not(:hover) {
border-right-color: transparent;
border-top-color: transparent;
}
background-color: transparent;
`
}
className={props.buttonClass ?? styles.select}
fieldNames={props.fieldNames}
options={options}
setOptions={setOptions}

View File

@ -1,15 +1,16 @@
import { css, cx } from '@emotion/css';
import { useForm } from '@tachybase/schema';
import { Input, Space } from 'antd';
import { cloneDeep } from 'lodash';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import sanitizeHTML from 'sanitize-html';
import { useForm } from '@tachybase/schema';
import { error } from '@tachybase/utils/client';
import { Space } from 'antd';
import { createStyles } from 'antd-style';
import cx from 'classnames';
import { cloneDeep } from 'lodash';
import sanitizeHTML from 'sanitize-html';
import { EllipsisWithTooltip } from '../..';
import { VariableSelect } from './VariableSelect';
import { useStyles } from './style';
import { VariableSelect } from './VariableSelect';
type RangeIndexes = [number, number, number, number];
@ -187,12 +188,60 @@ function getCurrentRange(element: HTMLElement): RangeIndexes {
return result;
}
const useStyles2 = createStyles(({ css }) => {
return {
container: css`
&.ant-input-group.ant-input-group-compact {
display: flex;
.ant-input {
flex-grow: 1;
min-width: 200px;
}
.ant-input-disabled {
.ant-tag {
color: #bfbfbf;
border-color: #d9d9d9;
}
}
}
> .x-button {
height: min-content;
}
`,
button: css`
overflow: auto;
white-space: ${multiline ? 'normal' : 'nowrap'};
.ant-tag {
display: inline;
line-height: 19px;
margin: 0 0.5em;
padding: 2px 7px;
border-radius: 10px;
}
`,
pretty: css`
overflow: auto;
.ant-tag {
display: inline;
line-height: 19px;
margin: 0 0.25em;
padding: 2px 7px;
border-radius: 10px;
}
`,
};
});
export function TextArea(props) {
const { wrapSSR, hashId, componentCls } = useStyles();
const { value = '', scope, onChange, multiline = true, changeOnSelect } = props;
const inputRef = useRef<HTMLDivElement>(null);
const [options, setOptions] = useState([]);
const form = useForm();
const { styles } = useStyles2();
const keyLabelMap = useMemo(() => createOptionsValueLabelMap(options), [options]);
const [ime, setIME] = useState<boolean>(false);
const [changed, setChanged] = useState(false);
@ -360,31 +409,7 @@ export function TextArea(props) {
const disabled = props.disabled || form.disabled;
return wrapSSR(
<Space.Compact
className={cx(
componentCls,
hashId,
css`
&.ant-input-group.ant-input-group-compact {
display: flex;
.ant-input {
flex-grow: 1;
min-width: 200px;
}
.ant-input-disabled {
.ant-tag {
color: #bfbfbf;
border-color: #d9d9d9;
}
}
}
> .x-button {
height: min-content;
}
`,
)}
>
<Space.Compact className={cx(componentCls, hashId, styles.container)}>
<div
role="button"
aria-label="textbox"
@ -394,23 +419,7 @@ export function TextArea(props) {
onPaste={onPaste}
onCompositionStart={onCompositionStart}
onCompositionEnd={onCompositionEnd}
className={cx(
hashId,
'ant-input',
{ 'ant-input-disabled': disabled },
css`
overflow: auto;
white-space: ${multiline ? 'normal' : 'nowrap'};
.ant-tag {
display: inline;
line-height: 19px;
margin: 0 0.5em;
padding: 2px 7px;
border-radius: 10px;
}
`,
)}
className={cx(hashId, 'ant-input', { 'ant-input-disabled': disabled }, styles.button)}
ref={inputRef}
contentEditable={!disabled}
dangerouslySetInnerHTML={{ __html: html }}
@ -458,12 +467,13 @@ async function preloadOptions(scope, value) {
return options;
}
TextArea.ReadPretty = function ReadPretty(props): JSX.Element {
TextArea.ReadPretty = function ReadPretty(props) {
const { value } = props;
const scope = typeof props.scope === 'function' ? props.scope() : props.scope;
const [options, setOptions] = useState([]);
const keyLabelMap = useMemo(() => createOptionsValueLabelMap(options), [options]);
const { styles } = useStyles2();
useEffect(() => {
preloadOptions(scope, value)
@ -474,22 +484,7 @@ TextArea.ReadPretty = function ReadPretty(props): JSX.Element {
}, [scope, value]);
const html = renderHTML(value ?? '', keyLabelMap);
const content = (
<span
dangerouslySetInnerHTML={{ __html: html }}
className={css`
overflow: auto;
.ant-tag {
display: inline;
line-height: 19px;
margin: 0 0.25em;
padding: 2px 7px;
border-radius: 10px;
}
`}
/>
);
const content = <span dangerouslySetInnerHTML={{ __html: html }} className={styles.pretty} />;
return (
<EllipsisWithTooltip ellipsis popoverContent={content}>

View File

@ -1,7 +1,9 @@
import { cx } from '@emotion/css';
import { Cascader } from 'antd';
import React, { useState } from 'react';
import { Cascader } from 'antd';
import cx from 'classnames';
import { useTranslation } from 'react-i18next';
import { useToken } from '../__builtins__';
import useStyles from './VariableSelect.style';
import { XButton } from './XButton';

View File

@ -1,8 +1,10 @@
import React, { createContext, HTMLAttributes, useContext, useMemo } from 'react';
import { observer, Schema, useField, useFieldSchema } from '@tachybase/schema';
import { TinyColor } from '@ctrl/tinycolor';
import { useDraggable, useDroppable } from '@dnd-kit/core';
import { cx } from '@emotion/css';
import { Schema, observer, useField, useFieldSchema } from '@tachybase/schema';
import React, { HTMLAttributes, createContext, useContext, useMemo } from 'react';
import cx from 'classnames';
import { useToken } from '../../antd/__builtins__';
export const DraggableContext = createContext(null);

View File

@ -1,9 +1,11 @@
import { DownOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import React, { createRef, forwardRef, useEffect, useMemo, useState } from 'react';
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@tachybase/schema';
import { DownOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import React, { useEffect, useMemo, useState, forwardRef, createRef } from 'react';
import { createStyles } from 'antd-style';
import { composeRef } from 'rc-util/lib/ref';
import { useDesignable } from '../../';
import { useACLRolesCheck, useRecordPkValue } from '../../acl/ACLProvider';
import {
@ -17,45 +19,45 @@ import { linkageAction } from '../../schema-component/antd/action/utils';
import { parseVariables } from '../../schema-component/common/utils/uitls';
import { useLocalVariables, useVariables } from '../../variables';
export const actionDesignerCss = css`
position: relative;
&:hover {
.general-schema-designer {
display: block;
}
}
.general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
const useStyles = createStyles(({ css }) => {
return {
designer: css`
position: relative;
&:hover {
.general-schema-designer {
display: block;
}
}
}
}
`;
.general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`,
};
});
export function useAclCheck(actionPath) {
const aclCheck = useAclCheckFn();
@ -108,6 +110,7 @@ const InternalCreateRecordAction = (props: any, ref) => {
const values = useRecord();
const ctx = useActionContext();
const variables = useVariables();
const { styles } = useStyles();
const localVariables = useLocalVariables({ currentForm: { values } as any });
useEffect(() => {
field.stateOfLinkageRules = {};
@ -129,7 +132,7 @@ const InternalCreateRecordAction = (props: any, ref) => {
const buttonRef = composeRef(ref, internalRef);
return (
//@ts-ignore
<div className={actionDesignerCss} ref={buttonRef as React.Ref<HTMLButtonElement>}>
<div className={styles.actionDesignerCss} ref={buttonRef as React.Ref<HTMLButtonElement>}>
<ActionContextProvider value={{ ...ctx, visible, setVisible }}>
<CreateAction
{...props}
@ -201,6 +204,7 @@ export const CreateAction = observer(
const localVariables = useLocalVariables({ currentForm: { values } as any });
const compile = useCompile();
const { designable } = useDesignable();
const { styles } = useStyles();
const icon = props.icon || null;
const menuItems = useMemo<MenuProps['items']>(() => {
return inheritsCollections.map((option) => ({
@ -233,7 +237,7 @@ export const CreateAction = observer(
});
}, [field, linkageRules, localVariables, variables]);
return (
<div className={actionDesignerCss}>
<div className={styles.actionDesignerCss}>
<FinallyButton
{...{
inheritsCollections,

View File

@ -1,51 +1,20 @@
import { DragOutlined, MenuOutlined, PlusOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import React, { FC, PropsWithChildren, useEffect, useMemo, useRef } from 'react';
import { useField, useFieldSchema } from '@tachybase/schema';
import { DragOutlined, MenuOutlined, PlusOutlined } from '@ant-design/icons';
import { Space } from 'antd';
import classNames from 'classnames';
import React, { FC, PropsWithChildren, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaToolbarProvider, useSchemaInitializerRender, useSchemaSettingsRender } from '../application';
import { useDataSourceManager } from '../data-source/data-source/DataSourceManagerProvider';
import { useDataSource } from '../data-source/data-source/DataSourceProvider';
import { DragHandler, useCompile, useDesignable, useGridContext, useGridRowContext } from '../schema-component';
import { gridRowColWrap } from '../schema-initializer/utils';
import { SchemaSettingsDropdown } from './SchemaSettings';
import { useGetAriaLabelOfDesigner } from './hooks/useGetAriaLabelOfDesigner';
import { SchemaSettingsDropdown } from './SchemaSettings';
import { useStyles } from './styles';
const titleCss = css`
pointer-events: none;
position: absolute;
font-size: 12px;
/* background: var(--colorSettings);
color: #fff; */
padding: 0;
line-height: 16px;
height: 16px;
border-bottom-right-radius: 2px;
border-radius: 2px;
top: 2px;
left: 2px;
.title-tag {
padding: 0 3px;
border-radius: 2px;
background: var(--colorSettings);
color: #fff;
display: block;
}
`;
const overrideAntdCSS = css`
& .ant-space-item .anticon {
margin: 0;
}
&:hover {
display: block !important;
}
`;
export interface GeneralSchemaDesignerProps {
disableInitializer?: boolean;
title?: string;
@ -89,6 +58,7 @@ export const GeneralSchemaDesigner: FC<PropsWithChildren<GeneralSchemaDesignerPr
);
const rowCtx = useGridRowContext();
const ctx = useGridContext();
const { styles } = useStyles();
const dm = useDataSourceManager();
const dataSources = dm?.getDataSources();
const dataSourceContext = useDataSource();
@ -116,9 +86,9 @@ export const GeneralSchemaDesigner: FC<PropsWithChildren<GeneralSchemaDesignerPr
}
return (
<SchemaToolbarProvider {...contextValue}>
<div className={classNames('general-schema-designer', overrideAntdCSS)}>
<div className={classNames('general-schema-designer', styles.overrideAntdCSS)}>
{title && (
<div className={classNames('general-schema-designer-title', titleCss)}>
<div className={classNames('general-schema-designer-title', styles.titleCss)}>
<Space size={2}>
<span className={'title-tag'}>
{showDataSource && dataSource

View File

@ -1,6 +1,6 @@
import { createStyles } from 'antd-style';
export const useStyles = createStyles(() => {
export const useStyles = createStyles(({ css }) => {
return {
toolbar: {
position: 'absolute',
@ -52,5 +52,35 @@ export const useStyles = createStyles(() => {
alignSelf: 'stretch',
},
},
titleCss: css`
pointer-events: none;
position: absolute;
font-size: 12px;
/* background: var(--colorSettings);
color: #fff; */
padding: 0;
line-height: 16px;
height: 16px;
border-bottom-right-radius: 2px;
border-radius: 2px;
top: 2px;
left: 2px;
.title-tag {
padding: 0 3px;
border-radius: 2px;
background: var(--colorSettings);
color: #fff;
display: block;
}
`,
overrideAntdCSS: css`
& .ant-space-item .anticon {
margin: 0;
}
&:hover {
display: block !important;
}
`,
};
});