Merge branch 'main' of https://github.com/webzard-io/sunmao-ui into fix/windlike-patch

This commit is contained in:
MrWindlike 2022-04-13 16:14:37 +08:00
commit 99c5c9abcb
8 changed files with 236 additions and 181 deletions

View File

@ -36,11 +36,13 @@
"eslint-plugin-react-hooks": "^4.3.0",
"lodash-es": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react-dom": "^17.0.2",
"react-resizable": "^3.0.4"
},
"devDependencies": {
"@types/lodash": "^4.14.170",
"@types/lodash-es": "^4.17.5",
"@types/react-resizable": "^1.7.4",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.31.1",

View File

@ -10,7 +10,7 @@ import { css } from '@emotion/css';
import { Type, Static } from '@sinclair/typebox';
import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
import { TablePropsSpec, ColumnSpec } from '../generated/types/Table';
import React, { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import React, { CSSProperties, useEffect, useMemo, useRef, useState } from 'react';
import { sortBy } from 'lodash-es';
import {
LIST_ITEM_EXP,
@ -19,6 +19,8 @@ import {
implementRuntimeComponent,
} from '@sunmao-ui/runtime';
import { TableInstance } from '@arco-design/web-react/es/Table/table';
import { ColumnProps } from '@arco-design/web-react/es/Table';
import { Resizable, ResizeCallbackData } from 'react-resizable';
const TableStateSpec = Type.Object({
clickedRow: Type.Optional(Type.Any()),
@ -32,14 +34,7 @@ type SortRule = {
direction?: 'ascend' | 'descend';
};
type ColumnProperty = Static<typeof ColumnSpec> & {
filterDropdown?: ({
filterKeys,
setFilterKeys,
confirm,
}: filterDropdownParam) => ReactNode;
render?: (ceilValue: any, record: any, index: number) => ReactNode;
};
type ColumnProperty = Static<typeof ColumnSpec> & ColumnProps;
type filterDropdownParam = {
filterKeys?: string[];
@ -63,6 +58,56 @@ const rowClickStyle = css`
}
`;
const resizableStyle = css`
position: relative;
background-clip: padding-box;
`;
const resizableHandleStyle = css`
position: absolute;
width: 10px;
height: 100%;
bottom: 0;
right: -5px;
cursor: col-resize;
z-index: 1;
`;
type ResizableTitleProps = {
onResize: (e: React.SyntheticEvent, data: ResizeCallbackData) => void;
width: number;
style: CSSProperties;
};
const ResizableTitle = (props: ResizableTitleProps) => {
const { onResize, width, ...restProps } = props;
if (!width) {
return <th {...restProps} />;
}
return (
<Resizable
width={width}
height={0}
handle={
<span
className={css`
${resizableStyle}
${resizableHandleStyle}
`}
onClick={e => {
e.stopPropagation();
}}
/>
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>
<th {...restProps} />
</Resizable>
);
};
export const exampleProperties: Static<typeof TablePropsSpec> = {
columns: [
{
@ -172,6 +217,24 @@ export const Table = implementRuntimeComponent({
direction: undefined,
});
const [filterRule, setFilterRule] = useState();
const [columns, setColumns] = useState<ColumnProperty[]>([]);
const handleResize = (
columnIdx: number
): ((e: React.SyntheticEvent, data: ResizeCallbackData) => void) => {
return (_e, data) => {
const { size } = data;
setColumns(prevColumns => {
const nextColumns = [...prevColumns];
nextColumns[columnIdx] = {
...nextColumns[columnIdx],
width: size.width,
};
return nextColumns;
});
};
};
const filteredData = useMemo(() => {
let filteredData = Array.isArray(data) ? data : [];
@ -207,106 +270,115 @@ export const Table = implementRuntimeComponent({
});
}, [selectedRowKeys, mergeState]);
const inputRef = useRef(null);
const columns = cProps.columns!.map((column, i) => {
const newColumn: ColumnProperty = { ...column };
if (newColumn.filter) {
newColumn.filterDropdown = ({
filterKeys,
setFilterKeys,
confirm,
}: filterDropdownParam) => {
return (
<div className="arco-table-custom-filter">
<Input.Search
ref={inputRef}
searchButton
placeholder="Please input and enter"
value={filterKeys?.[0] || ''}
onChange={value => {
setFilterKeys && setFilterKeys(value ? [value] : []);
}}
onSearch={() => {
confirm && confirm();
}}
/>
</div>
);
};
}
newColumn.render = (ceilValue: any, record: any, index: number) => {
const evalOptions = {
evalListItem: true,
scopeObject: {
[LIST_ITEM_EXP]: record,
},
};
const evaledColumn: ColumnProperty = services.stateManager.deepEval(
column,
evalOptions
);
const value = record[evaledColumn.dataIndex];
let colItem;
switch (evaledColumn.type) {
case 'button':
const handleClick = () => {
const rawColumn = (component.properties.columns as ColumnProperty[])[i];
if (!rawColumn.btnCfg) return;
const evaledButtonConfig = services.stateManager.deepEval(
rawColumn.btnCfg,
evalOptions
useEffect(() => {
setColumns(
cProps.columns!.map((column, i) => {
const newColumn: ColumnProperty = { ...column };
if (newColumn.filter) {
newColumn.filterDropdown = ({
filterKeys,
setFilterKeys,
confirm,
}: filterDropdownParam) => {
return (
<div className="arco-table-custom-filter">
<Input.Search
searchButton
placeholder="Please input and enter"
value={filterKeys?.[0] || ''}
onChange={value => {
setFilterKeys && setFilterKeys(value ? [value] : []);
}}
onSearch={() => {
confirm && confirm();
}}
/>
</div>
);
};
}
evaledButtonConfig.handlers.forEach(handler => {
services.apiService.send('uiMethod', {
componentId: handler.componentId,
name: handler.method.name,
parameters: handler.method.parameters || {},
});
});
if (newColumn.width) {
newColumn.onHeaderCell = col => ({
width: col.width,
onResize: handleResize(i),
});
}
newColumn.render = (ceilValue: any, record: any, index: number) => {
const evalOptions = {
evalListItem: true,
scopeObject: {
[LIST_ITEM_EXP]: record,
},
};
colItem = (
<Button
onClick={() => {
handleClick();
}}
>
{evaledColumn.btnCfg?.text}
</Button>
const evaledColumn: ColumnProperty = services.stateManager.deepEval(
column,
evalOptions
);
break;
case 'link':
colItem = <Link href={value}>{evaledColumn.displayValue || value}</Link>;
break;
case 'module':
const evalScope = {
[LIST_ITEM_EXP]: record,
[LIST_ITEM_INDEX_EXP]: index,
};
colItem = (
<ModuleRenderer
app={app}
evalScope={evalScope}
handlers={evaledColumn.module?.handlers || []}
id={evaledColumn.module?.id || ''}
properties={evaledColumn.module?.properties || {}}
services={services}
type={evaledColumn.module?.type || ''}
/>
);
break;
default:
colItem = <span>{evaledColumn.displayValue || value}</span>;
break;
}
return colItem;
};
return newColumn;
});
const value = record[evaledColumn.dataIndex];
let colItem;
switch (evaledColumn.type) {
case 'button':
const handleClick = () => {
const rawColumn = (component.properties.columns as ColumnProperty[])[i];
if (!rawColumn.btnCfg) return;
const evaledButtonConfig = services.stateManager.deepEval(
rawColumn.btnCfg,
evalOptions
);
evaledButtonConfig.handlers.forEach(handler => {
services.apiService.send('uiMethod', {
componentId: handler.componentId,
name: handler.method.name,
parameters: handler.method.parameters || {},
});
});
};
colItem = (
<Button
onClick={() => {
handleClick();
}}
>
{evaledColumn.btnCfg?.text}
</Button>
);
break;
case 'link':
colItem = <Link href={value}>{evaledColumn.displayValue || value}</Link>;
break;
case 'module':
const evalScope = {
[LIST_ITEM_EXP]: record,
[LIST_ITEM_INDEX_EXP]: index,
};
colItem = (
<ModuleRenderer
app={app}
evalScope={evalScope}
handlers={evaledColumn.module?.handlers || []}
id={evaledColumn.module?.id || ''}
properties={evaledColumn.module?.properties || {}}
services={services}
type={evaledColumn.module?.type || ''}
/>
);
break;
default:
const text = evaledColumn.displayValue || value;
colItem = <span title={column.ellipsis ? text : ''}>{text}</span>;
break;
}
return colItem;
};
return newColumn;
})
);
}, [cProps.columns]);
const handleChange = (
pagination: PaginationProps,
@ -340,6 +412,11 @@ export const Table = implementRuntimeComponent({
${rowClick ? rowClickStyle : ''}
`}
{...cProps}
components={{
header: {
th: ResizableTitle,
},
}}
columns={columns}
pagination={{
total: sortedData!.length,

View File

@ -32,6 +32,13 @@ export const ColumnSpec = Type.Object({
category: Category.Basic,
description: 'The text you want to display instead of raw text.',
}),
width: Type.Optional(Type.Number({
title: 'Width',
})),
ellipsis:Type.Optional(Type.Boolean({
title:'Ellipsis',
description:'If the cell content exceeds the length, whether it is automatically omitted and displays ...,After setting this property, the table-layout of the table will automatically become fixed.'
})),
sorter: Type.Boolean({
title: 'Enable Sort',
}),
@ -107,10 +114,10 @@ export const TablePropsSpec = Type.Object({
category: Category.Layout,
weight: 10,
}),
rowClick:Type.Boolean({
rowClick: Type.Boolean({
title: 'Row Click',
category: Category.Basic,
description:'If on, the table can be selected without setting the rowSelectionType'
description: 'If on, the table can be selected without setting the rowSelectionType'
}),
loading: Type.Boolean({
title: 'Show Loading',

View File

@ -41,7 +41,6 @@
"@sunmao-ui/core": "^0.5.4",
"@sunmao-ui/editor-sdk": "^0.1.8",
"@sunmao-ui/runtime": "^0.5.6",
"@types/react-resizable": "^1.7.4",
"acorn": "^8.7.0",
"acorn-loose": "^8.3.0",
"acorn-walk": "^8.2.0",
@ -53,10 +52,10 @@
"lodash-es": "^4.17.21",
"mobx": "^6.3.8",
"mobx-react-lite": "^3.2.2",
"re-resizable": "^6.9.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-json-tree": "^0.16.1",
"react-resizable": "^3.0.4",
"tern": "^0.24.3"
},
"devDependencies": {

View File

@ -15,6 +15,7 @@ import { groupBy, sortBy } from 'lodash-es';
import { EditorServices } from '../../types';
import { ExplorerMenuTabs } from '../../constants/enum';
import { RuntimeComponent } from '@sunmao-ui/core';
import { css } from '@emotion/css';
type Props = {
services: EditorServices;
@ -48,6 +49,12 @@ function getTagColor(version: string): string {
}
}
const tagStyle = css`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const IGNORE_COMPONENTS = ['dummy'];
export const ComponentList: React.FC<Props> = ({ services }) => {
@ -138,7 +145,7 @@ export const ComponentList: React.FC<Props> = ({ services }) => {
>
{c.metadata.displayName}
<Tag colorScheme={getTagColor(c.version)} size="sm">
{c.version}
<div className={tagStyle}>{c.version}</div>
</Tag>
</Flex>
);

View File

@ -26,8 +26,7 @@ import { css } from '@emotion/css';
import { EditorMaskWrapper } from './EditorMaskWrapper';
import { AppModel } from '../AppModel/AppModel';
import { LocalStorageForm } from './DataSource/LocalStorageForm';
import { Resizable } from 'react-resizable';
import { EXPLORE_MENU_MIN_WIDTH, TOOL_MENU_MIN_WIDTH } from '../constants/layout';
import { Resizable } from 're-resizable';
type ReturnOfInit = ReturnType<typeof initSunmaoUI>;
@ -40,40 +39,6 @@ type Props = {
libs: SunmaoLib[];
};
const getResizeBarStyle = (type: 'exploreMenu' | 'toolMenu') => {
return css`
.resize-bar {
position: absolute;
${type === 'exploreMenu' ? 'right: 0' : 'left:0'};
top: 0;
width: 8px;
height: 100%;
${type === 'exploreMenu'
? 'transform: translateX(50%)'
: 'transform: translateX(-50%)'};
cursor: col-resize;
display: flex;
place-items: center;
justify-content: center;
i {
width: 3px;
height: 100%;
transition: transform 0.1s ease-in, opacity 0.1s ease-in;
background-color: #eee;
opacity: 0;
transform: scaleX(0);
transform-origin: center;
}
&:hover i {
transform: scaleX(1);
opacity: 1;
}
}
`;
};
const ApiFormStyle = css`
width: 100%;
height: 100%;
@ -104,8 +69,6 @@ export const Editor: React.FC<Props> = observer(
const [code, setCode] = useState('');
const [recoverKey, setRecoverKey] = useState(0);
const [isError, setIsError] = useState<boolean>(false);
const [exploreMenuWidth, setExploreMenuWidth] = useState(EXPLORE_MENU_MIN_WIDTH);
const [toolMenuWidth, setToolMenuWidth] = useState(TOOL_MENU_MIN_WIDTH);
const onError = (err: Error | null) => {
setIsError(err !== null);
@ -222,7 +185,7 @@ export const Editor: React.FC<Props> = observer(
if (codeMode) {
return (
<Flex width="100%" height="100%">
<Box flex="1">
<Box width="full" height="full">
<SchemaEditor
defaultCode={JSON.stringify(app, null, 2)}
onChange={setCode}
@ -235,27 +198,21 @@ export const Editor: React.FC<Props> = observer(
return (
<>
<Resizable
width={exploreMenuWidth}
height={Infinity}
minConstraints={[200, Infinity]}
maxConstraints={[480, Infinity]}
axis="x"
onResize={(_e, data) => {
setExploreMenuWidth(data.size.width);
defaultSize={{
width: 280,
height: '100%',
}}
handle={
<div className="resize-bar">
<i />
</div>
}
enable={{ right: true }}
style={{ zIndex: 2 }}
maxWidth={480}
minWidth={200}
>
<Box
width={exploreMenuWidth}
className={getResizeBarStyle('exploreMenu')}
borderRightWidth="1px"
borderColor="gray.200"
position="relative"
zIndex="2"
height="full"
>
<Tabs
height="100%"
@ -301,28 +258,18 @@ export const Editor: React.FC<Props> = observer(
<Flex flex={1} position="relative" overflow="hidden">
{appBox}
<Resizable
width={toolMenuWidth}
height={Infinity}
minConstraints={[200, Infinity]}
maxConstraints={[480, Infinity]}
resizeHandles={['w']}
axis="x"
onResize={(_e, data) => {
setToolMenuWidth(data.size.width);
defaultSize={{
width: 320,
height: '100%',
}}
handle={
<div className="resize-bar">
<i />
</div>
}
enable={{ left: true }}
maxWidth={480}
minWidth={250}
>
<Box
minWidth={toolMenuWidth}
width={toolMenuWidth}
className={getResizeBarStyle('toolMenu')}
height="full"
borderLeftWidth="1px"
borderColor="gray.200"
overflow="auto"
position="relative"
zIndex="0"
>

View File

@ -11,3 +11,7 @@
@import 'codemirror/addon/hint/show-hint.css';
@import 'codemirror/addon/tern/tern.css';
/* let tooltip show on top of modal */
.CodeMirror-Tern-tooltip {
z-index: 1800;
}

View File

@ -5862,6 +5862,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-memoize@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e"
integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
@ -9188,6 +9193,13 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
re-resizable@^6.9.5:
version "6.9.5"
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.5.tgz#9d54ad5ee6323a48b83e8a49939960cafba235bc"
integrity sha512-Q4+K8gOPbUBmbJCa0qfoVXBGnCwkAJrZ9KUca4GDn5FmxyV2HtLrBz7u43uUOb0y7xKbwcfuftweiOCIDEiCQA==
dependencies:
fast-memoize "^2.5.1"
react-base16-styling@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.9.1.tgz#4906b4c0a51636f2dca2cea8b682175aa8bd0c92"