Pick: #9446、#9440、#9439 (#9459)

* chore: 优化编辑器选中顶部菜单栏三个点呼出的功能菜单位置,防止被挡住 (#9446)

* chore: 优化编辑器选中顶部菜单栏三个点呼出的功能菜单位置,防止被挡住

* fix: 修复新出来的节点无法点选的问题

* chore: 还原相关逻辑

* fix: 修复 condition-builder 操作符只有一个时不可以点选的问题 (#9439)

* fix: 修复 crud nested 模式深层次点选异常 (#9440)
This commit is contained in:
liaoxuezhi 2024-01-17 15:32:58 +08:00 committed by GitHub
parent e7bfbf77b3
commit 36ecaaa21b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 130 additions and 104 deletions

View File

@ -471,9 +471,9 @@ const data = [
version: '1',
grade: 'A'
}
].map(function (child, i) {
].map(function (child, j) {
return Object.assign({}, child, {
id: (i + 1) * 100 + (index + 1) * 1000 + i + 1
id: (index + 1) * 10000 + (i + 1) * 100 + 1 + j
});
})
});

View File

@ -1529,11 +1529,11 @@ export const TableStore = iRendererStore
self.selectedRows.clear();
selected.forEach(item => {
let resolved = self.rows.find(a => a.pristine === item);
let resolved = findTree(self.rows, a => a.pristine === item);
// 先严格比较,
if (!resolved) {
resolved = self.rows.find(a => {
resolved = findTree(self.rows, a => {
const selectValue = item[valueField || 'value'];
const itemValue = a.pristine[valueField || 'value'];
return selectValue === itemValue;
@ -1542,14 +1542,14 @@ export const TableStore = iRendererStore
// 再宽松比较
if (!resolved) {
resolved = self.rows.find(a => {
resolved = findTree(self.rows, a => {
const selectValue = item[valueField || 'value'];
const itemValue = a.pristine[valueField || 'value'];
return selectValue == itemValue;
});
}
resolved && self.selectedRows.push(resolved);
resolved && self.selectedRows.push(resolved as any);
});
updateCheckDisable();

View File

@ -253,7 +253,8 @@ export function isObjectShallowModified(
next: any,
strictModeOrFunc: boolean | ((lhs: any, rhs: any) => boolean) = true,
ignoreUndefined: boolean = false,
stack: Array<any> = []
stack: Array<any> = [],
maxDepth: number = -1
): boolean {
if (Array.isArray(prev) && Array.isArray(next)) {
return prev.length !== next.length
@ -310,6 +311,9 @@ export function isObjectShallowModified(
}
stack.push(prev);
if (maxDepth > 0 && stack.length > maxDepth) {
return true;
}
for (let i: number = keys.length - 1; i >= 0; i--) {
const key = keys[i];

View File

@ -0,0 +1,20 @@
import isPlainObject from 'lodash/isPlainObject';
export function labelToString(label: any): string {
const type = typeof label;
if (type === 'string') {
return label;
} else if (type === 'number') {
return `${label}`;
}
if (isPlainObject(label)) {
for (let key of ['__title', 'label', Object.keys(label)[0]]) {
if (typeof label[key] === 'string') {
return label[key];
}
}
}
return 'invalid label';
}

View File

@ -1,5 +1,16 @@
import {Evaluator, parse, evaluateForAsync} from 'amis-formula';
const AST_CACHE: {[key: string]: any} = {};
export function memoParse(str: string, options?: any) {
let key = `${str}${options?.evalMode ? '-eval' : ''}${
options?.allowFilter ? '-filter' : ''
}${options?.variableMode ? '-variable' : ''}`;
const ast = AST_CACHE[key] || parse(str, options);
AST_CACHE[key] = ast;
return ast;
}
export const tokenize = (
str: string,
data: object,
@ -10,10 +21,11 @@ export const tokenize = (
}
try {
const ast = parse(str, {
const ast = memoParse(str, {
evalMode: false,
allowFilter: true
});
const result = new Evaluator(data, {
defaultFilter
}).evalute(ast);

View File

@ -2,6 +2,7 @@ import {register as registerBulitin, getFilters} from './tpl-builtin';
import {register as registerLodash} from './tpl-lodash';
import {parse, evaluate} from 'amis-formula';
import {resolveCondition} from './resolveCondition';
import {memoParse} from './tokenize';
export interface Enginer {
test: (tpl: string) => boolean;
@ -145,14 +146,10 @@ export async function evalExpressionWithConditionBuilder(
return evalExpression(String(expression), data);
}
const AST_CACHE: {[key: string]: any} = {};
function evalFormula(expression: string, data: any) {
const ast =
AST_CACHE[expression] ||
parse(expression, {
evalMode: false
});
AST_CACHE[expression] = ast;
const ast = memoParse(expression, {
evalMode: false
});
return evaluate(ast, data, {
defaultFilter: 'raw'

View File

@ -38,6 +38,7 @@ export class BasicToolbarPlugin extends BasePlugin {
if ((node.draggable || draggableContainer) && !isSorptionContainer) {
toolbars.push({
id: 'drag',
iconSvg: 'drag-btn',
icon: 'fa fa-arrows',
tooltip: '按住拖动调整位置',
@ -127,6 +128,7 @@ export class BasicToolbarPlugin extends BasePlugin {
toolbars.push(
{
id: 'insert-before',
iconSvg: 'left-arrow-to-left',
tooltip: '向前插入组件',
// level: 'special',
@ -146,6 +148,7 @@ export class BasicToolbarPlugin extends BasePlugin {
)
},
{
id: 'insert-after',
iconSvg: 'arrow-to-right',
tooltip: '向后插入组件',
// level: 'special',
@ -173,6 +176,7 @@ export class BasicToolbarPlugin extends BasePlugin {
(node.info.plugin.popOverBody || node.info.plugin.popOverBodyCreator)
) {
toolbars.push({
id: 'edit',
icon: 'fa fa-pencil',
tooltip: '编辑',
placement: 'bottom',
@ -193,6 +197,7 @@ export class BasicToolbarPlugin extends BasePlugin {
if (node.removable || node.removable === undefined) {
toolbars.push({
id: 'delete',
iconSvg: 'delete-btn',
icon: 'fa',
tooltip: '删除',
@ -203,6 +208,7 @@ export class BasicToolbarPlugin extends BasePlugin {
}
toolbars.push({
id: 'more',
iconSvg: 'more-btn',
icon: 'fa fa-cog',
tooltip: '更多',
@ -213,8 +219,18 @@ export class BasicToolbarPlugin extends BasePlugin {
const info = (
e.target as HTMLElement
).parentElement!.getBoundingClientRect();
// 150 是 contextMenu 的宽度
// 默认右对齐
let x = window.scrollX + info.left + info.width - 150;
// 显示不全是改成左对齐
if (x < 0) {
x = window.scrollX + info.left;
}
this.manager.openContextMenu(id, '', {
x: window.scrollX + info.left + info.width - 155,
x: x,
y: window.scrollY + info.top + info.height + 8
});
}
@ -223,6 +239,7 @@ export class BasicToolbarPlugin extends BasePlugin {
if (info.scaffoldForm?.canRebuild ?? info.plugin.scaffoldForm?.canRebuild) {
toolbars.push({
id: 'build',
iconSvg: 'harmmer',
tooltip: `快速构建「${info.plugin.name}`,
placement: 'bottom',
@ -247,6 +264,7 @@ export class BasicToolbarPlugin extends BasePlugin {
if (selections.length) {
// 多选时的右键菜单
menus.push({
id: 'copy',
label: '重复一份',
icon: 'copy-icon',
disabled: selections.some(item => !item.node.duplicatable),
@ -254,12 +272,14 @@ export class BasicToolbarPlugin extends BasePlugin {
});
menus.push({
id: 'unselect',
label: '取消多选',
icon: 'cancel-icon',
onSelect: () => store.setActiveId(id)
});
menus.push({
id: 'delete',
label: '删除',
icon: 'delete-icon',
disabled: selections.some(item => !item.node.removable),
@ -281,23 +301,27 @@ export class BasicToolbarPlugin extends BasePlugin {
});
*/
menus.push({
id: 'insert',
label: '插入组件',
onHighlight: (isOn: boolean) => isOn && store.setHoverId(id, region),
onSelect: () => store.showInsertRendererPanel()
});
menus.push({
id: 'clear',
label: '清空',
onSelect: () => manager.emptyRegion(id, region)
});
menus.push({
id: 'paste',
label: '粘贴',
onSelect: () => manager.paste(id, region)
});
}
} else {
menus.push({
id: 'select',
label: `选中${first.label}`,
disabled: store.activeId === first.id,
data: id,
@ -326,6 +350,7 @@ export class BasicToolbarPlugin extends BasePlugin {
}
menus.push({
id: 'unselect',
label: '取消选中',
disabled: !store.activeId || store.activeId !== id,
onSelect: () => store.setActiveId('')
@ -334,23 +359,27 @@ export class BasicToolbarPlugin extends BasePlugin {
menus.push('|');
menus.push({
id: 'copy',
label: '重复一份',
disabled: !node.duplicatable,
onSelect: () => manager.duplicate(id)
});
menus.push({
id: 'copy-config',
label: '复制配置',
onSelect: () => manager.copy(id)
});
menus.push({
id: 'cat-config',
label: '剪切配置',
disabled: !node.removable,
onSelect: () => manager.cut(id)
});
menus.push({
id: 'paste-config',
label: '粘贴配置',
disabled:
!Array.isArray(parent) ||
@ -361,6 +390,7 @@ export class BasicToolbarPlugin extends BasePlugin {
});
menus.push({
id: 'delete',
label: '删除',
disabled: !node.removable,
className: 'text-danger',
@ -372,6 +402,7 @@ export class BasicToolbarPlugin extends BasePlugin {
const idx = Array.isArray(parent) ? parent.indexOf(schema) : -1;
menus.push({
id: 'move-forward',
label: '向前移动',
disabled: !(Array.isArray(parent) && idx > 0) || !node.moveable,
// || !node.prevSibling,
@ -379,6 +410,7 @@ export class BasicToolbarPlugin extends BasePlugin {
});
menus.push({
id: 'move-backward',
label: '向后移动',
disabled:
!(Array.isArray(parent) && idx < parent.length - 1) || !node.moveable,
@ -450,12 +482,14 @@ export class BasicToolbarPlugin extends BasePlugin {
// });
menus.push({
id: 'undo',
label: '撤销Undo',
disabled: !store.canUndo,
onSelect: () => store.undo()
});
menus.push({
id: 'redo',
label: '重做Redo',
disabled: !store.canRedo,
onSelect: () => store.redo()
@ -499,6 +533,7 @@ export class BasicToolbarPlugin extends BasePlugin {
if (first.childRegions.length && renderersPanel) {
if (first.childRegions.length > 1) {
menus.push({
id: 'insert',
label: '插入组件',
children: first.childRegions.map(region => ({
label: `${region.label}`,
@ -510,6 +545,7 @@ export class BasicToolbarPlugin extends BasePlugin {
});
} else {
menus.push({
id: 'insert',
label: '插入组件',
data: first.childRegions[0].region,
onHighlight: (isOn: boolean, region: string) =>
@ -520,6 +556,7 @@ export class BasicToolbarPlugin extends BasePlugin {
}
menus.push({
id: 'replace',
label: '替换组件',
disabled:
!node.host ||
@ -536,6 +573,7 @@ export class BasicToolbarPlugin extends BasePlugin {
(info.plugin.scaffoldForm?.canRebuild || info.scaffoldForm?.canRebuild)
) {
menus.push({
id: 'build',
label: `快速构建「${info.plugin.name}`,
disabled: schema.$$commonSchema,
onSelect: () =>

View File

@ -3,6 +3,7 @@ import {
getVariable,
mapObject,
mapTree,
eachTree,
extendObject,
createObject
} from 'amis-core';
@ -133,7 +134,6 @@ export const MainStore = types
id: 'root',
label: 'Root'
}),
map: types.optional(types.frozen(), {}),
theme: 'cxd', // 主题默认cxd主题
hoverId: '',
hoverRegion: '',
@ -389,29 +389,7 @@ export const MainStore = types
id: string,
regionOrType?: string
): EditorNodeType | undefined {
const key = id + (regionOrType ? '-' + regionOrType : '');
return self.map[key];
// let pool = self.root.children.concat();
// while (pool.length) {
// const item = pool.shift();
// if (
// item.id === id &&
// (!regionOrType ||
// item.region === regionOrType ||
// item.type === regionOrType)
// ) {
// return item;
// }
// // 将当前节点的子节点全部放置到 pool中
// if (item.children.length) {
// pool.push.apply(pool, item.children);
// }
// }
// return undefined;
return self.root.getNodeById(id, regionOrType);
},
get activeNodeInfo(): RendererInfo | null | undefined {
@ -1058,36 +1036,6 @@ export const MainStore = types
);
return {
setNode(node: EditorNodeType) {
const map = {...self.map};
if (node.region) {
map[node.id + '-' + node.region] = node;
} else {
// 同名类型不同的节点,优先使用上层的
// 因为原来的 getNodeById 是这种查找策略
// 所以孩子节点在父级写入了的情况下不写入
map[node.id] = map[node.id] || node;
map[node.id + '-' + node.type] = node;
}
self.map = map;
},
unsetNode(node: EditorNodeType) {
const map = {...self.map};
if (node.region) {
map[node.id + '-' + node.region] === node &&
delete map[node.id + '-' + node.region];
} else {
map[node.id] === node && delete map[node.id];
map[node.id + '-' + node.type] === node &&
delete map[node.id + '-' + node.type];
}
self.map = map;
},
setLayer(value: any) {
layer = value;
},
@ -1987,7 +1935,6 @@ export const MainStore = types
},
beforeDestroy() {
self.map = {};
lazyUpdateTargetName.cancel();
}
};

View File

@ -77,6 +77,32 @@ export const EditorNode = types
return info;
},
getNodeById(id: string, regionOrType?: string) {
// 找不到,再从 root.children 递归找
let pool = self.children.concat();
let resolved: any = undefined;
while (pool.length) {
const item = pool.shift();
if (
item.id === id &&
(!regionOrType ||
item.region === regionOrType ||
item.type === regionOrType)
) {
resolved = item;
break;
}
// 将当前节点的子节点全部放置到 pool中
if (item.children.length) {
pool.push.apply(pool, item.uniqueChildren);
}
}
return resolved;
},
setInfo(value: RendererInfo) {
info = value;
},
@ -617,7 +643,6 @@ export const EditorNode = types
});
const node = self.children[self.children.length - 1];
node.setInfo(props.info);
(getRoot(self) as any).setNode(node);
return node;
},
@ -628,24 +653,6 @@ export const EditorNode = types
return;
}
// 因为 react 的钩子是 父级先执行 willUnmout所以顶级的节点先删除
// 节点删除了,再去读取 mst 又会报错
// 所以在节点删除之前,先把所有孩子节点从 root.map 中删除
// 否则 root.map 里面会残存很多已经销毁的节点
const pool = [node];
const list = [];
while (pool.length) {
const item = pool.shift();
list.push(item);
pool.push(...item.children);
}
const root = getRoot(self) as any;
list.forEach((item: any) => {
root.unsetNode(item);
});
self.children.splice(idx, 1);
},

View File

@ -150,15 +150,18 @@ export class Evaluator {
// 只给简单的变量取值用法自动补fitler
if (defaultFilter && ~['getter', 'variable'].indexOf(ast.body?.type)) {
ast.body = {
type: 'filter',
input: ast.body,
filters: [
{
name: defaultFilter.replace(/^\s*\|\s*/, ''),
args: []
}
]
ast = {
...ast,
body: {
type: 'filter',
input: ast.body,
filters: [
{
name: defaultFilter.replace(/^\s*\|\s*/, ''),
args: []
}
]
}
};
}

View File

@ -24,6 +24,7 @@ interface ContextMenuProps {
}
export type MenuItem = {
id?: string;
label: string;
icon?: string;
disabled?: boolean;

View File

@ -223,7 +223,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
return (
<PopOverContainer
mobileUI={mobileUI}
disabled={operators.length < 2}
disabled={!!(value?.op && operators.length < 2)}
popOverContainer={popOverContainer || (() => findDOMNode(this))}
popOverRender={({onClose}) => (
<GroupedSelection

View File

@ -615,7 +615,7 @@ addSchemaFilter(function (schema: Schema, renderer: any, props: any) {
if (
newSchema !== schema &&
isObjectShallowModified(newSchema, schema, false)
isObjectShallowModified(newSchema, schema, false, false, [], 10)
) {
return newSchema;
}

View File

@ -76,7 +76,6 @@ export default function Cell({
return (
<td
style={style}
key={props.key}
className={cx(column.pristine.className, stickyClassName)}
>
<Checkbox
@ -93,7 +92,6 @@ export default function Cell({
return (
<td
style={style}
key={props.key}
className={cx(column.pristine.className, stickyClassName, {
'is-dragDisabled': !item.draggable
})}
@ -105,7 +103,6 @@ export default function Cell({
return (
<td
style={style}
key={props.key}
className={cx(column.pristine.className, stickyClassName)}
>
{item.expandable ? (