mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-05 05:18:34 +08:00
Merge changes I36dcfaa2,I0d1645bd into theme/editor-20230201
* changes: amis-saas-10220 外观源码编辑器 amis-saas-10220 外观源码编辑器
This commit is contained in:
commit
61ab64fd00
@ -367,7 +367,14 @@ export class ButtonPlugin extends BasePlugin {
|
||||
...buttonStateFunc("${editorState == 'active'}", 'active')
|
||||
]
|
||||
},
|
||||
getSchemaTpl('theme:cssCode')
|
||||
getSchemaTpl('theme:cssCode', {
|
||||
themeClass: [
|
||||
{
|
||||
value: '',
|
||||
state: ['default', 'hover', 'active']
|
||||
}
|
||||
]
|
||||
})
|
||||
])
|
||||
},
|
||||
{
|
||||
|
@ -282,7 +282,17 @@ export class NumberControlPlugin extends BasePlugin {
|
||||
)
|
||||
]
|
||||
},
|
||||
getSchemaTpl('theme:cssCode', {isFormItem: true})
|
||||
getSchemaTpl('theme:cssCode', {
|
||||
themeClass: [
|
||||
{
|
||||
name: '数字输入框',
|
||||
value: '',
|
||||
className: 'inputControlClassName',
|
||||
state: ['default', 'hover', 'active']
|
||||
}
|
||||
],
|
||||
isFormItem: true
|
||||
})
|
||||
],
|
||||
{...context?.schema, configTitle: 'style'}
|
||||
)
|
||||
|
@ -383,7 +383,19 @@ export class TextControlPlugin extends BasePlugin {
|
||||
]
|
||||
},
|
||||
getSchemaTpl('theme:cssCode', {
|
||||
themeClass: ['addOn'],
|
||||
themeClass: [
|
||||
{
|
||||
name: '输入框',
|
||||
value: '',
|
||||
className: 'inputControlClassName',
|
||||
state: ['default', 'hover', 'active']
|
||||
},
|
||||
{
|
||||
name: 'addOn',
|
||||
value: 'addOn',
|
||||
className: 'addOnClassName'
|
||||
}
|
||||
],
|
||||
isFormItem: true
|
||||
})
|
||||
],
|
||||
|
@ -274,30 +274,7 @@ export class PagePlugin extends BasePlugin {
|
||||
className: 'p-none',
|
||||
body: [
|
||||
getSchemaTpl('collapseGroup', [
|
||||
...getSchemaTpl('theme:common', ['layout']),
|
||||
getSchemaTpl('style:classNames', {
|
||||
isFormItem: false,
|
||||
schema: [
|
||||
getSchemaTpl('className', {
|
||||
name: 'headerClassName',
|
||||
label: '顶部'
|
||||
}),
|
||||
getSchemaTpl('className', {
|
||||
name: 'bodyClassName',
|
||||
label: '内容区'
|
||||
}),
|
||||
|
||||
getSchemaTpl('className', {
|
||||
name: 'asideClassName',
|
||||
label: '边栏'
|
||||
}),
|
||||
|
||||
getSchemaTpl('className', {
|
||||
name: 'toolbarClassName',
|
||||
label: '工具栏'
|
||||
})
|
||||
]
|
||||
})
|
||||
...getSchemaTpl('theme:common', ['layout'])
|
||||
])
|
||||
]
|
||||
},
|
||||
|
@ -3,11 +3,13 @@
|
||||
*/
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {Button, Editor, Overlay, PopOver} from 'amis-ui';
|
||||
import {FormControlProps, FormItem} from 'amis-core';
|
||||
import {FormControlProps, FormItem, uuid} from 'amis-core';
|
||||
import {parse as cssParse} from 'amis-postcss';
|
||||
import {PlainObject} from './types';
|
||||
import {debounce} from 'lodash';
|
||||
import {cloneDeep, debounce, isEmpty} from 'lodash';
|
||||
import {Icon} from '../../icons/index';
|
||||
import editorFactory from './themeLanguage';
|
||||
import cx from 'classnames';
|
||||
|
||||
const valueMap: PlainObject = {
|
||||
'margin-top': 'marginTop',
|
||||
@ -47,35 +49,67 @@ const fontStyle = [
|
||||
'line-height'
|
||||
];
|
||||
|
||||
function AmisStyleCodeEditor(props: FormControlProps) {
|
||||
interface CssNode {
|
||||
value: string;
|
||||
selector: string;
|
||||
}
|
||||
|
||||
interface CssNodeTab {
|
||||
name: string;
|
||||
children: CssNode[];
|
||||
}
|
||||
|
||||
function AmisThemeCssCodeEditor(props: FormControlProps) {
|
||||
const {themeClass, data} = props;
|
||||
const id = data.id.replace('u:', '');
|
||||
const [value, setValue] = useState('');
|
||||
function getCssAndSetValue(themeClass: string[]) {
|
||||
const [cssNodes, setCssNodes] = useState<CssNodeTab[]>([]);
|
||||
const [tabId, setTabId] = useState(0);
|
||||
function getCssAndSetValue(themeClass: any[]) {
|
||||
try {
|
||||
const nodes: any[] = [];
|
||||
const ids = themeClass.map(n => (n ? id + '-' + n : id));
|
||||
ids?.forEach(id => {
|
||||
const dom = document.getElementById(id || '') || null;
|
||||
const newCssNodes: CssNodeTab[] = [];
|
||||
themeClass?.forEach(n => {
|
||||
const classId = n.value ? id + '-' + n.value : id;
|
||||
const state = n.state || ['default'];
|
||||
const className = n.className || 'className';
|
||||
const dom = document.getElementById(classId || '') || null;
|
||||
const content = dom?.innerHTML || '';
|
||||
const ast = cssParse(content);
|
||||
|
||||
const nodes: any[] = [];
|
||||
ast.nodes.forEach((node: any) => {
|
||||
const selector = node.selector;
|
||||
if (!selector.endsWith('.hover') && !selector.endsWith('.active')) {
|
||||
nodes.push(node);
|
||||
}
|
||||
});
|
||||
ast.nodes = nodes;
|
||||
});
|
||||
|
||||
const css = nodes
|
||||
.map(node => {
|
||||
const css: {selector: string; value: string; state: string}[] = [];
|
||||
state.forEach((s: string) => {
|
||||
css.push({
|
||||
selector: `.${className}-${id}${s === 'default' ? '' : ':' + s}`,
|
||||
state: s,
|
||||
value: ''
|
||||
});
|
||||
});
|
||||
nodes.forEach(node => {
|
||||
const style = node.nodes.map((n: any) => `${n.prop}: ${n.value};`);
|
||||
return `${node.selector} {\n ${style.join('\n ')}\n}`;
|
||||
})
|
||||
.join('\n\n');
|
||||
setValue(css);
|
||||
const item = css.find(c => {
|
||||
if (
|
||||
c.selector === node.selector ||
|
||||
node.selector.endsWith(`:${c.state}`)
|
||||
) {
|
||||
return c;
|
||||
}
|
||||
return false;
|
||||
})!;
|
||||
item.value = style.join('\n');
|
||||
});
|
||||
|
||||
newCssNodes.push({
|
||||
name: n.name || '自定义样式',
|
||||
children: css
|
||||
});
|
||||
});
|
||||
setCssNodes(newCssNodes);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@ -85,53 +119,70 @@ function AmisStyleCodeEditor(props: FormControlProps) {
|
||||
getCssAndSetValue(themeClass);
|
||||
}, []);
|
||||
|
||||
const editorChange = debounce((value: string) => {
|
||||
const editorChange = debounce((nodeTabs: CssNodeTab[]) => {
|
||||
try {
|
||||
const ast = cssParse(value);
|
||||
const {data, onBulkChange} = props;
|
||||
const sourceCss = data.themeCss || data.css || {};
|
||||
|
||||
const newCss: any = {};
|
||||
ast.nodes.forEach((node: any) => {
|
||||
const nodes = node.nodes;
|
||||
const selector = node.selector;
|
||||
const nameEtr = /\.(.*)\-/.exec(selector);
|
||||
const cssCode: PlainObject = {};
|
||||
let name = nameEtr ? nameEtr[1] : '';
|
||||
let state = 'default';
|
||||
if (!!~selector.indexOf(':hover:active')) {
|
||||
state = 'active';
|
||||
} else if (!!~selector.indexOf(':hover')) {
|
||||
state = 'hover';
|
||||
}
|
||||
nodes.forEach((item: any) => {
|
||||
const prop = item.prop;
|
||||
const cssValue = item.value;
|
||||
if (!!~prop.indexOf('radius')) {
|
||||
const type = 'radius:' + state;
|
||||
!cssCode[type] && (cssCode[type] = {});
|
||||
const radius = cssValue.split(' ');
|
||||
nodeTabs.forEach(tab => {
|
||||
tab.children.forEach(node => {
|
||||
const nodes = node.value
|
||||
.replace(/\s/g, '')
|
||||
.split(';')
|
||||
.map(kv => {
|
||||
const [prop, value] = kv.split(':');
|
||||
return {
|
||||
prop,
|
||||
value
|
||||
};
|
||||
})
|
||||
.filter(n => n.value);
|
||||
const selector = node.selector;
|
||||
const nameEtr = /\.(.*)\-/.exec(selector);
|
||||
const cssCode: PlainObject = {};
|
||||
let name = nameEtr ? nameEtr[1] : '';
|
||||
let state = 'default';
|
||||
if (!!~selector.indexOf(':active')) {
|
||||
state = 'active';
|
||||
} else if (!!~selector.indexOf(':hover')) {
|
||||
state = 'hover';
|
||||
}
|
||||
nodes.forEach(item => {
|
||||
const prop = item.prop;
|
||||
const cssValue = item.value;
|
||||
if (!!~prop.indexOf('radius')) {
|
||||
const type = 'radius:' + state;
|
||||
!cssCode[type] && (cssCode[type] = {});
|
||||
const radius = cssValue.split(' ');
|
||||
|
||||
cssCode[type]['top-left-border-radius'] = radius[0];
|
||||
cssCode[type]['top-right-border-radius'] = radius[1];
|
||||
cssCode[type]['bottom-right-border-radius'] = radius[2];
|
||||
cssCode[type]['bottom-left-border-radius'] = radius[3];
|
||||
} else if (!!~prop.indexOf('border')) {
|
||||
!cssCode['border:' + state] && (cssCode['border:' + state] = {});
|
||||
cssCode['border:' + state][valueMap[prop] || prop] = cssValue;
|
||||
} else if (!!~prop.indexOf('padding') || !!~prop.indexOf('margin')) {
|
||||
!cssCode['padding-and-margin:' + state] &&
|
||||
(cssCode['padding-and-margin:' + state] = {});
|
||||
cssCode['padding-and-margin:' + state][valueMap[prop] || prop] =
|
||||
cssValue;
|
||||
} else if (fontStyle.includes(prop)) {
|
||||
!cssCode['font:' + state] && (cssCode['font:' + state] = {});
|
||||
cssCode['font:' + state][valueMap[prop] || prop] = cssValue;
|
||||
cssCode[type]['top-left-border-radius'] = radius[0];
|
||||
cssCode[type]['top-right-border-radius'] = radius[1];
|
||||
cssCode[type]['bottom-right-border-radius'] = radius[2];
|
||||
cssCode[type]['bottom-left-border-radius'] = radius[3];
|
||||
} else if (!!~prop.indexOf('border')) {
|
||||
!cssCode['border:' + state] && (cssCode['border:' + state] = {});
|
||||
cssCode['border:' + state][valueMap[prop] || prop] = cssValue;
|
||||
} else if (
|
||||
!!~prop.indexOf('padding') ||
|
||||
!!~prop.indexOf('margin')
|
||||
) {
|
||||
!cssCode['padding-and-margin:' + state] &&
|
||||
(cssCode['padding-and-margin:' + state] = {});
|
||||
cssCode['padding-and-margin:' + state][valueMap[prop] || prop] =
|
||||
cssValue;
|
||||
} else if (fontStyle.includes(prop)) {
|
||||
!cssCode['font:' + state] && (cssCode['font:' + state] = {});
|
||||
cssCode['font:' + state][valueMap[prop] || prop] = cssValue;
|
||||
} else {
|
||||
cssCode[(valueMap[prop] || prop) + ':' + state] = cssValue;
|
||||
}
|
||||
});
|
||||
if (newCss[name]) {
|
||||
newCss[name] = Object.assign(newCss[name], cssCode);
|
||||
} else {
|
||||
cssCode[(valueMap[prop] || prop) + ':' + state] = cssValue;
|
||||
newCss[name] = cssCode;
|
||||
}
|
||||
});
|
||||
newCss[name] = cssCode;
|
||||
});
|
||||
onBulkChange &&
|
||||
onBulkChange({
|
||||
@ -145,9 +196,21 @@ function AmisStyleCodeEditor(props: FormControlProps) {
|
||||
}
|
||||
});
|
||||
|
||||
function handleChange(value: string) {
|
||||
setValue(value);
|
||||
editorChange(value);
|
||||
function handleChange(value: string, i: number, j: number) {
|
||||
const newCssNodes = cssNodes;
|
||||
newCssNodes[i].children[j].value = value;
|
||||
setCssNodes(newCssNodes); // 好像不需要这个?
|
||||
editorChange(newCssNodes);
|
||||
}
|
||||
function formateTitle(title: string) {
|
||||
if (title.endsWith('hover')) {
|
||||
return '悬浮态样式';
|
||||
} else if (title.endsWith('active')) {
|
||||
return '点击态样式';
|
||||
} else if (title.endsWith('disabled')) {
|
||||
return '禁用态样式';
|
||||
}
|
||||
return '常规态样式';
|
||||
}
|
||||
|
||||
return (
|
||||
@ -158,25 +221,124 @@ function AmisStyleCodeEditor(props: FormControlProps) {
|
||||
<Icon icon="close" className="icon" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="ThemeCssCode-editor-content">
|
||||
<div className="ThemeCssCode-editor-content-header">
|
||||
{cssNodes.map((node, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => setTabId(index)}
|
||||
className={cx(
|
||||
'ThemeCssCode-editor-content-header-title',
|
||||
index === tabId &&
|
||||
'ThemeCssCode-editor-content-header-title--active'
|
||||
)}
|
||||
>
|
||||
{node.name}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="ThemeCssCode-editor-content-main">
|
||||
{cssNodes.map((node, i) => {
|
||||
const children = node.children;
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={cx(
|
||||
i !== tabId && 'ThemeCssCode-editor-content-body--hidden'
|
||||
)}
|
||||
>
|
||||
{children.map((css, j) => {
|
||||
return (
|
||||
<div
|
||||
className="ThemeCssCode-editor-content-body"
|
||||
key={`${i}-${j}-${css.selector}`}
|
||||
id={`${i}-${j}-${css.selector}`}
|
||||
>
|
||||
{children.length > 1 ? (
|
||||
<div className="ThemeCssCode-editor-content-body-title">
|
||||
{formateTitle(css.selector)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="ThemeCssCode-editor-content-body-editor">
|
||||
<Editor
|
||||
value={css.value}
|
||||
editorFactory={editorFactory}
|
||||
options={{
|
||||
onChange: (value: string) =>
|
||||
handleChange(value, i, j)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AmisStyleCodeEditor(props: FormControlProps) {
|
||||
const {data, onBulkChange} = props;
|
||||
const {style} = data;
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
function getCssAndSetValue(data: any) {
|
||||
if (isEmpty(data)) {
|
||||
return '';
|
||||
}
|
||||
let str = '';
|
||||
for (let key in data) {
|
||||
str += `${key}: ${data[key]};\n`;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const res = getCssAndSetValue(style);
|
||||
setValue(res);
|
||||
}, []);
|
||||
|
||||
const editorChange = debounce((value: string) => {
|
||||
const newStyle: PlainObject = {};
|
||||
value
|
||||
.replace(/\s/g, '')
|
||||
.split(';')
|
||||
.forEach(kv => {
|
||||
const [prop, value] = kv.split(':');
|
||||
if (value) {
|
||||
newStyle[prop] = value;
|
||||
}
|
||||
});
|
||||
onBulkChange &&
|
||||
onBulkChange({
|
||||
style: newStyle
|
||||
});
|
||||
});
|
||||
|
||||
function handleChange(value: string) {
|
||||
editorChange(value);
|
||||
setValue(value);
|
||||
}
|
||||
return (
|
||||
<div className="ThemeCssCode-editor">
|
||||
<div className="ThemeCssCode-editor-title">编辑样式源码</div>
|
||||
<div className="ThemeCssCode-editor-close">
|
||||
<Button onClick={props.onHide} level="link">
|
||||
<Icon icon="close" className="icon" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="ThemeCssCode-editor-content">
|
||||
<Editor
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
editorFactory={editorFactory}
|
||||
options={{
|
||||
automaticLayout: true,
|
||||
lineNumbers: 'off',
|
||||
glyphMargin: false,
|
||||
tabSize: 2,
|
||||
wordWrap: 'on',
|
||||
lineDecorationsWidth: 0,
|
||||
lineNumbersMinChars: 0,
|
||||
selectOnLineNumbers: true,
|
||||
scrollBeyondLastLine: false,
|
||||
folding: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
onChange: handleChange
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@ -186,7 +348,6 @@ function AmisStyleCodeEditor(props: FormControlProps) {
|
||||
|
||||
function ThemeCssCode(props: FormControlProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const {value} = props;
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
function handleShowEditor() {
|
||||
setShowEditor(true);
|
||||
@ -206,7 +367,17 @@ function ThemeCssCode(props: FormControlProps) {
|
||||
rootClose={false}
|
||||
>
|
||||
<PopOver overlay onHide={() => setShowEditor(false)}>
|
||||
<AmisStyleCodeEditor {...props} onHide={() => setShowEditor(false)} />
|
||||
{props.isLayout ? (
|
||||
<AmisStyleCodeEditor
|
||||
{...props}
|
||||
onHide={() => setShowEditor(false)}
|
||||
/>
|
||||
) : (
|
||||
<AmisThemeCssCodeEditor
|
||||
{...props}
|
||||
onHide={() => setShowEditor(false)}
|
||||
/>
|
||||
)}
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
</>
|
||||
|
3150
packages/amis-editor/src/renderer/style-control/themeLanguage.ts
Normal file
3150
packages/amis-editor/src/renderer/style-control/themeLanguage.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -430,15 +430,23 @@ setSchemaTpl(
|
||||
'theme:cssCode',
|
||||
({
|
||||
themeClass = [],
|
||||
isFormItem
|
||||
isFormItem,
|
||||
isLayout
|
||||
}: {
|
||||
themeClass?: string[];
|
||||
themeClass?: any[];
|
||||
isFormItem?: boolean;
|
||||
} = {}) => {
|
||||
console.log(themeClass);
|
||||
themeClass.push('');
|
||||
if (isFormItem) {
|
||||
themeClass.push(...['description', 'label']);
|
||||
themeClass.push(
|
||||
...[
|
||||
{
|
||||
name: 'description',
|
||||
value: 'description',
|
||||
className: 'descriptionClassName'
|
||||
},
|
||||
{name: 'label', value: 'label', className: 'labelClassName'}
|
||||
]
|
||||
);
|
||||
}
|
||||
return {
|
||||
title: '样式源码',
|
||||
@ -581,8 +589,7 @@ setSchemaTpl(
|
||||
].filter(comp => !~exclude.indexOf(comp.type.replace(/^style-/i, '')))
|
||||
},
|
||||
{
|
||||
header: '样式',
|
||||
key: 'style',
|
||||
title: '自定义样式',
|
||||
body: [
|
||||
getSchemaTpl('theme:border', {
|
||||
name: 'style'
|
||||
@ -606,24 +613,14 @@ setSchemaTpl(
|
||||
]
|
||||
},
|
||||
{
|
||||
header: '圆角',
|
||||
key: 'radius',
|
||||
body: []
|
||||
},
|
||||
{
|
||||
header: '间距',
|
||||
key: 'box-model',
|
||||
body: []
|
||||
},
|
||||
{
|
||||
header: '背景',
|
||||
key: 'background',
|
||||
body: []
|
||||
},
|
||||
{
|
||||
header: '阴影',
|
||||
key: 'box-shadow',
|
||||
body: []
|
||||
title: '样式源码',
|
||||
body: [
|
||||
{
|
||||
type: 'theme-cssCode',
|
||||
label: false,
|
||||
isLayout: true
|
||||
}
|
||||
]
|
||||
}
|
||||
].filter(item =>
|
||||
include.length ? ~include.indexOf(item.key) : !~exclude.indexOf(item.key)
|
||||
|
Loading…
Reference in New Issue
Block a user