fix outline backup mode

This commit is contained in:
kangwei 2020-05-10 03:57:38 +08:00
parent e1d3f1e5a6
commit f75a51e77e
17 changed files with 185 additions and 170 deletions

View File

@ -331,6 +331,12 @@ body {
display: none; display: none;
flex-shrink: 0; flex-shrink: 0;
margin-left: 2px; margin-left: 2px;
position: relative;
>.lc-panel {
position: absolute;
left: 0;
top: 0;
}
&.lc-area-visible { &.lc-area-visible {
display: block; display: block;
} }

View File

@ -110,7 +110,7 @@ export class Skeleton {
} }
return this.createPanel(config); return this.createPanel(config);
}, },
true, false,
true, true,
); );
this.mainArea = new Area( this.mainArea = new Area(

View File

@ -1,5 +1,6 @@
import { ReactElement, ComponentType } from 'react'; import { ReactElement, ComponentType } from 'react';
import { TitleContent, IconType, I18nData, TipContent } from '@ali/lowcode-types'; import { TitleContent, IconType, I18nData, TipContent } from '@ali/lowcode-types';
import { IWidget } from './widget/widget';
export interface IWidgetBaseConfig { export interface IWidgetBaseConfig {
type: string; type: string;
@ -16,6 +17,7 @@ export interface WidgetConfig extends IWidgetBaseConfig {
type: "Widget"; type: "Widget";
props?: { props?: {
align?: "left" | "right" | "bottom" | "center" | "top"; align?: "left" | "right" | "bottom" | "center" | "top";
onInit?: (widget: IWidget) => void;
}; };
content?: string | ReactElement | ComponentType<any>; // children content?: string | ReactElement | ComponentType<any>; // children
} }
@ -47,6 +49,7 @@ export function isDividerConfig(obj: any): obj is DividerConfig {
export interface IDockBaseConfig extends IWidgetBaseConfig { export interface IDockBaseConfig extends IWidgetBaseConfig {
props?: DockProps & { props?: DockProps & {
align?: "left" | "right" | "bottom" | "center" | "top"; align?: "left" | "right" | "bottom" | "center" | "top";
onInit?: (widget: IWidget) => void;
}; };
} }
@ -95,7 +98,8 @@ export interface PanelProps {
height?: number; // panel.props height?: number; // panel.props
maxWidth?: number; // panel.props maxWidth?: number; // panel.props
maxHeight?: number; // panel.props maxHeight?: number; // panel.props
onInit?: () => any; condition?: (widget: IWidget) => any;
onInit?: (widget: IWidget) => any;
onDestroy?: () => any; onDestroy?: () => any;
shortcut?: string; // 只有在特定位置,可触发 toggle show shortcut?: string; // 只有在特定位置,可触发 toggle show
} }

View File

@ -54,6 +54,9 @@ export default class Dock implements IWidget {
const { props = {}, name } = config; const { props = {}, name } = config;
this.name = name; this.name = name;
this.align = props.align; this.align = props.align;
if (props.onInit) {
props.onInit.call(this, this);
}
} }
setVisible(flag: boolean) { setVisible(flag: boolean) {

View File

@ -83,6 +83,9 @@ export default class PanelDock implements IWidget {
area: panelProps?.area, area: panelProps?.area,
}) as Panel; }) as Panel;
} }
if (props?.onInit) {
props.onInit.call(this, this);
}
} }
setVisible(flag: boolean) { setVisible(flag: boolean) {

View File

@ -1,6 +1,6 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { createElement, ReactNode } from 'react'; import { createElement, ReactNode } from 'react';
import { obx } from '@ali/lowcode-editor-core'; import { obx, computed } from '@ali/lowcode-editor-core';
import { uniqueId, createContent } from '@ali/lowcode-utils'; import { uniqueId, createContent } from '@ali/lowcode-utils';
import { TitleContent } from '@ali/lowcode-types'; import { TitleContent } from '@ali/lowcode-types';
import WidgetContainer from './widget-container'; import WidgetContainer from './widget-container';
@ -16,14 +16,18 @@ export default class Panel implements IWidget {
readonly name: string; readonly name: string;
readonly id: string; readonly id: string;
@obx.ref inited = false; @obx.ref inited = false;
@obx.ref private _actived = false; @obx.ref private _actived: boolean = false;
private emitter = new EventEmitter(); private emitter = new EventEmitter();
get actived(): boolean { get actived(): boolean {
return this._actived; return this._actived;
} }
get visible(): boolean { @computed get visible(): boolean {
if (this.parent?.visible) { if (!this.parent || this.parent.visible) {
const { props } = this.config;
if (props?.condition) {
return props.condition(this);
}
return this._actived; return this._actived;
} }
return false; return false;
@ -80,6 +84,9 @@ export default class Panel implements IWidget {
); );
content.forEach((item) => this.add(item)); content.forEach((item) => this.add(item));
} }
if (props.onInit) {
props.onInit.call(this, this);
}
// todo: process shortcut // todo: process shortcut
} }

View File

@ -60,6 +60,9 @@ export default class Widget implements IWidget {
const { props = {}, name } = config; const { props = {}, name } = config;
this.name = name; this.name = name;
this.align = props.align; this.align = props.align;
if (props.onInit) {
props.onInit.call(this, this);
}
} }
getId() { getId() {

View File

@ -1,6 +1,8 @@
import Pane from './views/pane'; import { OutlinePane } from './views/pane';
import { OutlineBackupPane } from './views/backup-pane';
import { IconOutline } from './icons/outline'; import { IconOutline } from './icons/outline';
import { intlNode } from './locale'; import { intlNode } from './locale';
import { getTreeMaster } from './tree-master';
export default { export default {
name: 'outline-pane', name: 'outline-pane',
@ -8,9 +10,7 @@ export default {
icon: IconOutline, icon: IconOutline,
description: intlNode('Outline Tree'), description: intlNode('Outline Tree'),
}, },
content: Pane, content: OutlinePane,
}; };
export { getTreeMaster } from './main'; export { OutlinePane, OutlineBackupPane, getTreeMaster };
export { Pane };

View File

@ -1,5 +1,4 @@
import { EventEmitter } from 'events'; import { computed, obx } from '@ali/lowcode-editor-core';
import { computed, obx, Editor } from '@ali/lowcode-editor-core';
import { import {
Designer, Designer,
ISensor, ISensor,
@ -18,108 +17,15 @@ import {
contains, contains,
Node, Node,
} from '@ali/lowcode-designer'; } from '@ali/lowcode-designer';
import { Tree } from './tree';
import TreeNode from './tree-node'; import TreeNode from './tree-node';
import { IndentTrack } from './helper/indent-track'; import { IndentTrack } from './helper/indent-track';
import DwellTimer from './helper/dwell-timer'; import DwellTimer from './helper/dwell-timer';
import { uniqueId } from '@ali/lowcode-utils'; import { uniqueId } from '@ali/lowcode-utils';
import { Backup } from './views/backup-pane';
import { IEditor } from '@ali/lowcode-types';
import { ITreeBoard, TreeMaster, getTreeMaster } from './tree-master';
export interface IScrollBoard { export class OutlineMain implements ISensor, ITreeBoard, IScrollable {
scrollToNode(treeNode: TreeNode, detail?: any): void;
}
class TreeMaster {
private emitter = new EventEmitter();
private currentFixed?: OutlineMain;
constructor(readonly designer: Designer) {
designer.dragon.onDragstart((e) => {
const tree = this.currentTree;
if (tree) {
tree.document.selection.getTopNodes().forEach((node) => {
tree.getTreeNode(node).setExpanded(false);
});
}
if (!this.currentFixed) {
this.emitter.emit('enable-builtin');
}
});
designer.activeTracker.onChange(({ node, detail }) => {
const tree = this.currentTree;
if (!tree || node.document !== tree.document) {
return;
}
const treeNode = tree.getTreeNode(node);
if (detail && isLocationChildrenDetail(detail)) {
treeNode.expand(true);
} else {
treeNode.expandParents();
}
this.boards.forEach((board) => {
board.scrollToNode(treeNode, detail);
});
});
}
setFixed(entry: OutlineMain) {
this.currentFixed = entry;
}
unFixed(entry: OutlineMain) {
if (entry === this.currentFixed) {
this.currentFixed = undefined;
}
}
onceEnableBuiltin(fn: () => void): () => void {
this.emitter.once('enable-builtin', fn);
return () => {
this.emitter.removeListener('enable-builtin', fn);
}
}
private boards = new Set<IScrollBoard>();
addBoard(board: IScrollBoard) {
this.boards.add(board);
}
removeBoard(board: IScrollBoard) {
this.boards.delete(board);
}
purge() {
this.emitter.removeAllListeners();
// todo others purge
}
private treeMap = new Map<string, Tree>();
@computed get currentTree(): Tree | null {
const doc = this.designer?.currentDocument;
if (doc) {
const id = doc.id;
if (this.treeMap.has(id)) {
return this.treeMap.get(id)!;
}
const tree = new Tree(doc);
// TODO: listen purge event to remove
this.treeMap.set(id, tree);
return tree;
}
return null;
}
}
const mastersMap = new Map<Designer, TreeMaster>();
export function getTreeMaster(designer: Designer): TreeMaster {
let master = mastersMap.get(designer);
if (!master) {
master = new TreeMaster(designer);
mastersMap.set(designer, master);
}
return master;
}
export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
private _designer?: Designer; private _designer?: Designer;
@obx.ref private _master?: TreeMaster; @obx.ref private _master?: TreeMaster;
get master() { get master() {
@ -130,8 +36,11 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
} }
readonly id = uniqueId('outline'); readonly id = uniqueId('outline');
private fixed = false; @obx.ref _visible: boolean = false;
constructor(readonly editor: Editor, at?: string) { get visible() {
return this._visible;
}
constructor(readonly editor: IEditor, readonly at: string | Symbol) {
let inited = false; let inited = false;
const setup = async () => { const setup = async () => {
if (inited) { if (inited) {
@ -142,29 +51,18 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
this.setupDesigner(designer); this.setupDesigner(designer);
}; };
// FIXME: dirty connect to others if (at === Backup) {
if (at === '__IN_SETTINGS__') {
setup(); setup();
} else { } else {
editor.on('skeleton.panel.show', (key: string) => { editor.on('skeleton.panel.show', (key: string) => {
if (key === at) { if (key === at) {
setup(); setup();
if (this.master) { this._visible = true;
this.master.setFixed(this);
} else {
this.fixed = true;
}
document.documentElement.classList.add('lowcode-has-fixed-tree');
} }
}); });
editor.on('skeleton.panel.hide', (key: string) => { editor.on('skeleton.panel.hide', (key: string) => {
if (key === at) { if (key === at) {
document.documentElement.classList.remove('lowcode-has-fixed-tree'); this._visible = false;
if (this.master) {
this.master.unFixed(this);
} else {
this.fixed = false;
}
} }
}); });
} }
@ -628,9 +526,6 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
this._designer = designer; this._designer = designer;
this._master = getTreeMaster(designer); this._master = getTreeMaster(designer);
this._master.addBoard(this); this._master.addBoard(this);
if (this.fixed) {
this._master.setFixed(this);
}
designer.dragon.addSensor(this); designer.dragon.addSensor(this);
this.scroller = designer.createScroller(this); this.scroller = designer.createScroller(this);
} }

View File

@ -0,0 +1,93 @@
import { computed, obx } from '@ali/lowcode-editor-core';
import { Designer, isLocationChildrenDetail } from '@ali/lowcode-designer';
import TreeNode from './tree-node';
import { Tree } from './tree';
import { Backup } from './views/backup-pane';
export interface ITreeBoard {
readonly visible: boolean;
readonly at: string | Symbol;
scrollToNode(treeNode: TreeNode, detail?: any): void;
}
export class TreeMaster {
constructor(readonly designer: Designer) {
designer.dragon.onDragstart(() => {
// needs?
this.toVision();
});
designer.activeTracker.onChange(({ node, detail }) => {
const tree = this.currentTree;
if (!tree || node.document !== tree.document) {
return;
}
const treeNode = tree.getTreeNode(node);
if (detail && isLocationChildrenDetail(detail)) {
treeNode.expand(true);
} else {
treeNode.expandParents();
}
this.boards.forEach((board) => {
board.scrollToNode(treeNode, detail);
});
});
}
private toVision() {
const tree = this.currentTree;
if (tree) {
tree.document.selection.getTopNodes().forEach((node) => {
tree.getTreeNode(node).setExpanded(false);
});
}
}
@obx.val private boards = new Set<ITreeBoard>();
addBoard(board: ITreeBoard) {
this.boards.add(board);
}
removeBoard(board: ITreeBoard) {
this.boards.delete(board);
}
@computed hasVisibleTreeBoard() {
for (const item of this.boards) {
if (item.visible && item.at !== Backup) {
return true;
}
}
return false;
}
purge() {
// todo others purge
}
private treeMap = new Map<string, Tree>();
@computed get currentTree(): Tree | null {
const doc = this.designer?.currentDocument;
if (doc) {
const id = doc.id;
if (this.treeMap.has(id)) {
return this.treeMap.get(id)!;
}
const tree = new Tree(doc);
// TODO: listen purge event to remove
this.treeMap.set(id, tree);
return tree;
}
return null;
}
}
const mastersMap = new Map<Designer, TreeMaster>();
export function getTreeMaster(designer: Designer): TreeMaster {
let master = mastersMap.get(designer);
if (!master) {
master = new TreeMaster(designer);
mastersMap.set(designer, master);
}
return master;
}

View File

@ -1,28 +1,16 @@
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { PluginProps } from '@ali/lowcode-types'; import { PluginProps } from '@ali/lowcode-types';
import OutlinePane from './pane'; import { OutlinePane } from './pane';
export const Backup = Symbol.for('backup-outline');
export class OutlineBackupPane extends PureComponent<PluginProps> { export class OutlineBackupPane extends PureComponent<PluginProps> {
state = {
outlineInited: false,
};
private dispose = this.props.main.onceOutlineVisible(() => {
this.setState({
outlineInited: true,
});
});
componentWillUnmount() {
this.dispose();
}
render() { render() {
if (!this.state.outlineInited) {
return null;
}
return ( return (
<OutlinePane <OutlinePane
editor={this.props.main.editor} editor={this.props.editor}
config={{ config={{
name: '__IN_SETTINGS__', name: Backup,
}} }}
/> />
); );

View File

@ -4,13 +4,11 @@ import { intl } from '../locale';
import { OutlineMain } from '../main'; import { OutlineMain } from '../main';
import TreeView from './tree'; import TreeView from './tree';
import './style.less'; import './style.less';
import { IEditor } from '@ali/lowcode-types';
@observer @observer
export default class OutlinePane extends Component<{ config: any; editor: any; inSettings?: boolean }> { export class OutlinePane extends Component<{ config: any; editor: IEditor }> {
private main = new OutlineMain( private main = new OutlineMain(this.props.editor, this.props.config.name || this.props.config.pluginKey);
this.props.editor,
this.props.config.name || this.props.config.pluginKey,
);
shouldComponentUpdate() { shouldComponentUpdate() {
return false; return false;

View File

@ -2,6 +2,8 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
position: relative; position: relative;
z-index: 20;
background-color: white;
> .lc-outline-tree-container { > .lc-outline-tree-container {
top: 0; top: 0;

View File

@ -139,8 +139,6 @@ export interface Utils {
export interface PluginProps { export interface PluginProps {
editor: IEditor; editor: IEditor;
config: PluginConfig; config: PluginConfig;
i18n?: I18nFunction;
ref?: RefObject<ReactElement>;
[key: string]: any; [key: string]: any;
} }

View File

@ -732,11 +732,9 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
const supports: any = {}; const supports: any = {};
if (canUseCondition != null) { if (canUseCondition != null) {
console.info('canUseCondition', componentName);
supports.condition = canUseCondition; supports.condition = canUseCondition;
} }
if (canLoop != null) { if (canLoop != null) {
console.info('canLoop', componentName);
supports.loop = canLoop; supports.loop = canLoop;
} }
meta.configure = { props, component, supports }; meta.configure = { props, component, supports };

View File

@ -2,7 +2,7 @@ import { isJSBlock, isJSExpression, isJSSlot } from '@ali/lowcode-types';
import { isPlainObject } from '@ali/lowcode-utils'; import { isPlainObject } from '@ali/lowcode-utils';
import { globalContext, Editor } from '@ali/lowcode-editor-core'; import { globalContext, Editor } from '@ali/lowcode-editor-core';
import { Designer, TransformStage, addBuiltinComponentAction } from '@ali/lowcode-designer'; import { Designer, TransformStage, addBuiltinComponentAction } from '@ali/lowcode-designer';
import Outline from '@ali/lowcode-plugin-outline-pane'; import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
import { toCss } from '@ali/vu-css-style'; import { toCss } from '@ali/vu-css-style';
import DesignerPlugin from '@ali/lowcode-plugin-designer'; import DesignerPlugin from '@ali/lowcode-plugin-designer';
@ -16,9 +16,11 @@ globalContext.register(editor, Editor);
export const skeleton = new Skeleton(editor); export const skeleton = new Skeleton(editor);
editor.set(Skeleton, skeleton); editor.set(Skeleton, skeleton);
editor.set('skeleton', skeleton);
export const designer = new Designer({ editor: editor }); export const designer = new Designer({ editor: editor });
editor.set(Designer, designer); editor.set(Designer, designer);
editor.set('designer', designer);
// 节点 props 初始化 // 节点 props 初始化
designer.addPropsReducer((props, node) => { designer.addPropsReducer((props, node) => {
@ -146,6 +148,17 @@ skeleton.add({
area: 'leftFixedArea', area: 'leftFixedArea',
}, },
}); });
skeleton.add({
area: 'rightArea',
name: 'backupOutline',
type: 'Panel',
props: {
condition: () => {
return designer.dragon.dragging && !getTreeMaster(designer).hasVisibleTreeBoard();
}
},
content: OutlineBackupPane,
});
// skeleton.add({ // skeleton.add({
// name: 'sourceEditor', // name: 'sourceEditor',

View File

@ -59,8 +59,6 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
title, title,
description, description,
align: place, align: place,
onInit: init,
onDestroy: destroy,
}, },
contentProps: props, contentProps: props,
index: index || props?.index, index: index || props?.index,
@ -84,6 +82,8 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
maxWidth, maxWidth,
height, height,
maxHeight, maxHeight,
onInit: init,
onDestroy: destroy,
}; };
if (contents && Array.isArray(contents)) { if (contents && Array.isArray(contents)) {
@ -100,7 +100,10 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
}); });
} }
} }
} else if (type === 'action') { } else {
newConfig.props.onInit = init;
newConfig.props.onDestroy = destroy;
if (type === 'action') {
newConfig.area = 'top'; newConfig.area = 'top';
newConfig.type = 'Dock'; newConfig.type = 'Dock';
} else if (type === 'tab') { } else if (type === 'tab') {
@ -113,6 +116,7 @@ function upgradeConfig(config: OldPaneConfig): IWidgetBaseConfig & { area: strin
newConfig.area = 'main'; newConfig.area = 'main';
newConfig.type = 'Widget'; newConfig.type = 'Widget';
} }
}
return newConfig; return newConfig;
} }