Merge branch 'pre-release' into amis-saas-9193

This commit is contained in:
jinye 2023-02-16 14:58:34 +08:00
commit db0ff4f3e8
22 changed files with 901 additions and 66 deletions

View File

@ -1,10 +1,28 @@
{
"name": "amis-editor-core",
"version": "5.2.4-beta.2",
"version": "5.2.4-beta.32",
"description": "amis 可视化编辑器",
"main": "lib/index.js",
"module": "esm/index.js",
"types": "lib/index.d.ts",
"exports": {
".": {
"require": "./lib/index.js",
"import": "./esm/index.js"
},
"./lib/*": {
"require": "./lib/*",
"import": "./esm/*"
},
"./scss/*": {
"require": "./scss/*",
"import": "./scss/*"
},
"./*": {
"require": "./lib/*.js",
"import": "./esm/*.js"
}
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "npm run clean-dist && NODE_ENV=production rollup -c",

View File

@ -0,0 +1,57 @@
// 状态组件页面设计器
.ae-Status-label {
& > span {
display: flex;
align-items: center;
}
&-tip-icon {
margin-left: 0.25rem;
line-height: 16px;
.icon {
width: 16px;
height: 16px;
fill: #84868c;
}
}
}
.ae-Status-default-icon {
&-tooltip {
max-width: none;
}
&-tip {
font-size: 12px;
}
}
.ae-Status-control {
.cxd-Combo-itemInner {
.cxd-Form-row {
margin-right: 0;
.cxd-Form-col {
padding-left: 2px;
padding-right: 2px;
&:first-child {
flex-grow: 0;
min-width: 55px;
.cxd-IconSelectControl-input {
padding-left: 6px;
}
.cxd-IconSelectControl-clear {
right: 4px;
}
}
&:last-child {
flex-grow: 0;
}
.cxd-IconSelectControl-input-icon-id {
display: none;
}
.Theme-ColorPicker-label-out {
width: 1.35rem;
height: 1.35rem;
position: relative;
top: 0.2rem;
}
}
}
}
}

View File

@ -48,6 +48,7 @@
&-input-label {
flex: 1;
margin-right: var(--gap-sm);
margin-bottom: 0;
}
&-input-value {
margin-right: var(--gap-sm);

View File

@ -35,6 +35,7 @@
@import './control/timeline_item_control';
@import './control/tree_option_control';
@import './control/_inpupt-file';
@import './control/_status';
/* 样式控件 */
@import './style-control/box-model';
@ -482,41 +483,94 @@
background-color: #fff;
transform: translate(-50%, -50%);
position: absolute;
z-index: 100;
pointer-events: all;
display: none;
}
.ae-WResizer {
left: 100%;
top: 50%;
cursor: ew-resize;
&[data-value]::before {
position: absolute;
content: '';
pointer-events: none;
right: 8px;
top: 50%;
transform: translateY(-50%);
width: 0;
margin: 0;
padding: 0;
box-sizing: content-box;
border: 6px solid rgba(7, 12, 20, 0.85);
border-color: transparent transparent transparent rgba(7, 12, 20, 0.85);
z-index: 100;
}
&[data-value]:after {
position: absolute;
content: attr(data-value);
background-color: $Editor-theme;
right: 15px;
top: 50%;
transform: translate(0, -50%);
padding: 2px 8px;
color: #fff;
text-align: center;
background-color: rgba(7,12,20,0.85);
// background-color: $Editor-theme;
box-shadow: 0 2px 8px 0 rgba(7,12,20,0.12);
}
}
.ae-border-WResizer,
.ae-border-HResizer {
position: absolute;
top: 0px;
right: -2px;
width: 2px;
height: 100%;
border-right: 2px solid $Editor-theme;
background-color: #fff;
z-index: 99;
pointer-events: all;
cursor: ew-resize;
display: none;
}
.ae-border-HResizer {
top: auto;
bottom: -2px;
right: 0px;
border-right: none;
border-bottom: 2px solid $Editor-theme;
width: 100%;
height: 2px;
cursor: ns-resize !important;
}
.ae-WResizer {
left: calc(100% + 1px);
top: 50%;
cursor: ew-resize;
&[data-value]:after {
right: 20px;
top: 50%;
transform: translate(0, -50%);
}
}
.ae-HResizer {
left: 50%;
top: 100%;
top: calc(100% + 1px);
cursor: ns-resize;
&[data-value]::before {
right: 50%;
top: auto;
bottom: 8px;
transform: translate(50%, 0);
border-color: rgba(7, 12, 20, 0.85) transparent transparent transparent;
}
&[data-value]:after {
position: absolute;
content: attr(data-value);
background-color: $Editor-theme;
bottom: 15px;
bottom: 20px;
left: 50%;
transform: translate(-50%, 0);
padding: 2px 8px;
color: #fff;
}
}
@ -525,15 +579,13 @@
top: 100%;
cursor: nwse-resize;
&[data-value]::before {
border-color: transparent;
}
&[data-value]:after {
position: absolute;
content: attr(data-value);
background-color: $Editor-theme;
bottom: 15px;
right: 15px;
padding: 2px 8px;
color: #fff;
text-align: center;
}
}
@ -564,7 +616,9 @@
.ae-WResizer,
.ae-HResizer,
.ae-Resizer {
.ae-Resizer,
.ae-border-WResizer,
.ae-border-HResizer {
display: block;
}
}
@ -574,6 +628,17 @@
z-index: 5;
}
&.isFreeContainerElem {
// 自由容器的直接子元素拖拽剔除过渡
transition: none !important;
&.selected {
pointer-events: all; // 高亮区启用鼠标事件支持拖拽
cursor: move;
}
}
.ae-Editor-toolbar {
display: none;
background: $Editor-hlbox-bg;
@ -1111,6 +1176,14 @@
background: #f7f7f8;
}
&.fill-placeholder {
position: absolute;
top: 4px;
left: 4px;
width: calc(100% - 8px) !important;
height: calc(100% - 8px) !important;
}
&:first-child {
display: flex;
align-items: center;
@ -1362,7 +1435,7 @@ div.ae-DragImage {
white-space: nowrap;
width: 50px;
height: 30px;
z-index: 100000;
z-index: -10;
> span {
position: absolute;

View File

@ -40,7 +40,6 @@
}
}
.Theme-ShadowEditor {
margin-left: -0.5rem;
.Theme-Wrapper-header-left {
font-weight: 400;
color: #5c5f66;

View File

@ -17,6 +17,7 @@ import {PopOverForm} from './PopOverForm';
import {ContextMenuPanel} from './Panel/ContextMenuPanel';
import {LeftPanels} from './Panel/LeftPanels';
import {RightPanels} from './Panel/RightPanels';
import type {VariableGroup, VariableOptions} from '../variable';
export interface EditorProps extends PluginEventListener {
value: SchemaObject;
@ -98,6 +99,11 @@ export interface EditorProps extends PluginEventListener {
};
};
/** 上下文变量 */
variables?: VariableGroup[];
/** 变量配置 */
variableOptions?: VariableOptions;
onUndo?: () => void; // 用于触发外部 undo 事件
onRedo?: () => void; // 用于触发外部 redo 事件
onSave?: () => void; // 用于触发外部 save 事件
@ -139,7 +145,10 @@ export default class Editor extends Component<EditorProps> {
isSubEditor,
amisDocHost: props.amisDocHost,
ctx: props.ctx,
superEditorData
superEditorData,
appLocale: props.appLocale,
appCorpusData: props?.amisEnv?.replaceText,
i18nEnabled: props?.i18nEnabled ?? false
},
config
);
@ -149,7 +158,10 @@ export default class Editor extends Component<EditorProps> {
}
this.manager = new EditorManager(config, this.store);
(window as any).editorStore = this.store;
// 子编辑器不再重新设置 editorStore
if (!(props.isSubEditor && (window as any).editorStore)) {
(window as any).editorStore = this.store;
}
// 添加快捷键事件
document.addEventListener('keydown', this.handleKeyDown);
@ -212,6 +224,14 @@ export default class Editor extends Component<EditorProps> {
if (props.ctx !== prevProps.ctx) {
this.store.setCtx(props.ctx);
}
if (props.appLocale !== prevProps.appLocale) {
this.store.setAppLocale(props.appLocale);
}
if (props?.amisEnv?.replaceText !== prevProps?.amisEnv?.replaceText) {
this.store.setAppCorpusData(props?.amisEnv?.replaceText);
}
}
componentWillUnmount() {
@ -543,6 +563,8 @@ export default class Editor extends Component<EditorProps> {
store={this.store}
manager={this.manager}
theme={theme}
appLocale={appLocale}
amisEnv={amisEnv}
/>
)}

View File

@ -48,7 +48,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
if (!isLeftButton || e.defaultPrevented) return;
e.preventDefault();
const {manager, id, node} = this.props;
const {manager, id, node, store} = this.props;
if (!node) {
return;
}
@ -69,6 +69,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
](e, {
dom: target as HTMLElement,
node: node,
store: store,
resizer:
direction === 'both'
? this.resizerDom
@ -136,7 +137,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
if (ref) {
ref.addEventListener('mousedown', this.handleWResizerMouseDown);
} else {
this.wResizerDom.addEventListener(
this.wResizerDom?.addEventListener(
'mousedown',
this.handleWResizerMouseDown
);
@ -152,7 +153,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
if (ref) {
ref.addEventListener('mousedown', this.handleHResizerMouseDown);
} else {
this.hResizerDom.addEventListener(
this.hResizerDom?.addEventListener(
'mousedown',
this.handleHResizerMouseDown
);
@ -168,7 +169,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
if (ref) {
ref.addEventListener('mousedown', this.handleResizerMouseDown);
} else {
this.resizerDom.addEventListener(
this.resizerDom?.addEventListener(
'mousedown',
this.handleResizerMouseDown
);
@ -188,6 +189,18 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
this.props.store.setHoverId(this.props.id);
}
// 特殊布局元素和自由容器直接子元素直接拖拽调整位置
@autobind
handleDragStart(e: React.DragEvent) {
const {manager, id} = this.props;
if (manager.disableHover) {
return;
}
manager.startDrag(id, e);
}
// @autobind
// handleMouseLeave() {
// this.props.store.setHoverId(this.props.id);
@ -208,8 +221,10 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
const secondaryToolbars = store.sortedSecondaryToolbars;
const specialToolbars = store.sortedSpecialToolbars;
const isActive = store.isActive(id);
const curFreeContainerId = store.parentIsFreeContainer();
const isHover =
store.isHoved(id) || store.dropId === id || store.insertOrigId === id;
store.isHoved(id) || store.dropId === id || store.insertOrigId === id || curFreeContainerId === id;
const isDraggableContainer = store.draggableContainer(id);
// 获取当前高亮画布宽度
const aePreviewOffsetWidth = document.getElementById(
@ -228,7 +243,8 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
hover: isHover,
regionOn: node.childRegions.some(region =>
store.isRegionHighlighted(region.id, region.region)
)
),
isFreeContainerElem: !!curFreeContainerId || isDraggableContainer
},
className
)}
@ -242,6 +258,8 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
}}
ref={this.mainRef}
onMouseEnter={this.handleMouseEnter}
draggable={!!curFreeContainerId || isDraggableContainer}
onDragStart={this.handleDragStart}
>
{isActive ? (
<div
@ -352,11 +370,17 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
{children}
{node.widthMutable ? (
<span className="ae-WResizer" ref={this.wResizerRef}></span>
<>
<span className="ae-border-WResizer" ref={this.wResizerRef}></span>
<span className="ae-WResizer" ref={this.wResizerRef}></span>
</>
) : null}
{node.heightMutable ? (
<span className="ae-HResizer" ref={this.hResizerRef}></span>
<>
<span className="ae-border-HResizer" ref={this.hResizerRef}></span>
<span className="ae-HResizer" ref={this.hResizerRef}></span>
</>
) : null}
{node.widthMutable && node.heightMutable ? (

View File

@ -48,10 +48,20 @@ export default class RenderersPanel extends React.Component<
manager.switchToRegion(region);
}
handleDragStart(e: React.DragEvent) {
@autobind
handleDragStart(e: React.DragEvent, label: string) {
const current = e.currentTarget;
const id = current.getAttribute('data-id')!;
e.dataTransfer.setData(`dnd-dom/[data-id="${id}"]`, '');
/*
// 增加默认拖拽过程中元素
e.dataTransfer!.effectAllowed = 'move';
e.dataTransfer!.setDragImage(
this.props.manager?.dnd?.createDragImage(label),
0,
0
);
*/
}
// 组件点选使用
@ -175,7 +185,7 @@ export default class RenderersPanel extends React.Component<
data-dnd-data={JSON.stringify(
item.scaffold || {type: item.type}
)}
onDragStart={this.handleDragStart}
onDragStart={(e: React.DragEvent) => this.handleDragStart(e, item.name)}
>
<div
className="icon-box"

View File

@ -14,6 +14,8 @@ interface RightPanelsProps {
store: EditorStoreType;
manager: EditorManager;
theme?: string;
appLocale?: string;
amisEnv?: any;
}
interface RightPanelsStates {

View File

@ -3,6 +3,7 @@ import React from 'react';
import {findDOMNode} from 'react-dom';
import {EditorManager} from '../manager';
import {RegionConfig, RendererInfo} from '../plugin';
import {needFillPlaceholder} from '../util';
import {EditorStoreType} from '../store/editor';
import {EditorNodeContext, EditorNodeType} from '../store/node';
@ -105,12 +106,18 @@ export class RegionWrapper extends React.Component<RegionWrapperProps> {
const isLayoutItem =
this.props.rendererName === 'wrapper' ||
this.props.rendererName === 'container';
let isNeedFillPlaceholder = false;
if (needFillPlaceholder(this.props)) {
isNeedFillPlaceholder = true;
}
return (
<EditorNodeContext.Provider value={this.editorNode}>
{this.props.children}
<span
className={`ae-Region-placeholder ${
isLayoutItem ? 'layout-content' : ''
} ${
isNeedFillPlaceholder ? 'fill-placeholder' : ''
}`}
>
{this.props.placeholder || this.props.label}

View File

@ -104,7 +104,7 @@ export class SubEditor extends React.Component<SubEditorProps> {
if (!!subEditorContext) {
superEditorData = createObject(
store.superEditorData,
subEditorContext?.data?.__super
subEditorContext?.data
);
}
return {
@ -157,6 +157,7 @@ export class SubEditor extends React.Component<SubEditorProps> {
appLocale={config.appLocale}
i18nEnabled={config.i18nEnabled}
plugins={config.plugins}
actionOptions={config.actionOptions}
showCustomRenderersPanel={
store.showCustomRenderersPanel ?? true
}

View File

@ -20,6 +20,7 @@ import {Schema} from 'amis';
import type {DataScope} from 'amis-core';
import type {RendererConfig} from 'amis-core/lib/factory';
import {SchemaCollection} from 'amis/lib/Schema';
import {omit} from 'lodash';
// 创建 Node Store 并构建成树
export function makeWrapper(
@ -302,7 +303,7 @@ function SchemaFrom({
popOverContainer
},
{
...env
...omit(env, 'replaceText')
// theme: 'cxd' // 右侧属性配置面板固定使用cxd主题展示
}
);

View File

@ -6,6 +6,7 @@ import findIndex from 'lodash/findIndex';
import {EditorDNDManager} from '.';
import {renderThumbToGhost} from '../component/factory';
import {EditorNodeType} from '../store/node';
import {translateSchema} from '../util';
import {DNDModeInterface} from './interface';
export class DefaultDNDMode implements DNDModeInterface {
@ -56,7 +57,12 @@ export class DefaultDNDMode implements DNDModeInterface {
} else {
const manager = this.dnd.manager;
const store = manager.store;
renderThumbToGhost(ghost, this.region, store.dragSchema, manager);
renderThumbToGhost(
ghost,
this.region,
translateSchema(store.dragSchema),
manager
);
this.dndContainer.appendChild(ghost);
}
}

View File

@ -94,11 +94,11 @@ export class EditorDNDManager {
* @param id
* @param node
*/
createDragImage(id: string, node: EditorNodeType) {
createDragImage(id: string, node?: EditorNodeType) {
const dragImage = document.createElement('div');
dragImage.classList.add('ae-DragImage');
// bca-disable-next-line
dragImage.innerHTML = `<span>${node.label}</span>`;
dragImage.innerHTML = `<span>${node?.label || id}</span>`;
document.body.appendChild(dragImage);
// dragImage.style.cssText += `width: ${node.w}px; height: ${node.h}px;`;
this.dragImage = dragImage;
@ -296,9 +296,15 @@ export class EditorDNDManager {
// 如果当前选中了某个组件,则默认让其第一个区域处于拖入状态。
if (containerId) {
const node = store.getNodeById(containerId);
if (node?.childRegions.length) {
this.switchToRegion(e, node.id, node.childRegions[0].region);
let slotIndex = 0;
node.childRegions.forEach((regionItem: any, index: number) => {
// 优先使用body作为插入子元素的位置
if (regionItem.region) {
slotIndex = index;
}
});
this.switchToRegion(e, node.id, node.childRegions[slotIndex].region);
}
}
break;
@ -325,9 +331,9 @@ export class EditorDNDManager {
const curElem = target.closest(`[data-region][data-region-host]`);
const hostId = curElem?.getAttribute('data-region-host');
const region = curElem?.getAttribute('data-region');
if (
d > 5 &&
d > 0 &&
this.curDragId &&
this.manager.draggableContainer(this.curDragId)
) {

View File

@ -261,5 +261,6 @@ extendLocale('en-US', {
'2a6287664de5ab46c65220c7182878ff': 'Cut',
'db805d4e361ac2d3fc6047eaea1a7c69': 'Free container',
'969e9e56b3812abffa3994f35ea31835': 'Adsorption vessel',
'5b804b05e14aaacc75033a4f77fc2844': 'Source code'
'5b804b05e14aaacc75033a4f77fc2844': 'Source code',
'69a53230577258b6d97ee932befcc168': '<Column>:'
});

View File

@ -233,5 +233,6 @@ extendLocale('zh-CN', {
'2a6287664de5ab46c65220c7182878ff': '已剪切',
'db805d4e361ac2d3fc6047eaea1a7c69': '自由容器',
'969e9e56b3812abffa3994f35ea31835': '吸附容器',
'5b804b05e14aaacc75033a4f77fc2844': '源码'
'5b804b05e14aaacc75033a4f77fc2844': '源码',
'69a53230577258b6d97ee932befcc168': '<列>:'
});

View File

@ -2,7 +2,7 @@
* @file 西 compoennt/Editor.tsx
* UI 西
*/
import {getRenderers, RenderOptions} from 'amis-core';
import {getRenderers, RenderOptions, mapTree} from 'amis-core';
import {
PluginInterface,
BasicPanelItem,
@ -55,16 +55,21 @@ import {reaction} from 'mobx';
import {hackIn, makeSchemaFormRender, makeWrapper} from './component/factory';
import {env} from './env';
import debounce from 'lodash/debounce';
import sortBy from 'lodash/sortBy';
import reverse from 'lodash/reverse';
import cloneDeep from 'lodash/cloneDeep';
import {openContextMenus, toast, alert, DataScope, DataSchema} from 'amis';
import {parse, stringify} from 'json-ast-comments';
import {EditorNodeType} from './store/node';
import {EditorProps} from './component/Editor';
import findIndex from 'lodash/findIndex';
import {EditorDNDManager} from './dnd';
import {VariableManager} from './variable';
import {IScopedContext} from 'amis';
import {SchemaObject, SchemaCollection} from 'amis/lib/Schema';
import type {RendererConfig} from 'amis-core/lib/factory';
import isPlainObject from 'lodash/isPlainObject';
import {omit} from 'lodash';
export interface EditorManagerConfig
extends Omit<EditorProps, 'value' | 'onChange'> {}
@ -158,6 +163,9 @@ export class EditorManager {
dataSchema: DataSchema;
readonly isInFrame: boolean = false;
/** 变量管理 */
readonly variableManager;
constructor(
readonly config: EditorManagerConfig,
readonly store: EditorStoreType,
@ -168,7 +176,7 @@ export class EditorManager {
// 传给 amis 渲染器的默认 env
this.env = {
...env, // 默认的 env 中带 jumpTo
...config.amisEnv, // 用户也可以设置自定义环境配置,用于覆盖默认的 env
...omit(config.amisEnv, 'replaceText'), // 用户也可以设置自定义环境配置,用于覆盖默认的 env
theme: config.theme
};
this.env.beforeDispatchEvent = this.beforeDispatchEvent.bind(
@ -207,8 +215,15 @@ export class EditorManager {
this.dnd = parent?.dnd || new EditorDNDManager(this, store);
this.dataSchema =
parent?.dataSchema || new DataSchema(config.schemas || []);
this.dataSchema.current.tag = '系统变量';
/** 初始化变量管理 */
this.variableManager = new VariableManager(
this.dataSchema,
config?.variables,
config?.variableOptions
);
if (isInFrame) {
return;
}
@ -469,6 +484,10 @@ export class EditorManager {
let id = curRendererId || this.store.activeId;
let panels: Array<BasicPanelItem> = [];
if (!id && this.store?.schema) {
id = this.store?.schema.$$id; // 默认使用根节点id
}
if (id || this.store.selections.length) {
id = id || this.store.selections[0];
const node = this.store.getNodeById(id);
@ -793,7 +812,8 @@ export class EditorManager {
if (
(node.type === 'wrapper' || node.type === 'container') &&
node.schema?.body?.length === 0 &&
(schemaData?.type === 'flex' || subRenderer?.rendererName === 'flex')
(schemaData?.type === 'flex' || subRenderer?.rendererName === 'flex') &&
!node.schema?.isFreeContainer
) {
const newSchemaData = schemaData || subRenderer?.scaffold;
// 布局能力提升: 点击插入新元素当wrapper为空插入布局容器时自动改为置换避免过多层级
@ -1033,6 +1053,13 @@ export class EditorManager {
}
}
/**
* flex容器
*/
isFlexContainer(id: string) {
return this.store.isFlexContainer(id);
}
/**
* flex布局子容器
* 备注: 以便额外增加布局相关配置项
@ -1707,6 +1734,7 @@ export class EditorManager {
dom: HTMLElement;
node: EditorNodeType;
resizer: HTMLElement;
store: EditorStoreType;
}
) {
return this.trigger('size-change-start', {

View File

@ -564,6 +564,7 @@ export interface ResizeMoveEventContext extends EventContext {
dom: HTMLElement;
resizer: HTMLElement;
node: EditorNodeType;
store: EditorStoreType;
}
export interface AfterBuildPanelBody extends EventContext {
@ -625,7 +626,6 @@ export function createEvent<T extends EventContext>(
return event;
}
export interface PluginEventListener {
onInit?: (
event: PluginEvent<
@ -1208,3 +1208,146 @@ export abstract class BasePlugin implements PluginInterface {
);
}
}
/**
*
*/
export class LayoutBasePlugin extends BasePlugin {
onActive(event: PluginEvent<ActiveEventContext>) {
const context = event.context;
if (context.info?.plugin !== this || !context.node) {
return;
}
const node = context.node!;
const curSchema = node.schema || {};
if (curSchema.isFixedWidth) {
node.setWidthMutable(true);
}
if (curSchema.isFixedHeight) {
node.setHeightMutable(true);
}
}
onWidthChangeStart(
event: PluginEvent<
ResizeMoveEventContext,
{
onMove(e: MouseEvent): void;
onEnd(e: MouseEvent): void;
}
>
) {
return this.onSizeChangeStart(event, 'horizontal');
}
onHeightChangeStart(
event: PluginEvent<
ResizeMoveEventContext,
{
onMove(e: MouseEvent): void;
onEnd(e: MouseEvent): void;
}
>
) {
return this.onSizeChangeStart(event, 'vertical');
}
onSizeChangeStart(
event: PluginEvent<
ResizeMoveEventContext,
{
onMove(e: MouseEvent): void;
onEnd(e: MouseEvent): void;
}
>,
direction: 'both' | 'vertical' | 'horizontal' = 'both'
) {
const context = event.context;
const node = context.node;
const store = context.store;
if (node.info?.plugin !== this) {
return;
}
const resizer = context.resizer;
const dom = context.dom;
const doc = store.getDoc() || document;
const frameRect = dom.parentElement!.getBoundingClientRect();
const rect = dom.getBoundingClientRect();
const startX = context.nativeEvent.pageX;
const startY = context.nativeEvent.pageY;
event.setData({
onMove: (e: MouseEvent) => {
const dy = e.pageY - startY;
const dx = e.pageX - startX;
const height = Math.max(50, rect.height + dy);
const width = Math.max(100, Math.min(rect.width + dx, frameRect.width));
const state: any = {
width,
height
};
const dragHlBoxItem = doc.querySelector(
`[data-hlbox-id='${node.id}']`
) as HTMLElement;
// 实时调整被拖拽元素的坐标值
const dragContainerItem = doc.querySelector(
`[data-editor-id='${node.id}']`
) as HTMLElement;
if (direction === 'both') {
resizer.setAttribute('data-value', `${width}px x ${height}px`);
dragHlBoxItem.style.height = `${height}px`;
dragHlBoxItem.style.width = `${width}px`;
dragContainerItem.style.height = `${height}px`;
dragContainerItem.style.width = `${width}px`;
} else if (direction === 'vertical') {
resizer.setAttribute('data-value', `${height}px`);
dragHlBoxItem.style.height = `${height}px`;
dragContainerItem.style.height = `${height}px`;
delete state.width;
} else {
resizer.setAttribute('data-value', `${width}px`);
dragHlBoxItem.style.width = `${width}px`;
dragContainerItem.style.width = `${width}px`;
delete state.height;
}
node.updateState(state);
requestAnimationFrame(() => {
node.calculateHighlightBox();
});
},
onEnd: (e: MouseEvent) => {
const dy = e.pageY - startY;
const dx = e.pageX - startX;
const height = Math.max(50, rect.height + dy);
const width = Math.max(100, Math.min(rect.width + dx, frameRect.width));
const state: any = {
width,
height
};
if (direction === 'vertical') {
delete state.width;
} else if (direction === 'horizontal') {
delete state.height;
}
resizer.removeAttribute('data-value');
node.updateSchemaStyle(state);
requestAnimationFrame(() => {
node.calculateHighlightBox();
});
}
});
}
}

View File

@ -1,4 +1,10 @@
import {findTree, getVariable, mapObject, createObject} from 'amis-core';
import {
findTree,
getVariable,
mapObject,
mapTree,
extendObject
} from 'amis-core';
import {cast, getEnv, Instance, types} from 'mobx-state-tree';
import {
diff,
@ -11,8 +17,8 @@ import {
stringRegExp,
needDefaultWidth,
guid,
reGenerateID,
addStyleClassName
addStyleClassName,
appTranslate
} from '../../src/util';
import {
InsertEventContext,
@ -44,7 +50,6 @@ import isPlainObject from 'lodash/isPlainObject';
import {EditorManagerConfig} from '../manager';
import {EditorNode, EditorNodeType} from './node';
import findIndex from 'lodash/findIndex';
import {cloneDeep} from 'lodash';
export interface SchemaHistory {
versionId: number;
@ -201,7 +206,15 @@ export const MainStore = types
// 自动收集可以供 target/reload 使用的名称列表
targetNames: types.optional(types.array(types.frozen<TargetName>()), []),
ctx: types.frozen()
ctx: types.frozen(),
/** 是否开启应用多语言 */
i18nEnabled: types.optional(types.boolean, false),
/** 应用语言 */
appLocale: types.optional(types.string, 'zh-CN'),
/** 应用语料 */
appCorpusData: types.optional(types.frozen(), {}),
/** 应用多语言状态,用于其它组件进行订阅 */
appLocaleState: types.optional(types.number, 0)
})
.views(self => {
return {
@ -322,6 +335,12 @@ export const MainStore = types
: nodes.push.apply(nodes, self.selections);
}
// 判断父元素是否为自由容器元素
const curFreeContainerId = this.parentIsFreeContainer(self.activeId);
if (curFreeContainerId) {
nodes.push(curFreeContainerId);
}
if (self.insertMode === 'insert' && self.insertId) {
nodes.push(self.insertId);
}
@ -865,9 +884,21 @@ export const MainStore = types
);
return idx < self.schemaHistory.length - 1;
},
// 判断当前元素定位是否为flex容器
isFlexContainer(id: string) {
const activeId = id ?? self.activeId;
const curSchema = this.getSchema(activeId);
if (
curSchema?.style?.display === 'flex' ||
curSchema?.style?.display === 'inline-flex'
) {
return true;
}
return false;
},
// 判断是否是布局容器中的列级元素
isFlexItem(id: string) {
const activeId = id || self.activeId;
const activeId = id ?? self.activeId;
const parentSchema = this.getSchemaParentById(activeId, true);
if (
parentSchema?.type === 'flex' ||
@ -880,7 +911,7 @@ export const MainStore = types
},
// 判断父级布局容器是否为垂直排列
isFlexColumnItem(id: string) {
const activeId = id || self.activeId;
const activeId = id ?? self.activeId;
const parentSchema = this.getSchemaParentById(activeId, true);
const isFlexItem =
parentSchema?.type === 'flex' ||
@ -909,8 +940,59 @@ export const MainStore = types
}
return false;
},
// 判断父元素是否为自由容器元素如果父级元素是自由容器则返回父元素ID
parentIsFreeContainer(id?: string) {
const activeId = id ?? self.activeId;
const curNode = this.getNodeById(activeId)!;
const parentNode = curNode?.parent;
if (!parentNode) {
return false;
}
const curSchema = this.getSchema(parentNode.id);
if (curSchema?.isFreeContainer) {
return parentNode.id;
}
return false;
},
get getSuperEditorData() {
return self.superEditorData || {};
},
// 获取组件选择树
getComponentTreeSource() {
return mapTree(
self.root.children ?? [],
(item: any) => {
const schema = item.id
? JSONGetById(self.schema, item.id)
: self.schema;
let cmptLabel = '';
const itemLabel = appTranslate(item?.label);
const schemaLabel = appTranslate(schema?.label);
const schemaTitle = appTranslate(schema?.title);
if (item?.region) {
cmptLabel = itemLabel;
} else {
const labelPrefix =
item.type !== 'cell' ? `<${itemLabel}>:` : `<列>:`;
cmptLabel = `${labelPrefix}${
schemaLabel ?? schemaTitle ?? itemLabel
}`;
}
cmptLabel = cmptLabel ?? itemLabel;
return {
id: item.id,
label: cmptLabel,
value: schema?.id ?? item.id,
type: schema?.type ?? item.type,
schema,
disabled: !!item.region,
visible: item.region ? !!item?.children.length : true,
children: item?.children
};
},
1,
true
);
}
};
})
@ -1112,7 +1194,6 @@ export const MainStore = types
self.activeId = id;
self.activeRegion = region;
self.selections = selections;
// if (!self.panelKey && id) {
// self.panelKey = 'config';
// }
@ -1499,7 +1580,13 @@ export const MainStore = types
}
self.subEditorContext = {
...context,
data: context.data || {}
data: extendObject(context.data, {
__curCmptTreeWrap: {
label: context.title,
disabled: true
},
__superCmptTreeSource: self.getComponentTreeSource()
})
};
},
@ -1735,6 +1822,26 @@ export const MainStore = types
}
});
this.traceableSetSchema(json);
},
/** 更改应用多语言的状态 */
updateAppLocaleState() {
self.appLocaleState += 1;
},
/** 设置应用语言,支持应用国际化 */
setAppLocale(locale?: string) {
if (!locale) {
return;
}
self.appLocale = locale;
this.updateAppLocaleState();
},
/** 设置应用的语料数据 */
setAppCorpusData(data: any = {}) {
self.appCorpusData = data;
this.updateAppLocaleState();
}
};
});

View File

@ -636,6 +636,24 @@ export const EditorNode = types
root.changeValueById(info.id, schema);
},
updateSchemaStyle(value: any) {
const info = self.info!;
if (info.editable === false) {
return;
}
const root = getRoot(self) as any;
let schema = root.getSchema(info.id);
schema = {
...schema,
style: {
...schema.style,
...value
}
};
root.changeValueById(info.id, schema);
},
setComponent(value: any) {
component = value;
},

View File

@ -1,7 +1,7 @@
/**
* @file
*/
import {utils, hasIcon} from 'amis';
import {utils, hasIcon, mapObject} from 'amis';
import isEqual from 'lodash/isEqual';
import {isObservable, reaction} from 'mobx';
import DeepDiff, {Diff} from 'deep-diff';
@ -993,7 +993,8 @@ export function unitFormula(insetStr: string, offsetVal: number) {
insetUnit = 'px';
}
const newOffsetVal = insetNum + curOffsetVal;
return `${newOffsetVal >= 0 ? newOffsetVal : '0'}${insetUnit}`;
return `${newOffsetVal}${insetUnit}`;
// return `${newOffsetVal >= 0 ? newOffsetVal : '0'}${insetUnit}`; // 限制拖拽区域
}
/**
@ -1022,10 +1023,63 @@ export function needDefaultWidth(elemType: string) {
'progress',
'diff-editor',
'editor',
'input-range'
'input-range',
'flex'
];
if (needDefaultWidthElemType.indexOf(elemType) > -1) {
return true;
}
return false;
}
/** 是否开启应用国际化 */
export function getI18nEnabled() {
return (window as any)?.editorStore?.i18nEnabled ?? false;
}
/** schema 翻译方法 */
export function translateSchema(schema: any, replaceData?: any) {
replaceData = replaceData || (window as any)?.editorStore?.appCorpusData;
if (!isPlainObject(replaceData)) {
return schema;
}
return mapObject(schema, (item: any) => {
return replaceData[item] || item;
});
}
/** 应用级别的翻译方法 */
export function appTranslate(value?: string) {
if (!isString(value)) {
return value;
}
return (window as any)?.editorStore?.appCorpusData?.[value!] || value;
}
/**
*
*/
export function needFillPlaceholder(curProps: any) {
let needFillPlaceholder = false;
if (!curProps) {
return false;
}
// 识别page中的aside、body
if (
curProps.rendererName === 'page' &&
(curProps.name === 'aside' || curProps.name === 'body')
) {
return true;
}
// 识别自由容器
if (curProps.node?.schema?.isFreeContainer) {
return true;
}
// 支持在plugin中配置
if (curProps.$$editor?.needFillPlaceholder) {
needFillPlaceholder = true;
} else if (curProps.regionConfig?.needFillPlaceholder) {
needFillPlaceholder = true;
}
return needFillPlaceholder;
}

View File

@ -0,0 +1,256 @@
/**
* @file
* @desc
*/
import sortBy from 'lodash/sortBy';
import cloneDeep from 'lodash/cloneDeep';
import reverse from 'lodash/reverse';
import pick from 'lodash/pick';
import {JSONSchema, DataSchema, mapTree, findTree, eachTree} from 'amis-core';
import type {Option} from 'amis-core';
export interface VariableGroup {
/** 变量命名空间 */
name: string;
/** 标题显示名称 */
title: string;
/* 父节点scope id */
parentId: string;
/** 顺序 */
order: number;
/** 结构定义根结点必须为object */
schema: JSONSchema;
}
export interface VariableOptions {
/** 变量Schema被添加到Scope之前触发 */
beforeScopeInsert?: (
context: VariableManager,
schema: JSONSchema
) => JSONSchema;
/** 事件变量Schema被添加到Scope之后触发 */
afterScopeInsert?: (context: VariableManager) => void;
/** 获取上下文数据结构时触发,可以自定义返回的数据结构 */
onContextSchemaChange?: (
context: VariableManager,
schema: JSONSchema[]
) => JSONSchema[];
/** 获取上下文数据Options时触发可以自定义返回的数据结构 */
onContextOptionChange?: (
context: VariableManager,
option: Option[],
type: 'normal' | 'formula'
) => Option[];
}
export class VariableManager {
/* 变量列表 */
readonly variables: VariableGroup[];
/* 上下文结构 */
readonly dataSchema: DataSchema;
/* 变量管理配置 */
readonly options: VariableOptions;
constructor(
dataSchema: DataSchema | undefined,
variables: VariableGroup[] | undefined,
options: VariableOptions | undefined
) {
this.variables = Array.isArray(variables)
? sortBy(cloneDeep(variables), [item => item.order ?? 1])
: [];
this.dataSchema =
dataSchema instanceof DataSchema ? dataSchema : new DataSchema([]);
this.options = pick(options, [
'beforeScopeInsert',
'afterScopeInsert',
'onContextSchemaChange',
'onContextOptionChange'
]);
this.init();
}
/**
*
* (root)
*
*
*
* ...
*/
init() {
const variables = this.variables;
const dataSchema = this.dataSchema;
const {beforeScopeInsert, afterScopeInsert} = this.options ?? {};
variables.forEach(item => {
const {parentId, name: scopeName, title: tagName} = item;
let schema = item.schema;
if (!dataSchema.hasScope(parentId)) {
return;
}
dataSchema.switchTo(parentId);
if (dataSchema.hasScope(scopeName)) {
dataSchema.removeScope(scopeName);
}
if (beforeScopeInsert && typeof beforeScopeInsert === 'function') {
schema = beforeScopeInsert(this, schema);
}
/** 初始化变量Scope */
dataSchema.addScope(schema, scopeName);
dataSchema.switchTo(scopeName);
/** 这里的Tag指变量的命名空间中文名称 */
dataSchema.current.tag = tagName;
if (afterScopeInsert && typeof afterScopeInsert === 'function') {
afterScopeInsert(this);
}
});
dataSchema.switchToRoot();
}
/**
*
*/
getVariableContextSchema() {
let variableSchemas: JSONSchema[] = [];
const {onContextSchemaChange} = this.options ?? {};
if (this.variables && this.variables?.length > 0) {
variableSchemas = this.variables
.map(item => {
if (this.dataSchema.hasScope(item.name)) {
const varScope = this.dataSchema.getScope(item.name);
/** 变量的Scope只有一个根结点 */
return varScope.schemas.length > 0 ? varScope.schemas[0] : null;
}
return null;
})
.filter((item): item is JSONSchema => item !== null);
}
if (onContextSchemaChange && typeof onContextSchemaChange === 'function') {
variableSchemas = onContextSchemaChange(this, variableSchemas);
}
return variableSchemas;
}
/**
* Option结构
*/
getVariableFormulaOptions(reverseOrder: boolean = false) {
const {onContextOptionChange} = this.options ?? {};
let options: Option[] = [];
if (this.variables && this.variables?.length > 0) {
this.variables.forEach(item => {
if (this.dataSchema.hasScope(item.name)) {
const varScope = this.dataSchema.getScope(item.name);
const children = mapTree(varScope.getDataPropsAsOptions(), item => ({
...item,
/** tag默认会被赋值为description这里得替换回来 */
tag: item.type
}));
if (varScope.tag) {
options.push({label: varScope.tag, children});
} else {
options.push(...children);
}
}
});
}
if (onContextOptionChange && typeof onContextOptionChange === 'function') {
options = onContextOptionChange(this, options, 'formula');
}
eachTree(options, item => {
if (item.type === 'array') {
delete item.children;
}
});
return reverseOrder ? options : reverse(options);
}
/**
*
*/
getVariableOptions() {
const {onContextOptionChange} = this.options ?? {};
let options: Option[] =
this.getVariableFormulaOptions(false)?.[0]?.children ?? [];
options = mapTree(
options,
(item: Option, key: number, level: number, paths: Option[]) => {
return {
...item,
valueExpression:
typeof item.value === 'string' && !item.value.startsWith('${')
? `\${${item.value}}`
: item.value
};
}
);
if (onContextOptionChange && typeof onContextOptionChange === 'function') {
options = onContextOptionChange(this, options, 'normal');
}
return options;
}
/**
*
* @returns
*/
getPageVariablesOptions() {
let options: Option[] = [];
const pageScope = this.dataSchema?.root.children?.filter(
item => item.tag === '页面变量'
)[0];
if (pageScope) {
options = pageScope.getDataPropsAsOptions();
}
eachTree(options, item => {
if (item.type === 'array') {
delete item.children;
}
});
return options;
}
/**
*
*/
getNameByPath(path: string, valueField = 'value', labelField = 'label') {
if (!path || typeof path !== 'string') {
return '';
}
const options = [
...this.getVariableOptions(),
...this.getPageVariablesOptions()
];
const node = findTree(
options,
item => item[valueField ?? 'value'] === path
);
return node
? node[labelField ?? 'label'] ?? node[valueField ?? 'value'] ?? ''
: '';
}
}