@leafer-in/editor
Version:
447 lines (334 loc) • 14.6 kB
text/typescript
import { IGroupInputData, IUI, IEventListenerId, IPointData, ILeafList, IEditSize, IGroup, IObject, IAlign, IAxis, IFunction, IMatrix, IApp, ILeaferMode } from '@leafer-ui/interface'
import { Group, DataHelper, LeafList, RenderEvent, LeafHelper, Direction9, Plugin, isString, PropertyEvent, LeaferEvent, isArray } from '@leafer-ui/draw'
import { DragEvent, RotateEvent, ZoomEvent, MoveEvent, useModule } from '@leafer-ui/core'
import { IEditBox, IEditPoint, IEditor, IEditorConfig, IEditTool, IEditorScaleEvent, IInnerEditor, ISimulateElement } from '@leafer-in/interface'
import { EditSelect } from './display/EditSelect'
import { EditBox } from './display/EditBox'
import { EditMask } from './display/EditMask'
import { config } from './config'
import { onTarget, onHover } from './editor/target'
import { targetAttr, mergeConfigAttr } from './decorator/data'
import { EditorHelper } from './helper/EditorHelper'
import { simulate } from './editor/simulate'
import { EditToolCreator } from './tool/EditToolCreator'
import { InnerEditorEvent } from './event/InnerEditorEvent'
import { EditorGroupEvent } from './event/EditorGroupEvent'
import { SimulateElement } from './display/SimulateElement'
import { TransformTool } from './tool/TransformTool'
export class Editor extends Group implements IEditor {
public config: IEditorConfig
readonly mergeConfig: IEditorConfig
readonly mergedConfig: IEditorConfig
public target?: IUI | IUI[]
public hoverTarget?: IUI
public dimTarget?: IGroup | IGroup[] // 需要淡化的容器
// 列表
public leafList: ILeafList = new LeafList() // from target
public get list(): IUI[] { return this.leafList.list as IUI[] }
public get dragHoverExclude(): IUI[] { return [this.editBox.rect] }
public openedGroupList: ILeafList = new LeafList()
// 状态
public get editing(): boolean { return !!this.list.length }
public innerEditing: boolean
public get groupOpening(): boolean { return !!this.openedGroupList.length }
public resizeDirection?: Direction9
public get multiple(): boolean { return this.list.length > 1 }
public get single(): boolean { return this.list.length === 1 }
public get dragPoint(): IEditPoint { return this.editBox.dragPoint }
public get dragging(): boolean { return this.editBox.dragging }
public get gesturing(): boolean { return this.editBox.gesturing } // 手势操作元素中
public get moving(): boolean { return this.editBox.moving }
public get resizing(): boolean { return this.editBox.resizing }
public get rotating(): boolean { return this.editBox.rotating }
public get skewing(): boolean { return this.editBox.skewing }
// 组件
public get element() { return this.multiple ? this.simulateTarget : this.list[0] as ISimulateElement }
public simulateTarget: ISimulateElement = new SimulateElement(this)
public editBox: IEditBox = new EditBox(this)
public get buttons() { return this.editBox.buttons }
public editTool?: IEditTool
public innerEditor?: IInnerEditor
public editToolList: IObject = {}
public selector: EditSelect = new EditSelect(this)
public editMask: EditMask = new EditMask(this)
public hasDimOthers?: boolean
public get targetLeafer() { const first = this.list[0]; return first && first.leafer }
public targetChanged: boolean
public targetEventIds: IEventListenerId[] = []
constructor(userConfig?: IEditorConfig, data?: IGroupInputData) {
super(data)
let mergedConfig: IEditorConfig = DataHelper.clone(config)
if (userConfig) mergedConfig = DataHelper.default(userConfig, mergedConfig)
this.mergedConfig = this.config = mergedConfig
this.addMany(this.editMask, this.selector, this.editBox)
if (!Plugin.has('resize')) this.config.editSize = 'scale'
}
// select
public select(target: IUI | IUI[]): void {
this.target = target
}
public cancel(): void {
this.target = null
}
// item
public hasItem(item: IUI): boolean {
return this.leafList.has(item)
}
public getItem(index?: number): IUI {
return this.list[index || 0]
}
public addItem(item: IUI): void {
if (!this.hasItem(item) && !item.locked) this.leafList.add(item), this.target = this.leafList.list as IUI[]
}
public removeItem(item: IUI): void {
if (this.hasItem(item)) this.leafList.remove(item), this.target = this.leafList.list as IUI[]
}
public shiftItem(item: IUI): void {
this.hasItem(item) ? this.removeItem(item) : this.addItem(item)
}
// 淡化 / 突出
public setDimOthers(value: boolean | number, attrName: 'bright' | 'dim' = 'dim', list?: IUI[]): void {
if (!list) {
const { dimTarget, targetLeafer } = this
list = dimTarget ? (isArray(dimTarget) ? dimTarget : [dimTarget]) : [targetLeafer]
}
if (list[0] && list[0][attrName] !== (value || false)) list.forEach(item => DataHelper.stintSet(item, attrName, value))
}
public setBright(value: boolean): void {
this.setDimOthers(value, 'bright', this.list)
}
public cancelDimOthers(): void {
this.setDimOthers(false)
this.setBright(false)
this.hasDimOthers = undefined
}
// update
public update(): void {
if (this.editing) {
if (!this.element.parent) return this.cancel()
if (this.innerEditing) this.innerEditor.update()
this.editTool.update()
this.selector.update()
}
}
public updateEditBox(): void {
if (this.multiple) simulate(this)
this.update()
}
// editTool
public getEditTool(name: string): IEditTool {
return this.editToolList[name] = this.editToolList[name] || EditToolCreator.get(name, this)
}
public updateEditTool(): void {
this.unloadEditTool()
if (this.editing) {
const target = this.element
let name = target.editOuter || 'EditTool'
const { beforeEditOuter } = this.mergeConfig
if (beforeEditOuter) {
const check = beforeEditOuter({ target, name })
if (isString(check)) name = check
else if (check === false) return
}
if (EditToolCreator.list[name]) {
const tool = this.editTool = this.getEditTool(name)
this.editBox.load()
tool.load()
this.update()
}
}
}
public unloadEditTool(): void {
let tool = this.editTool
if (tool) {
this.editBox.unload()
tool.unload()
this.editTool = null
}
}
// get
public getEditSize(_ui: IUI): IEditSize {
return this.mergeConfig.editSize
}
// TransformTool will rewrite -----
// operate
public onMove(_e: DragEvent | MoveEvent): void { }
public onScale(_e: DragEvent | ZoomEvent): void { }
public onRotate(_e: DragEvent | RotateEvent): void { }
public onSkew(_e: DragEvent): void { }
// transform
public move(_x: number | IPointData, _y = 0): void { }
public scaleWithDrag(_data: IEditorScaleEvent): void { }
override scaleOf(_origin: IPointData | IAlign, scaleX: number, _scaleY = scaleX, _resize?: boolean): void { }
override flip(_axis: IAxis): void { }
override rotateOf(_origin: IPointData | IAlign, _rotation: number): void { }
override skewOf(_origin: IPointData | IAlign, _skewX: number, _skewY = 0, _resize?: boolean): void { }
public checkTransform(_type: 'moveable' | 'resizeable' | 'rotateable' | 'skewable'): boolean { return undefined }
protected getWorldOrigin(_origin: IPointData | IAlign): IPointData { return undefined }
protected getChangedTransform(_func: IFunction): IMatrix { return undefined }
// --------
// group
public group(userGroup?: IGroup | IGroupInputData): IGroup {
if (this.multiple) {
this.emitGroupEvent(EditorGroupEvent.BEFORE_GROUP)
this.target = EditorHelper.group(this.list, this.element, userGroup)
this.emitGroupEvent(EditorGroupEvent.GROUP, this.target as IGroup)
}
return this.target as IGroup
}
public ungroup(): IUI[] {
const { list } = this
if (list.length) {
list.forEach(item => item.isBranch && this.emitGroupEvent(EditorGroupEvent.BEFORE_UNGROUP, item as IGroup))
this.target = EditorHelper.ungroup(list)
list.forEach(item => item.isBranch && this.emitGroupEvent(EditorGroupEvent.UNGROUP, item as IGroup))
}
return this.list
}
public openGroup(group: IGroup): void {
this.emitGroupEvent(EditorGroupEvent.BEFORE_OPEN, group)
this.openedGroupList.add(group)
group.hitChildren = true
this.emitGroupEvent(EditorGroupEvent.OPEN, group)
}
public closeGroup(group: IGroup): void {
this.emitGroupEvent(EditorGroupEvent.BEFORE_CLOSE, group)
this.openedGroupList.remove(group)
group.hitChildren = false
this.emitGroupEvent(EditorGroupEvent.CLOSE, group)
}
public checkOpenedGroups(): void {
const opened = this.openedGroupList
if (opened.length) {
let { list } = opened
if (this.editing) list = [], opened.forEach(item => this.list.every(leaf => !LeafHelper.hasParent(leaf, item)) && list.push(item))
list.forEach(item => this.closeGroup(item as IGroup))
}
if (this.editing && !this.selector.dragging) this.checkDeepSelect()
}
public checkDeepSelect(): void {
let parent: IGroup, { list } = this
for (let i = 0; i < list.length; i++) {
parent = list[i].parent
while (parent && !parent.hitChildren) {
this.openGroup(parent)
parent = parent.parent
}
}
}
public emitGroupEvent(type: string, group?: IGroup): void {
const event = new EditorGroupEvent(type, { editTarget: group })
this.emitEvent(event)
if (group) group.emitEvent(event)
}
// inner
public getInnerEditor(name: string): IInnerEditor {
return this.editToolList[name] = this.editToolList[name] || EditToolCreator.get(name, this)
}
public openInnerEditor(target?: IUI, nameOrSelect?: string | boolean, select?: boolean): void {
let name: string
if (isString(nameOrSelect)) name = nameOrSelect
else if (!select) select = nameOrSelect
if (target && select) this.target = target
if (this.single) {
if (!target) target = this.element
if (!name) name = target.editInner
const { beforeEditInner } = this.mergeConfig
if (beforeEditInner) {
const check = beforeEditInner({ target, name })
if (isString(check)) name = check
else if (check === false) return
}
if (EditToolCreator.list[name]) {
this.editTool.unload()
this.innerEditing = true
this.innerEditor = this.getInnerEditor(name)
this.innerEditor.editTarget = target
this.emitInnerEvent(InnerEditorEvent.BEFORE_OPEN)
this.innerEditor.load()
this.emitInnerEvent(InnerEditorEvent.OPEN)
}
}
}
public closeInnerEditor(onlyInnerEditor?: boolean): void {
if (this.innerEditing) {
this.innerEditing = false
this.emitInnerEvent(InnerEditorEvent.BEFORE_CLOSE)
this.innerEditor.unload()
this.emitInnerEvent(InnerEditorEvent.CLOSE)
if (!onlyInnerEditor) this.updateEditTool()
this.innerEditor = null
}
}
public emitInnerEvent(type: string): void {
const { innerEditor } = this, { editTarget } = innerEditor
const event = new InnerEditorEvent(type, { editTarget, innerEditor })
this.emitEvent(event)
editTarget.emitEvent(event)
}
// lock
public lock(): void {
this.list.forEach(leaf => leaf.locked = true)
this.update()
}
public unlock(): void {
this.list.forEach(leaf => leaf.locked = false)
this.update()
}
// level
public toTop(): void {
if (this.list.length) {
EditorHelper.toTop(this.list)
this.leafList.update()
}
}
public toBottom(): void {
if (this.list.length) {
EditorHelper.toBottom(this.list)
this.leafList.update()
}
}
protected onAppRenderStart(app: IApp): void {
if (this.targetChanged = app.children.some(leafer => leafer !== this.leafer && leafer.renderer.changed)) this.editBox.forceRender()
}
protected onRenderStart(): void {
if (this.targetChanged) this.update()
}
protected onChildScroll(): void {
if (this.multiple) this.updateEditBox()
}
// event
public listenTargetEvents(): void {
if (!this.targetEventIds.length) {
const { app, leafer, targetLeafer, editMask } = this
this.targetEventIds = [
leafer.on_(RenderEvent.START, this.onRenderStart, this),
targetLeafer && targetLeafer.on_(PropertyEvent.SCROLL, this.onChildScroll, this),
app.on_(RenderEvent.CHILD_START, this.onAppRenderStart, this),
app.on_(LeaferEvent.UPDATE_MODE, (data: { mode: ILeaferMode }) => { if (data.mode && data.mode !== 'normal') this.cancel() })
]
if (editMask.visible) editMask.forceRender()
}
}
public removeTargetEvents(): void {
const { targetEventIds, editMask } = this
if (targetEventIds.length) {
this.off_(targetEventIds)
if (editMask.visible) editMask.forceRender()
}
}
public destroy(): void {
if (!this.destroyed) {
this.target = this.hoverTarget = null
Object.values(this.editToolList).forEach(item => item.destroy())
this.simulateTarget.destroy()
this.editToolList = {}
this.simulateTarget = this.editTool = this.innerEditor = null
super.destroy()
}
}
}