mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
feat: add split pane for idraw design
This commit is contained in:
parent
b23aeae6f1
commit
f643cd7d7b
24 changed files with 1130 additions and 331 deletions
|
|
@ -1,149 +0,0 @@
|
|||
import { createUUID } from '@idraw/util';
|
||||
import type { DesignData } from '../src';
|
||||
|
||||
const data: DesignData = {
|
||||
components: [
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'component',
|
||||
name: 'Button default',
|
||||
x: 50,
|
||||
y: 50,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#1f1f1f',
|
||||
children: [
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: -40,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#f44336'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: -20,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#ff9800'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#ffc106'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: 20,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#cddc39'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: 40,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#4caf50'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'component',
|
||||
name: 'Button primary',
|
||||
x: 50,
|
||||
y: 50,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#f0f0f0',
|
||||
children: [
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: -40,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#f44336'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: -20,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#ff9800'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#ffc106'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: 20,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#cddc39'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: 40,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#4caf50'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
modules: [],
|
||||
pages: []
|
||||
};
|
||||
|
||||
export default data;
|
||||
91
packages/design/dev/data/components/button.ts
Normal file
91
packages/design/dev/data/components/button.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { createUUID } from '@idraw/util';
|
||||
import type { DesignComponent, DesignComponentItem } from '../../../src';
|
||||
|
||||
function createButtonItem(variantName: string) {
|
||||
const componentItem: DesignComponentItem = {
|
||||
uuid: createUUID(),
|
||||
type: 'component-item',
|
||||
name: `Button ${variantName}`,
|
||||
x: 50,
|
||||
y: 50,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
children: [
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: -40,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#f44336'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: -20,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#ff9800'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#ffc106'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: 20,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#cddc39'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'circle',
|
||||
x: 40,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#4caf50'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
return componentItem;
|
||||
}
|
||||
|
||||
export function createButton(name: string) {
|
||||
const button: DesignComponent = {
|
||||
uuid: createUUID(),
|
||||
type: 'component',
|
||||
name: `Button ${name}`,
|
||||
x: 50,
|
||||
y: 50,
|
||||
w: 800,
|
||||
h: 400,
|
||||
desc: {
|
||||
default: createButtonItem('default'),
|
||||
variants: [createButtonItem('primary'), createButtonItem('secondary')]
|
||||
}
|
||||
};
|
||||
return button;
|
||||
}
|
||||
91
packages/design/dev/data/components/checkbox.ts
Normal file
91
packages/design/dev/data/components/checkbox.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { createUUID } from '@idraw/util';
|
||||
import type { DesignComponent, DesignComponentItem } from '../../../src';
|
||||
|
||||
function createCheckboxItem(variantName: string) {
|
||||
const componentItem: DesignComponentItem = {
|
||||
uuid: createUUID(),
|
||||
type: 'component-item',
|
||||
name: `Checkbox ${variantName}`,
|
||||
x: 50,
|
||||
y: 50,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
children: [
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'rect',
|
||||
x: -40,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#f44336'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'rect',
|
||||
x: -20,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#ff9800'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'rect',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#ffc106'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'rect',
|
||||
x: 20,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#cddc39'
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: createUUID(),
|
||||
type: 'rect',
|
||||
x: 40,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
desc: {
|
||||
bgColor: '#4caf50'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
return componentItem;
|
||||
}
|
||||
|
||||
export function createCheckbox(name: string) {
|
||||
const checkbox: DesignComponent = {
|
||||
uuid: createUUID(),
|
||||
type: 'component',
|
||||
name: `Checkbox ${name}`,
|
||||
x: 50,
|
||||
y: 50,
|
||||
w: 800,
|
||||
h: 400,
|
||||
desc: {
|
||||
default: createCheckboxItem('default'),
|
||||
variants: [createCheckboxItem('primary'), createCheckboxItem('secondary')]
|
||||
}
|
||||
};
|
||||
return checkbox;
|
||||
}
|
||||
11
packages/design/dev/data/index.ts
Normal file
11
packages/design/dev/data/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import type { DesignData } from '../../src';
|
||||
import { createButton } from './components/button';
|
||||
import { createCheckbox } from './components/checkbox';
|
||||
|
||||
const data: DesignData = {
|
||||
components: [createButton('001'), createButton('002'), createCheckbox('001'), createCheckbox('002')],
|
||||
modules: [],
|
||||
pages: []
|
||||
};
|
||||
|
||||
export default data;
|
||||
|
|
@ -7,19 +7,19 @@ const dom = document.querySelector('#lab') as HTMLDivElement;
|
|||
const root = createRoot(dom);
|
||||
|
||||
const App = () => {
|
||||
// const style = { margin: 0, padding: 0 }
|
||||
// const [width, setWidth] = useState<number>(window.innerWidth);
|
||||
// const [height, setHeight] = useState<number>(window.innerHeight);
|
||||
// useEffect(() => {
|
||||
// window.addEventListener('resize', () => {
|
||||
// setWidth(window.innerWidth);
|
||||
// setHeight(window.innerHeight);
|
||||
// });
|
||||
// }, []);
|
||||
const style = { margin: 0, padding: 0 };
|
||||
const [width, setWidth] = useState<number>(window.innerWidth);
|
||||
const [height, setHeight] = useState<number>(window.innerHeight);
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
setWidth(window.innerWidth);
|
||||
setHeight(window.innerHeight);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const style = { margin: 40 };
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
// const style = { margin: 40 };
|
||||
// const width = 800;
|
||||
// const height = 600;
|
||||
|
||||
return <Design width={width} height={height} style={style} designData={data} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,28 +1,6 @@
|
|||
import { createContext } from 'react';
|
||||
import type { Dispatch } from 'react';
|
||||
import type { Data } from '@idraw/types';
|
||||
import { DesignData } from './types';
|
||||
|
||||
export interface DesignState {
|
||||
designData: DesignData | null;
|
||||
viewDrawData: Data | null;
|
||||
viewDrawUUID: string | null;
|
||||
themeMode: 'light' | 'dark';
|
||||
}
|
||||
|
||||
export type DesignActionType = 'updateThemeMode' | 'updateDesignData';
|
||||
|
||||
export type DesignAction = {
|
||||
type: DesignActionType;
|
||||
payload: Partial<DesignState>;
|
||||
};
|
||||
|
||||
export type DesignDispatch = Dispatch<DesignAction>;
|
||||
|
||||
export interface DesignContext {
|
||||
state?: DesignState;
|
||||
dispatch?: DesignDispatch;
|
||||
}
|
||||
import { DesignData, DesignState, DesignAction, DesignContext } from './types';
|
||||
import { parseComponentsToDrawData } from './util/view-data';
|
||||
|
||||
export function createDesignData(): DesignData {
|
||||
return {
|
||||
|
|
@ -32,6 +10,16 @@ export function createDesignData(): DesignData {
|
|||
};
|
||||
}
|
||||
|
||||
export function createDesignContextState(opts?: Partial<DesignState>): DesignState {
|
||||
return {
|
||||
designData: opts?.designData || createDesignData(),
|
||||
activeDrawDataType: 'component', // TODO
|
||||
themeMode: opts?.themeMode || 'light',
|
||||
viewDrawData: null,
|
||||
viewDrawUUID: null
|
||||
};
|
||||
}
|
||||
|
||||
export function createDesignReducer(state: DesignState, action: DesignAction): DesignState {
|
||||
switch (action.type) {
|
||||
case 'updateThemeMode': {
|
||||
|
|
@ -52,24 +40,40 @@ export function createDesignReducer(state: DesignState, action: DesignAction): D
|
|||
return {
|
||||
...state,
|
||||
...{
|
||||
data: action?.payload?.designData
|
||||
designData: action?.payload?.designData
|
||||
}
|
||||
};
|
||||
}
|
||||
case 'switchDrawDataType': {
|
||||
if (!action?.payload?.activeDrawDataType) {
|
||||
return state;
|
||||
}
|
||||
let newState = {
|
||||
...state,
|
||||
...{
|
||||
activeDrawDataType: action?.payload.activeDrawDataType
|
||||
}
|
||||
};
|
||||
if (action.payload.activeDrawDataType === 'component') {
|
||||
newState = {
|
||||
...newState,
|
||||
viewDrawData: parseComponentsToDrawData(state.designData?.components || [])
|
||||
};
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export function createDesignContextState(opts?: Partial<DesignState>): DesignState {
|
||||
return {
|
||||
designData: opts?.designData || createDesignData(),
|
||||
themeMode: opts?.themeMode || 'light',
|
||||
viewDrawData: null,
|
||||
viewDrawUUID: null
|
||||
};
|
||||
}
|
||||
|
||||
export const Context = createContext<DesignContext>({});
|
||||
export const Context = createContext<DesignContext>({
|
||||
state: createDesignContextState(),
|
||||
dispatch: () => {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
export const Provider = Context.Provider;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
@import "./theme/dark.less";
|
||||
@import "./theme/light.less";
|
||||
@import './theme/dark.less';
|
||||
@import './theme/light.less';
|
||||
|
||||
@import "./icons/index.less";
|
||||
@import './icons/index.less';
|
||||
|
||||
@import "./modules/header.less";
|
||||
@import "./modules/sketch.less";
|
||||
@import "./modules/toolbar.less";
|
||||
@import "./modules/panel-layer.less";
|
||||
@import './modules/header.less';
|
||||
@import './modules/sketch.less';
|
||||
@import './modules/toolbar.less';
|
||||
@import './modules/panel-layer.less';
|
||||
@import './modules/split-pane.less';
|
||||
|
|
|
|||
|
|
@ -10,28 +10,18 @@
|
|||
|
||||
.@{mod-panel-layer}-header {
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
// border-bottom: 1px solid;
|
||||
// border-color: ~'var(--@{prefix}-border-color)';
|
||||
}
|
||||
|
||||
.@{mod-panel-layer}-content {
|
||||
flex: 1;
|
||||
// display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.@{mod-panel-layer}-footer {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.@{mod-panel-layer}-tabs {
|
||||
// width: 100%;
|
||||
// display: flex;
|
||||
// padding-bottom: 100px;
|
||||
|
||||
.@{mod-panel-layer}-tab-title {
|
||||
font-size: 16;
|
||||
margin: 0 4px;
|
||||
}
|
||||
overflow: auto;
|
||||
|
||||
// fix antd style
|
||||
.ant-tree-switcher {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
@ -44,4 +34,28 @@
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.@{mod-panel-layer}-footer {
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid;
|
||||
border-color: ~'var(--@{prefix}-border-color)';
|
||||
}
|
||||
|
||||
.@{mod-panel-layer}-tabs {
|
||||
// width: 100%;
|
||||
// display: flex;
|
||||
// padding-bottom: 100px;
|
||||
|
||||
.@{mod-panel-layer}-tab-title {
|
||||
font-size: 16;
|
||||
margin: 0 4px;
|
||||
}
|
||||
.@{mod-panel-layer}-tab-content {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,4 +47,19 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.@{mod-sketch}-left {
|
||||
border-right: 1px solid;
|
||||
border-color: ~'var(--@{prefix}-border-color)';
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.@{mod-sketch}-right {
|
||||
border-left: 1px solid;
|
||||
border-color: ~'var(--@{prefix}-border-color)';
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.@{mod-sketch}-center {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
packages/design/src/css/modules/split-pane.less
Normal file
55
packages/design/src/css/modules/split-pane.less
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// @import '../variable.less';
|
||||
|
||||
@mod-split-pane: ~'@{prefix}-mod-split-pane';
|
||||
|
||||
.@{mod-split-pane} {
|
||||
// background: #000;
|
||||
opacity: 0.2;
|
||||
z-index: 1;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-background-clip: padding;
|
||||
-webkit-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
|
||||
&:hover {
|
||||
-webkit-transition: all 2s ease;
|
||||
transition: all 2s ease;
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
height: 11px;
|
||||
margin: -5px 0;
|
||||
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: row-resize;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.horizontal:hover {
|
||||
border-top: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
width: 11px;
|
||||
margin: 0 -6px;
|
||||
border-left: 5px solid rgba(255, 255, 255, 0);
|
||||
border-right: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
&.vertical:hover {
|
||||
border-left: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-right: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.disabled:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +1,28 @@
|
|||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Tabs from 'antd/es/tabs';
|
||||
import type { TabsProps } from 'antd';
|
||||
import { prefixName } from './config';
|
||||
import { LayerTree } from './layer-tree';
|
||||
import FileOutlined from '@ant-design/icons/FileOutlined';
|
||||
import AppstoreOutlined from '@ant-design/icons/AppstoreOutlined';
|
||||
import CalculatorOutlined from '@ant-design/icons/CalculatorOutlined';
|
||||
import { prefixName } from './config';
|
||||
import { LayerTree } from './layer-tree';
|
||||
import { Context } from '../../context';
|
||||
import { DesignDrawDataType } from '../../types';
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: 'page',
|
||||
label: <FileOutlined className={prefixName('tab', 'title')} />,
|
||||
children: (
|
||||
<div style={{ width: '100%', overflow: 'auto' }}>
|
||||
<LayerTree type="page" />
|
||||
</div>
|
||||
)
|
||||
label: <FileOutlined className={prefixName('tab', 'title')} />
|
||||
},
|
||||
{
|
||||
key: 'module',
|
||||
label: <AppstoreOutlined className={prefixName('tab', 'title')} />,
|
||||
children: (
|
||||
<div style={{ width: '100%', overflow: 'auto' }}>
|
||||
<LayerTree type="module" />
|
||||
</div>
|
||||
)
|
||||
label: <AppstoreOutlined className={prefixName('tab', 'title')} />
|
||||
},
|
||||
{
|
||||
key: 'component',
|
||||
label: <CalculatorOutlined className={prefixName('tab', 'title')} />,
|
||||
children: (
|
||||
<div style={{ width: '100%', overflow: 'auto' }}>
|
||||
<LayerTree type="component" />
|
||||
</div>
|
||||
)
|
||||
label: <CalculatorOutlined className={prefixName('tab', 'title')} />
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -46,15 +33,27 @@ export interface PanelLayerProps {
|
|||
|
||||
export const PanelLayer = (props: PanelLayerProps) => {
|
||||
const { className, style } = props;
|
||||
const { state, dispatch } = useContext(Context);
|
||||
|
||||
const defaultTabKey = items[2].key;
|
||||
return (
|
||||
<div style={style} className={classnames(prefixName(), className)}>
|
||||
{/* <div className={prefixName('header')}>header</div> */}
|
||||
<div className={prefixName('content')}>
|
||||
<Tabs className={prefixName('tabs')} defaultActiveKey={defaultTabKey} centered items={items} size="small" />
|
||||
<div className={prefixName('header')}>
|
||||
<Tabs
|
||||
className={prefixName('tabs')}
|
||||
tabBarStyle={{ marginBottom: 0 }}
|
||||
activeKey={state?.activeDrawDataType as string}
|
||||
centered
|
||||
items={items}
|
||||
size="small"
|
||||
onTabClick={(activeKey: string) => {
|
||||
dispatch({ type: 'switchDrawDataType', payload: { activeDrawDataType: activeKey as DesignDrawDataType } });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className={prefixName('footer')}>footer</div> */}
|
||||
<div className={prefixName('content')}>
|
||||
<LayerTree type={state.activeDrawDataType} />
|
||||
</div>
|
||||
<div className={prefixName('footer')}>footer</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { parseComponentViewTree } from '../../util/component';
|
|||
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { DataNode, TreeProps } from 'antd/es/tree';
|
||||
import type { DesignItemType } from '../../types';
|
||||
import type { DesignDrawDataType } from '../../types';
|
||||
|
||||
const { DirectoryTree } = Tree;
|
||||
const baseName = 'layer-tree';
|
||||
|
|
@ -16,7 +16,7 @@ const baseName = 'layer-tree';
|
|||
export interface LayerTreeProps {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
type: DesignItemType;
|
||||
type: DesignDrawDataType;
|
||||
}
|
||||
|
||||
export const LayerTree = (props: LayerTreeProps) => {
|
||||
|
|
@ -24,18 +24,18 @@ export const LayerTree = (props: LayerTreeProps) => {
|
|||
const { state } = useContext(Context);
|
||||
|
||||
const onSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
|
||||
// TODO
|
||||
console.log('selected', selectedKeys, info);
|
||||
};
|
||||
|
||||
let treeData: DataNode[] = [];
|
||||
|
||||
if (type === 'component') {
|
||||
treeData = parseComponentViewTree(state?.designData || null);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={style} className={classnames(prefixName(baseName), className)}>
|
||||
<DirectoryTree showLine blockNode switcherIcon={<DownOutlined />} icon={null} defaultExpandedKeys={['0-0-0']} onSelect={onSelect} treeData={treeData} />
|
||||
<DirectoryTree showLine blockNode switcherIcon={<DownOutlined />} icon={null} onSelect={onSelect} treeData={treeData} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React, { useEffect, useRef, useState } from 'react';
|
|||
import classnames from 'classnames';
|
||||
import { Core, MiddlewareScroller, MiddlewareSelector } from '@idraw/core';
|
||||
import { calcElementsContextSize } from '@idraw/util';
|
||||
import Drawer from 'antd/es/drawer';
|
||||
import { getData } from '../../data';
|
||||
import { Toolbar } from '../toolbar';
|
||||
import { PanelLayer } from '../panel-layer';
|
||||
|
|
@ -10,9 +9,11 @@ import { Header } from '../header';
|
|||
import type { CSSProperties } from 'react';
|
||||
import { createPrefixName } from '../../css';
|
||||
import { HEADER_HEIGHT } from './layout';
|
||||
import SplitPane from '../split-pane';
|
||||
|
||||
const modName = 'mod-sketch';
|
||||
const siderWidth = 200;
|
||||
const leftSiderDefaultWidth = 240;
|
||||
const rightSiderDefaultWidth = 200;
|
||||
|
||||
const prefixName = createPrefixName(modName);
|
||||
|
||||
|
|
@ -26,8 +27,6 @@ export interface SketchProps {
|
|||
export const Sketch = (props: SketchProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const refCore = useRef<Core | null>(null);
|
||||
const refLeftDOM = useRef<HTMLDivElement | null>(null);
|
||||
const refRighttDOM = useRef<HTMLDivElement | null>(null);
|
||||
const { className, style, width, height } = props;
|
||||
const data = getData();
|
||||
const devicePixelRatio = window.devicePixelRatio;
|
||||
|
|
@ -35,11 +34,15 @@ export const Sketch = (props: SketchProps) => {
|
|||
const [openLeftSider, setOpenLeftSider] = useState<boolean>(true);
|
||||
const [openRightSider, setOpenRightSider] = useState<boolean>(false);
|
||||
|
||||
const [leftWidth, setLeftWidth] = useState<number>(openLeftSider ? leftSiderDefaultWidth : 0);
|
||||
const [rightWidth, setRightWidth] = useState<number>(openRightSider ? rightSiderDefaultWidth : 0);
|
||||
const [centerWidth, setCenterWidth] = useState<number>(width - leftWidth - rightWidth);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref?.current) {
|
||||
if (!refCore?.current) {
|
||||
const options = {
|
||||
width,
|
||||
width: centerWidth,
|
||||
height: height - HEADER_HEIGHT,
|
||||
devicePixelRatio
|
||||
};
|
||||
|
|
@ -52,90 +55,98 @@ export const Sketch = (props: SketchProps) => {
|
|||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const prevWidth = leftWidth + centerWidth + rightWidth;
|
||||
let newLeftWidth = Math.floor(width * (leftWidth / prevWidth));
|
||||
let newRightWidth = Math.floor(width * (rightWidth / prevWidth));
|
||||
|
||||
newLeftWidth = Math.min(newLeftWidth, leftSiderDefaultWidth);
|
||||
newRightWidth = Math.min(newRightWidth, rightSiderDefaultWidth);
|
||||
|
||||
const newCenterWidth = width - newLeftWidth - newRightWidth;
|
||||
setLeftWidth(newLeftWidth);
|
||||
setRightWidth(newRightWidth);
|
||||
setCenterWidth(newCenterWidth);
|
||||
}, [height, width]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!refCore?.current) {
|
||||
return;
|
||||
}
|
||||
const core = refCore.current;
|
||||
|
||||
const contextSize = calcElementsContextSize(data.elements, { viewWidth: width, viewHeight: height });
|
||||
core.resize({
|
||||
width,
|
||||
width: centerWidth,
|
||||
height: height - HEADER_HEIGHT,
|
||||
devicePixelRatio,
|
||||
...contextSize
|
||||
});
|
||||
}, [height, width]);
|
||||
}, [height, centerWidth]);
|
||||
|
||||
return (
|
||||
<div className={classnames(prefixName(), className)} style={{ ...style, ...{ width, height, padding: 0 } }}>
|
||||
<div className={prefixName('header')} style={{ height: HEADER_HEIGHT }}>
|
||||
<Header />
|
||||
</div>
|
||||
<div ref={ref} className={prefixName('content')} style={{ top: HEADER_HEIGHT }}></div>
|
||||
<div ref={refLeftDOM}></div>
|
||||
<div ref={refRighttDOM}></div>
|
||||
<div className={prefixName('content')} style={{ top: HEADER_HEIGHT }}>
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
defaultSize={centerWidth + rightWidth}
|
||||
allowResize
|
||||
onChange={(px: number) => {
|
||||
setCenterWidth(px - rightWidth);
|
||||
setLeftWidth(width - px);
|
||||
}}
|
||||
pane1Style={{
|
||||
width: leftWidth
|
||||
}}
|
||||
pane2Style={{
|
||||
width: centerWidth + rightWidth
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<PanelLayer className={prefixName('left')} />
|
||||
</div>
|
||||
<div style={{ width: centerWidth + rightWidth, display: 'flex', flexDirection: 'row' }}>
|
||||
<div ref={ref} className={prefixName('center')} style={{ width: centerWidth, height: height - HEADER_HEIGHT }}></div>
|
||||
<div className={prefixName('right')} style={{ width: rightWidth, height: height - HEADER_HEIGHT }}>
|
||||
Right
|
||||
</div>
|
||||
</div>
|
||||
</SplitPane>
|
||||
</div>
|
||||
<Toolbar
|
||||
className={prefixName('toolbar-position')}
|
||||
openLeftSider={openLeftSider}
|
||||
openRightSider={openRightSider}
|
||||
onClickToggleLayer={() => {
|
||||
setOpenLeftSider(openLeftSider ? false : true);
|
||||
const open = openLeftSider ? false : true;
|
||||
|
||||
let newLeftWidth = leftWidth;
|
||||
if (open) {
|
||||
newLeftWidth = leftSiderDefaultWidth;
|
||||
} else {
|
||||
newLeftWidth = 0;
|
||||
}
|
||||
setLeftWidth(newLeftWidth);
|
||||
setCenterWidth(width - newLeftWidth - rightWidth);
|
||||
setRightWidth(rightWidth);
|
||||
setOpenLeftSider(open);
|
||||
}}
|
||||
onClickToggleSetting={() => {
|
||||
setOpenRightSider(openRightSider ? false : true);
|
||||
const open = openRightSider ? false : true;
|
||||
let newRightWidth = rightWidth;
|
||||
if (open) {
|
||||
newRightWidth = rightSiderDefaultWidth;
|
||||
} else {
|
||||
newRightWidth = 0;
|
||||
}
|
||||
setLeftWidth(leftWidth);
|
||||
setCenterWidth(width - leftWidth - newRightWidth);
|
||||
setRightWidth(newRightWidth);
|
||||
setOpenRightSider(open);
|
||||
}}
|
||||
/>
|
||||
<Drawer
|
||||
// title="left Drawer"
|
||||
placement="left"
|
||||
closable={false}
|
||||
onClose={() => {
|
||||
console.log('on close left');
|
||||
setOpenLeftSider(false);
|
||||
}}
|
||||
mask={false}
|
||||
open={openLeftSider}
|
||||
getContainer={() => {
|
||||
return refLeftDOM.current as HTMLDivElement;
|
||||
}}
|
||||
width={siderWidth}
|
||||
bodyStyle={{
|
||||
padding: 0
|
||||
}}
|
||||
rootStyle={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
}}
|
||||
>
|
||||
<PanelLayer />
|
||||
</Drawer>
|
||||
<Drawer
|
||||
title="right Drawer"
|
||||
placement="right"
|
||||
onClose={() => {
|
||||
console.log('on close right');
|
||||
setOpenRightSider(false);
|
||||
}}
|
||||
mask={false}
|
||||
open={openRightSider}
|
||||
getContainer={() => {
|
||||
return refRighttDOM.current as HTMLDivElement;
|
||||
}}
|
||||
width={siderWidth}
|
||||
rootStyle={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
}}
|
||||
>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
6
packages/design/src/modules/split-pane/index.tsx
Normal file
6
packages/design/src/modules/split-pane/index.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Thanks to: https://github.com/tomkp/react-split-pane/blob/master/src/index.js
|
||||
import SplitPane from './split-pane';
|
||||
import Pane from './pane';
|
||||
|
||||
export default SplitPane;
|
||||
export { Pane };
|
||||
49
packages/design/src/modules/split-pane/pane.tsx
Normal file
49
packages/design/src/modules/split-pane/pane.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Thanks to: https://github.com/tomkp/react-split-pane/blob/master/src/Pane.js
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
class Pane extends React.PureComponent {
|
||||
render() {
|
||||
const { children, className, split, style: styleProps, size, eleRef } = this.props;
|
||||
|
||||
const classes = ['Pane', split, className];
|
||||
|
||||
let style = {
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
outline: 'none'
|
||||
};
|
||||
|
||||
if (size !== undefined) {
|
||||
if (split === 'vertical') {
|
||||
style.width = size;
|
||||
} else {
|
||||
style.height = size;
|
||||
style.display = 'flex';
|
||||
}
|
||||
style.flex = 'none';
|
||||
}
|
||||
|
||||
style = Object.assign({}, style, styleProps || {});
|
||||
|
||||
return (
|
||||
<div ref={eleRef} className={classes.join(' ')} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Pane.propTypes = {
|
||||
// className: PropTypes.string.isRequired,
|
||||
// children: PropTypes.node.isRequired,
|
||||
// size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
// split: PropTypes.oneOf(['vertical', 'horizontal']),
|
||||
// style: stylePropType,
|
||||
// eleRef: PropTypes.func,
|
||||
// };
|
||||
|
||||
// Pane.defaultProps = {};
|
||||
|
||||
export default Pane;
|
||||
64
packages/design/src/modules/split-pane/resizer.tsx
Normal file
64
packages/design/src/modules/split-pane/resizer.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Thanks to: https://github.com/tomkp/react-split-pane/blob/master/src/Resizer.js
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { createPrefixName } from '../../css';
|
||||
|
||||
const modName = 'mod-split-pane';
|
||||
|
||||
const prefixName = createPrefixName(modName);
|
||||
|
||||
export const RESIZER_DEFAULT_CLASSNAME = prefixName();
|
||||
|
||||
class Resizer extends React.Component {
|
||||
render() {
|
||||
const { className, onClick, onDoubleClick, onMouseDown, onTouchEnd, onTouchStart, resizerClassName = RESIZER_DEFAULT_CLASSNAME, split, style } = this.props;
|
||||
const classes = [resizerClassName, split, className];
|
||||
|
||||
return (
|
||||
<span
|
||||
role="presentation"
|
||||
className={classes.join(' ')}
|
||||
style={style}
|
||||
onMouseDown={(event) => onMouseDown(event)}
|
||||
onTouchStart={(event) => {
|
||||
event.preventDefault();
|
||||
onTouchStart(event);
|
||||
}}
|
||||
onTouchEnd={(event) => {
|
||||
event.preventDefault();
|
||||
onTouchEnd(event);
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (onClick) {
|
||||
event.preventDefault();
|
||||
onClick(event);
|
||||
}
|
||||
}}
|
||||
onDoubleClick={(event) => {
|
||||
if (onDoubleClick) {
|
||||
event.preventDefault();
|
||||
onDoubleClick(event);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Resizer.propTypes = {
|
||||
// className: PropTypes.string.isRequired,
|
||||
// onClick: PropTypes.func,
|
||||
// onDoubleClick: PropTypes.func,
|
||||
// onMouseDown: PropTypes.func.isRequired,
|
||||
// onTouchStart: PropTypes.func.isRequired,
|
||||
// onTouchEnd: PropTypes.func.isRequired,
|
||||
// split: PropTypes.oneOf(['vertical', 'horizontal']),
|
||||
// style: stylePropType,
|
||||
// resizerClassName: PropTypes.string.isRequired,
|
||||
// };
|
||||
// Resizer.defaultProps = {
|
||||
// resizerClassName: RESIZER_DEFAULT_CLASSNAME,
|
||||
// };
|
||||
|
||||
export default Resizer;
|
||||
374
packages/design/src/modules/split-pane/split-pane.tsx
Normal file
374
packages/design/src/modules/split-pane/split-pane.tsx
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
// Thanks to: https://github.com/tomkp/react-split-pane/blob/master/src/SplitPane.js
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
// import PropTypes from 'prop-types';
|
||||
// import stylePropType from 'react-style-proptype';
|
||||
// import { polyfill } from 'react-lifecycles-compat';
|
||||
|
||||
import Pane from './pane';
|
||||
import Resizer, { RESIZER_DEFAULT_CLASSNAME } from './resizer';
|
||||
|
||||
function unFocus(document, window) {
|
||||
if (document.selection) {
|
||||
document.selection.empty();
|
||||
} else {
|
||||
try {
|
||||
window.getSelection().removeAllRanges();
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultSize(defaultSize, minSize, maxSize, draggedSize) {
|
||||
if (typeof draggedSize === 'number') {
|
||||
const min = typeof minSize === 'number' ? minSize : 0;
|
||||
const max = typeof maxSize === 'number' && maxSize >= 0 ? maxSize : Infinity;
|
||||
return Math.max(min, Math.min(max, draggedSize));
|
||||
}
|
||||
if (defaultSize !== undefined) {
|
||||
return defaultSize;
|
||||
}
|
||||
return minSize;
|
||||
}
|
||||
|
||||
function removeNullChildren(children) {
|
||||
return React.Children.toArray(children).filter((c) => c);
|
||||
}
|
||||
class SplitPane extends React.Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onMouseDown = this.onMouseDown.bind(this);
|
||||
this.onTouchStart = this.onTouchStart.bind(this);
|
||||
this.onMouseMove = this.onMouseMove.bind(this);
|
||||
this.onTouchMove = this.onTouchMove.bind(this);
|
||||
this.onMouseUp = this.onMouseUp.bind(this);
|
||||
|
||||
// order of setting panel sizes.
|
||||
// 1. size
|
||||
// 2. getDefaultSize(defaultSize, minsize, maxSize)
|
||||
|
||||
const { size, defaultSize, minSize, maxSize, primary } = props;
|
||||
|
||||
const initialSize = size !== undefined ? size : getDefaultSize(defaultSize, minSize, maxSize, null);
|
||||
|
||||
this.state = {
|
||||
active: false,
|
||||
resized: false,
|
||||
pane1Size: primary === 'first' ? initialSize : undefined,
|
||||
pane2Size: primary === 'second' ? initialSize : undefined,
|
||||
|
||||
// these are props that are needed in static functions. ie: gDSFP
|
||||
instanceProps: {
|
||||
size
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('mouseup', this.onMouseUp);
|
||||
document.addEventListener('mousemove', this.onMouseMove);
|
||||
document.addEventListener('touchmove', this.onTouchMove);
|
||||
this.setState(SplitPane.getSizeUpdate(this.props, this.state));
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
return SplitPane.getSizeUpdate(nextProps, prevState);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('mouseup', this.onMouseUp);
|
||||
document.removeEventListener('mousemove', this.onMouseMove);
|
||||
document.removeEventListener('touchmove', this.onTouchMove);
|
||||
}
|
||||
|
||||
onMouseDown(event) {
|
||||
const eventWithTouches = Object.assign({}, event, {
|
||||
touches: [{ clientX: event.clientX, clientY: event.clientY }]
|
||||
});
|
||||
this.onTouchStart(eventWithTouches);
|
||||
}
|
||||
|
||||
onTouchStart(event) {
|
||||
const { allowResize, onDragStarted, split } = this.props;
|
||||
if (allowResize) {
|
||||
unFocus(document, window);
|
||||
const position = split === 'vertical' ? event.touches[0].clientX : event.touches[0].clientY;
|
||||
|
||||
if (typeof onDragStarted === 'function') {
|
||||
onDragStarted();
|
||||
}
|
||||
this.setState({
|
||||
active: true,
|
||||
position
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove(event) {
|
||||
const eventWithTouches = Object.assign({}, event, {
|
||||
touches: [{ clientX: event.clientX, clientY: event.clientY }]
|
||||
});
|
||||
this.onTouchMove(eventWithTouches);
|
||||
}
|
||||
|
||||
onTouchMove(event) {
|
||||
const { allowResize, maxSize, minSize, onChange, split, step } = this.props;
|
||||
const { active, position } = this.state;
|
||||
|
||||
if (allowResize && active) {
|
||||
unFocus(document, window);
|
||||
const isPrimaryFirst = this.props.primary === 'first';
|
||||
const ref = isPrimaryFirst ? this.pane1 : this.pane2;
|
||||
const ref2 = isPrimaryFirst ? this.pane2 : this.pane1;
|
||||
if (ref) {
|
||||
const node = ref;
|
||||
const node2 = ref2;
|
||||
|
||||
if (node.getBoundingClientRect) {
|
||||
const width = node.getBoundingClientRect().width;
|
||||
const height = node.getBoundingClientRect().height;
|
||||
const current = split === 'vertical' ? event.touches[0].clientX : event.touches[0].clientY;
|
||||
const size = split === 'vertical' ? width : height;
|
||||
let positionDelta = position - current;
|
||||
if (step) {
|
||||
if (Math.abs(positionDelta) < step) {
|
||||
return;
|
||||
}
|
||||
// Integer division
|
||||
// eslint-disable-next-line no-bitwise
|
||||
positionDelta = ~~(positionDelta / step) * step;
|
||||
}
|
||||
let sizeDelta = isPrimaryFirst ? positionDelta : -positionDelta;
|
||||
|
||||
const pane1Order = parseInt(window.getComputedStyle(node).order);
|
||||
const pane2Order = parseInt(window.getComputedStyle(node2).order);
|
||||
if (pane1Order > pane2Order) {
|
||||
sizeDelta = -sizeDelta;
|
||||
}
|
||||
|
||||
let newMaxSize = maxSize;
|
||||
if (maxSize !== undefined && maxSize <= 0) {
|
||||
const splitPane = this.splitPane;
|
||||
if (split === 'vertical') {
|
||||
newMaxSize = splitPane.getBoundingClientRect().width + maxSize;
|
||||
} else {
|
||||
newMaxSize = splitPane.getBoundingClientRect().height + maxSize;
|
||||
}
|
||||
}
|
||||
|
||||
let newSize = size - sizeDelta;
|
||||
const newPosition = position - positionDelta;
|
||||
|
||||
if (newSize < minSize) {
|
||||
newSize = minSize;
|
||||
} else if (maxSize !== undefined && newSize > newMaxSize) {
|
||||
newSize = newMaxSize;
|
||||
} else {
|
||||
this.setState({
|
||||
position: newPosition,
|
||||
resized: true
|
||||
});
|
||||
}
|
||||
|
||||
if (onChange) onChange(newSize);
|
||||
|
||||
this.setState({
|
||||
draggedSize: newSize,
|
||||
[isPrimaryFirst ? 'pane1Size' : 'pane2Size']: newSize
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
const { allowResize, onDragFinished } = this.props;
|
||||
const { active, draggedSize } = this.state;
|
||||
if (allowResize && active) {
|
||||
if (typeof onDragFinished === 'function') {
|
||||
onDragFinished(draggedSize);
|
||||
}
|
||||
this.setState({ active: false });
|
||||
}
|
||||
}
|
||||
|
||||
// we have to check values since gDSFP is called on every render and more in StrictMode
|
||||
static getSizeUpdate(props, state) {
|
||||
const newState = {};
|
||||
const { instanceProps } = state;
|
||||
|
||||
if (instanceProps.size === props.size && props.size !== undefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const newSize = props.size !== undefined ? props.size : getDefaultSize(props.defaultSize, props.minSize, props.maxSize, state.draggedSize);
|
||||
|
||||
if (props.size !== undefined) {
|
||||
newState.draggedSize = newSize;
|
||||
}
|
||||
|
||||
const isPanel1Primary = props.primary === 'first';
|
||||
|
||||
newState[isPanel1Primary ? 'pane1Size' : 'pane2Size'] = newSize;
|
||||
newState[isPanel1Primary ? 'pane2Size' : 'pane1Size'] = undefined;
|
||||
|
||||
newState.instanceProps = { size: props.size };
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
allowResize,
|
||||
children,
|
||||
className,
|
||||
onResizerClick,
|
||||
onResizerDoubleClick,
|
||||
paneClassName,
|
||||
pane1ClassName,
|
||||
pane2ClassName,
|
||||
paneStyle,
|
||||
pane1Style: pane1StyleProps,
|
||||
pane2Style: pane2StyleProps,
|
||||
resizerClassName,
|
||||
resizerStyle,
|
||||
split,
|
||||
style: styleProps
|
||||
} = this.props;
|
||||
|
||||
const { pane1Size, pane2Size } = this.state;
|
||||
|
||||
const disabledClass = allowResize ? '' : 'disabled';
|
||||
const resizerClassNamesIncludingDefault = resizerClassName ? `${resizerClassName} ${RESIZER_DEFAULT_CLASSNAME}` : resizerClassName;
|
||||
|
||||
const notNullChildren = removeNullChildren(children);
|
||||
|
||||
const style = {
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
outline: 'none',
|
||||
overflow: 'hidden',
|
||||
MozUserSelect: 'text',
|
||||
WebkitUserSelect: 'text',
|
||||
msUserSelect: 'text',
|
||||
userSelect: 'text',
|
||||
...styleProps
|
||||
};
|
||||
|
||||
if (split === 'vertical') {
|
||||
Object.assign(style, {
|
||||
flexDirection: 'row',
|
||||
left: 0,
|
||||
right: 0
|
||||
});
|
||||
} else {
|
||||
Object.assign(style, {
|
||||
bottom: 0,
|
||||
flexDirection: 'column',
|
||||
minHeight: '100%',
|
||||
top: 0,
|
||||
width: '100%'
|
||||
});
|
||||
}
|
||||
|
||||
const classes = ['SplitPane', className, split, disabledClass];
|
||||
|
||||
const pane1Style = { ...paneStyle, ...pane1StyleProps };
|
||||
const pane2Style = { ...paneStyle, ...pane2StyleProps };
|
||||
|
||||
const pane1Classes = ['Pane1', paneClassName, pane1ClassName].join(' ');
|
||||
const pane2Classes = ['Pane2', paneClassName, pane2ClassName].join(' ');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes.join(' ')}
|
||||
ref={(node) => {
|
||||
this.splitPane = node;
|
||||
}}
|
||||
style={style}
|
||||
>
|
||||
<Pane
|
||||
className={pane1Classes}
|
||||
key="pane1"
|
||||
eleRef={(node) => {
|
||||
this.pane1 = node;
|
||||
}}
|
||||
size={pane1Size}
|
||||
split={split}
|
||||
style={pane1Style}
|
||||
>
|
||||
{notNullChildren[0]}
|
||||
</Pane>
|
||||
<Resizer
|
||||
className={disabledClass}
|
||||
onClick={onResizerClick}
|
||||
onDoubleClick={onResizerDoubleClick}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onTouchStart={this.onTouchStart}
|
||||
onTouchEnd={this.onMouseUp}
|
||||
key="resizer"
|
||||
resizerClassName={resizerClassNamesIncludingDefault}
|
||||
split={split}
|
||||
style={resizerStyle || {}}
|
||||
/>
|
||||
<Pane
|
||||
className={pane2Classes}
|
||||
key="pane2"
|
||||
eleRef={(node) => {
|
||||
this.pane2 = node;
|
||||
}}
|
||||
size={pane2Size}
|
||||
split={split}
|
||||
style={pane2Style}
|
||||
>
|
||||
{notNullChildren[1]}
|
||||
</Pane>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// SplitPane.propTypes = {
|
||||
// allowResize: PropTypes.bool,
|
||||
// children: PropTypes.arrayOf(PropTypes.node).isRequired,
|
||||
// className: PropTypes.string,
|
||||
// primary: PropTypes.oneOf(['first', 'second']),
|
||||
// minSize: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
// maxSize: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
// defaultSize: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
// size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
// split: PropTypes.oneOf(['vertical', 'horizontal']),
|
||||
// onDragStarted: PropTypes.func,
|
||||
// onDragFinished: PropTypes.func,
|
||||
// onChange: PropTypes.func,
|
||||
// onResizerClick: PropTypes.func,
|
||||
// onResizerDoubleClick: PropTypes.func,
|
||||
// style: stylePropType,
|
||||
// resizerStyle: stylePropType,
|
||||
// paneClassName: PropTypes.string,
|
||||
// pane1ClassName: PropTypes.string,
|
||||
// pane2ClassName: PropTypes.string,
|
||||
// paneStyle: stylePropType,
|
||||
// pane1Style: stylePropType,
|
||||
// pane2Style: stylePropType,
|
||||
// resizerClassName: PropTypes.string,
|
||||
// step: PropTypes.number
|
||||
// };
|
||||
|
||||
// SplitPane.defaultProps = {
|
||||
// allowResize: true,
|
||||
// minSize: 50,
|
||||
// primary: 'first',
|
||||
// split: 'vertical',
|
||||
// paneClassName: '',
|
||||
// pane1ClassName: '',
|
||||
// pane2ClassName: ''
|
||||
// };
|
||||
|
||||
// polyfill(SplitPane);
|
||||
|
||||
export default SplitPane;
|
||||
25
packages/design/src/types/context.ts
Normal file
25
packages/design/src/types/context.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import type { Dispatch } from 'react';
|
||||
import type { Data } from '@idraw/types';
|
||||
import { DesignData, DesignDrawDataType } from './data';
|
||||
|
||||
export interface DesignState {
|
||||
activeDrawDataType: DesignDrawDataType;
|
||||
designData: DesignData | null;
|
||||
viewDrawData: Data | null;
|
||||
viewDrawUUID: string | null;
|
||||
themeMode: 'light' | 'dark';
|
||||
}
|
||||
|
||||
export type DesignActionType = 'updateThemeMode' | 'updateDesignData' | 'switchDrawDataType';
|
||||
|
||||
export type DesignAction = {
|
||||
type: DesignActionType;
|
||||
payload: Partial<DesignState>;
|
||||
};
|
||||
|
||||
export type DesignDispatch = Dispatch<DesignAction>;
|
||||
|
||||
export interface DesignContext {
|
||||
state: DesignState;
|
||||
dispatch: DesignDispatch;
|
||||
}
|
||||
|
|
@ -1,13 +1,23 @@
|
|||
import type { Element, ElementType, ElementSize, ElementBaseDesc } from '@idraw/types';
|
||||
|
||||
export type DesignItemType = 'component' | 'module' | 'page';
|
||||
export type DesignItemType = 'component' | 'component-item' | 'module' | 'page';
|
||||
|
||||
export type DesignComponentItem = Omit<ElementSize, 'angle'> & {
|
||||
uuid: string;
|
||||
type: 'component-item';
|
||||
name: string;
|
||||
desc?: ElementBaseDesc & {
|
||||
children: Array<Element<ElementType> | DesignComponentItem>;
|
||||
};
|
||||
};
|
||||
|
||||
export type DesignComponent = Omit<ElementSize, 'angle'> & {
|
||||
uuid: string;
|
||||
type: 'component';
|
||||
name: string;
|
||||
desc?: ElementBaseDesc & {
|
||||
children: Array<Element<ElementType> | DesignComponent>;
|
||||
default: DesignComponentItem;
|
||||
variants: DesignComponentItem[];
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * from './data';
|
||||
export * from './context';
|
||||
export * from './view';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import type { ElementType } from '@idraw/types';
|
||||
import type { DesignItemType } from './data';
|
||||
|
||||
export interface ViewTreeNode {
|
||||
title: string;
|
||||
key: string;
|
||||
type: DesignItemType | ElementType;
|
||||
children?: ViewTreeNode[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,11 @@
|
|||
import { Data } from '@idraw/types';
|
||||
import { ViewTreeNode, DesignData, DesignComponent } from '../types';
|
||||
import { parseComponentToViewTreeNode } from './view-tree';
|
||||
|
||||
export function parseComponentViewTree(designData: DesignData | null): ViewTreeNode[] {
|
||||
const list: ViewTreeNode[] = [];
|
||||
const treeNodes: ViewTreeNode[] = [];
|
||||
designData?.components?.forEach((comp: DesignComponent) => {
|
||||
const children: ViewTreeNode[] = [];
|
||||
if (Array.isArray(comp?.desc?.children)) {
|
||||
comp?.desc?.children?.forEach((child) => {
|
||||
children.push({
|
||||
key: child.uuid,
|
||||
title: child.name || 'Unamed',
|
||||
children: []
|
||||
});
|
||||
});
|
||||
}
|
||||
list.push({
|
||||
key: comp.uuid,
|
||||
title: comp.name || 'Unamed',
|
||||
children
|
||||
});
|
||||
const node = parseComponentToViewTreeNode(comp);
|
||||
treeNodes.push(node);
|
||||
});
|
||||
return list;
|
||||
return treeNodes;
|
||||
}
|
||||
|
|
|
|||
66
packages/design/src/util/view-data.ts
Normal file
66
packages/design/src/util/view-data.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { deepClone } from '@idraw/util';
|
||||
import type { Data, Element } from '@idraw/types';
|
||||
import type { DesignComponent, DesignComponentItem } from '../types';
|
||||
|
||||
function parseComponentItemToElement(item: DesignComponentItem): Element<'group'> {
|
||||
const elem: Element<'group'> = {
|
||||
uuid: item.uuid,
|
||||
name: item.name,
|
||||
type: 'group',
|
||||
x: item.x,
|
||||
y: item.y,
|
||||
w: item.w,
|
||||
h: item.h,
|
||||
desc: {
|
||||
...item.desc,
|
||||
children: []
|
||||
}
|
||||
};
|
||||
item.desc?.children?.forEach?.((child) => {
|
||||
if (child.type === 'component-item') {
|
||||
const childElem = parseComponentItemToElement(child);
|
||||
elem.desc.children.push(childElem);
|
||||
} else {
|
||||
const childElem = deepClone(child);
|
||||
elem.desc.children.push(childElem);
|
||||
}
|
||||
});
|
||||
return elem;
|
||||
}
|
||||
|
||||
function parseComponentToElement(comp: DesignComponent): Element<'group'> {
|
||||
const elem: Element<'group'> = {
|
||||
uuid: comp.uuid,
|
||||
name: comp.name,
|
||||
type: 'group',
|
||||
x: comp.x,
|
||||
y: comp.y,
|
||||
w: comp.w,
|
||||
h: comp.h,
|
||||
desc: {
|
||||
children: []
|
||||
}
|
||||
};
|
||||
|
||||
if (comp?.desc?.default) {
|
||||
elem.desc.children.push(parseComponentItemToElement(comp.desc.default));
|
||||
}
|
||||
if (comp?.desc?.variants && Array.isArray(comp?.desc?.variants)) {
|
||||
comp.desc.variants.forEach((item) => {
|
||||
elem.desc.children.push(parseComponentItemToElement(item));
|
||||
});
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
export function parseComponentsToDrawData(components: DesignComponent[]): Data {
|
||||
const data: Data = {
|
||||
elements: []
|
||||
};
|
||||
components.forEach((comp: DesignComponent) => {
|
||||
const elem = parseComponentToElement(comp);
|
||||
data.elements.push(elem);
|
||||
});
|
||||
return data;
|
||||
}
|
||||
70
packages/design/src/util/view-tree.ts
Normal file
70
packages/design/src/util/view-tree.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import type { Element, ElementType } from '@idraw/types';
|
||||
import { ViewTreeNode, DesignComponent, DesignComponentItem } from '../types';
|
||||
|
||||
function parseElementToViewTreeNode(elem: Element<ElementType>): ViewTreeNode | null {
|
||||
let treeNode: ViewTreeNode | null = null;
|
||||
if (elem.uuid) {
|
||||
treeNode = {
|
||||
key: elem.uuid,
|
||||
title: elem.name || 'Unamed',
|
||||
type: elem.type,
|
||||
children: []
|
||||
};
|
||||
if (Array.isArray((elem as Element<'group'>)?.desc?.children)) {
|
||||
(elem as Element<'group'>).desc.children.forEach((child: Element<ElementType>) => {
|
||||
const childNode = parseElementToViewTreeNode(child);
|
||||
if (childNode) {
|
||||
treeNode?.children?.push(childNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
function parseComponentItemToViewTreeNode(comp: DesignComponentItem): ViewTreeNode {
|
||||
const treeNode: Required<ViewTreeNode> = {
|
||||
key: comp.uuid,
|
||||
title: comp.name || 'Unamed',
|
||||
type: comp.type,
|
||||
children: []
|
||||
};
|
||||
|
||||
if (comp?.desc?.children && Array.isArray(comp?.desc?.children)) {
|
||||
comp.desc.children.forEach((child) => {
|
||||
let childNode: ViewTreeNode | null = null;
|
||||
if (child.type === 'component') {
|
||||
childNode = parseComponentToViewTreeNode(child as DesignComponent);
|
||||
} else {
|
||||
childNode = parseElementToViewTreeNode(child as Element<ElementType>);
|
||||
}
|
||||
if (childNode) {
|
||||
treeNode.children.push(childNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
export function parseComponentToViewTreeNode(comp: DesignComponent): ViewTreeNode {
|
||||
const treeNode: Required<ViewTreeNode> = {
|
||||
key: comp.uuid,
|
||||
title: comp.name || 'Unamed',
|
||||
type: comp.type,
|
||||
children: []
|
||||
};
|
||||
|
||||
if (comp?.desc?.default) {
|
||||
const node = parseComponentItemToViewTreeNode(comp.desc.default);
|
||||
treeNode.children.push(node);
|
||||
}
|
||||
|
||||
if (Array.isArray(comp?.desc?.variants)) {
|
||||
comp?.desc?.variants?.forEach((child: DesignComponentItem) => {
|
||||
const node = parseComponentItemToViewTreeNode(child);
|
||||
treeNode.children.push(node);
|
||||
});
|
||||
}
|
||||
|
||||
return treeNode;
|
||||
}
|
||||
Loading…
Reference in a new issue