feat:add context menu
This commit is contained in:
@@ -193,4 +193,80 @@
|
|||||||
z-index: 9;
|
z-index: 9;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border: 1px dotted #000000;
|
border: 1px dotted #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-container {
|
||||||
|
z-index: 9;
|
||||||
|
position: fixed;
|
||||||
|
display: none;
|
||||||
|
padding: 4px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 2px 12px 0 rgb(56 56 56 / 20%);
|
||||||
|
border: 1px solid #e2e6ed;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-content .contextmenu-sub-item::after {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
right: 12px;
|
||||||
|
background: url(../images/submenu-dropdown.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-content .contextmenu-item {
|
||||||
|
width: 180px;
|
||||||
|
padding: 0 32px 0 16px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-content .contextmenu-item.hover {
|
||||||
|
background: rgba(25, 55, 88, .04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-content .contextmenu-item span {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #3d4757;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-content .contextmenu-item span.shortcut {
|
||||||
|
color: #767c85;
|
||||||
|
height: 30px;
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-content .contextmenu-item i {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-divider {
|
||||||
|
background-color: #e2e6ed;
|
||||||
|
margin: 4px 16px;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextmenu-print {
|
||||||
|
background-image: url(../../assets/images/print.svg);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M12 4h-1V2H5v2H4V2a1 1 0 011-1h6a1 1 0 011 1v2zm0 5v4a1 1 0 01-1 1H5a1 1 0 01-1-1V9h1v4h6V9h1z"/><path d="M12 12v-1h2V5H2v6h2v1H2a1 1 0 01-1-1V5a1 1 0 011-1h12a1 1 0 011 1v6a1 1 0 01-1 1h-2z"/><path d="M3 8h10v1H3zm8-2h2v1h-2z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 369 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M0 0h16v16H0z"/><g fill="#767C85"><path d="M7 12.243l-.707-.707 4.243-4.243.707.707z"/><path d="M6.293 4.464L7 3.757 11.243 8l-.707.707z"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 260 B |
@@ -4,6 +4,10 @@ import { CommandAdapt } from "./CommandAdapt"
|
|||||||
|
|
||||||
export class Command {
|
export class Command {
|
||||||
|
|
||||||
|
private static cut: Function
|
||||||
|
private static copy: Function
|
||||||
|
private static paste: Function
|
||||||
|
private static selectAll: Function
|
||||||
private static undo: Function
|
private static undo: Function
|
||||||
private static redo: Function
|
private static redo: Function
|
||||||
private static painter: Function
|
private static painter: Function
|
||||||
@@ -30,6 +34,10 @@ export class Command {
|
|||||||
private static pageScaleAdd: Function
|
private static pageScaleAdd: Function
|
||||||
|
|
||||||
constructor(adapt: CommandAdapt) {
|
constructor(adapt: CommandAdapt) {
|
||||||
|
Command.cut = adapt.cut.bind(adapt)
|
||||||
|
Command.copy = adapt.copy.bind(adapt)
|
||||||
|
Command.paste = adapt.paste.bind(adapt)
|
||||||
|
Command.selectAll = adapt.selectAll.bind(adapt)
|
||||||
Command.undo = adapt.undo.bind(adapt)
|
Command.undo = adapt.undo.bind(adapt)
|
||||||
Command.redo = adapt.redo.bind(adapt)
|
Command.redo = adapt.redo.bind(adapt)
|
||||||
Command.painter = adapt.painter.bind(adapt)
|
Command.painter = adapt.painter.bind(adapt)
|
||||||
@@ -56,6 +64,23 @@ export class Command {
|
|||||||
Command.pageScaleAdd = adapt.pageScaleAdd.bind(adapt)
|
Command.pageScaleAdd = adapt.pageScaleAdd.bind(adapt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全局命令
|
||||||
|
public executeCut() {
|
||||||
|
return Command.cut()
|
||||||
|
}
|
||||||
|
|
||||||
|
public executeCopy() {
|
||||||
|
return Command.copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
public executePaste() {
|
||||||
|
return Command.paste()
|
||||||
|
}
|
||||||
|
|
||||||
|
public executeSelectAll() {
|
||||||
|
return Command.selectAll()
|
||||||
|
}
|
||||||
|
|
||||||
// 撤销、重做、格式刷、清除格式
|
// 撤销、重做、格式刷、清除格式
|
||||||
public executeUndo() {
|
public executeUndo() {
|
||||||
return Command.undo()
|
return Command.undo()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { getUUID } from "../../utils"
|
|||||||
import { formatElementList } from "../../utils/element"
|
import { formatElementList } from "../../utils/element"
|
||||||
import { printImageBase64 } from "../../utils/print"
|
import { printImageBase64 } from "../../utils/print"
|
||||||
import { Draw } from "../draw/Draw"
|
import { Draw } from "../draw/Draw"
|
||||||
|
import { CanvasEvent } from "../event/CanvasEvent"
|
||||||
import { HistoryManager } from "../history/HistoryManager"
|
import { HistoryManager } from "../history/HistoryManager"
|
||||||
import { Position } from "../position/Position"
|
import { Position } from "../position/Position"
|
||||||
import { RangeManager } from "../range/RangeManager"
|
import { RangeManager } from "../range/RangeManager"
|
||||||
@@ -25,6 +26,7 @@ export class CommandAdapt {
|
|||||||
private range: RangeManager
|
private range: RangeManager
|
||||||
private position: Position
|
private position: Position
|
||||||
private historyManager: HistoryManager
|
private historyManager: HistoryManager
|
||||||
|
private canvasEvent: CanvasEvent
|
||||||
private options: Required<IEditorOption>
|
private options: Required<IEditorOption>
|
||||||
|
|
||||||
constructor(draw: Draw) {
|
constructor(draw: Draw) {
|
||||||
@@ -32,15 +34,35 @@ export class CommandAdapt {
|
|||||||
this.range = draw.getRange()
|
this.range = draw.getRange()
|
||||||
this.position = draw.getPosition()
|
this.position = draw.getPosition()
|
||||||
this.historyManager = draw.getHistoryManager()
|
this.historyManager = draw.getHistoryManager()
|
||||||
|
this.canvasEvent = draw.getCanvasEvent()
|
||||||
this.options = draw.getOptions()
|
this.options = draw.getOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public cut() {
|
||||||
|
this.canvasEvent.cut()
|
||||||
|
}
|
||||||
|
|
||||||
|
public copy() {
|
||||||
|
this.canvasEvent.copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async paste() {
|
||||||
|
const text = await navigator.clipboard.readText()
|
||||||
|
if (text) {
|
||||||
|
this.canvasEvent.input(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectAll() {
|
||||||
|
this.canvasEvent.selectAll()
|
||||||
|
}
|
||||||
|
|
||||||
public undo() {
|
public undo() {
|
||||||
return this.historyManager.undo()
|
this.historyManager.undo()
|
||||||
}
|
}
|
||||||
|
|
||||||
public redo() {
|
public redo() {
|
||||||
return this.historyManager.redo()
|
this.historyManager.redo()
|
||||||
}
|
}
|
||||||
|
|
||||||
public painter() {
|
public painter() {
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
import { EDITOR_COMPONENT } from "../../dataset/constant/Editor"
|
||||||
|
import { EditorComponent } from "../../dataset/enum/Editor"
|
||||||
|
import { IContextMenuContext, IRegisterContextMenu } from "../../interface/contextmenu/ContextMenu"
|
||||||
|
import { findParent } from "../../utils"
|
||||||
|
import { Command } from "../command/Command"
|
||||||
|
import { Draw } from "../draw/Draw"
|
||||||
|
import { Position } from "../position/Position"
|
||||||
|
import { RangeManager } from "../range/RangeManager"
|
||||||
|
|
||||||
|
interface IRenderPayload {
|
||||||
|
contextMenuList: IRegisterContextMenu[];
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
parentMenuConatiner?: HTMLDivElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ContextMenu {
|
||||||
|
|
||||||
|
private command: Command
|
||||||
|
private range: RangeManager
|
||||||
|
private position: Position
|
||||||
|
private container: HTMLDivElement
|
||||||
|
private contextMenuList: IRegisterContextMenu[]
|
||||||
|
private contextMenuContainerList: HTMLDivElement[]
|
||||||
|
private contextMenuRelationShip: Map<HTMLDivElement, HTMLDivElement>
|
||||||
|
|
||||||
|
constructor(draw: Draw, command: Command) {
|
||||||
|
this.command = command
|
||||||
|
this.range = draw.getRange()
|
||||||
|
this.position = draw.getPosition()
|
||||||
|
this.container = draw.getContainer()
|
||||||
|
this.contextMenuList = []
|
||||||
|
this.contextMenuContainerList = []
|
||||||
|
this.contextMenuRelationShip = new Map()
|
||||||
|
// 接管菜单权限
|
||||||
|
document.addEventListener('contextmenu', this._proxyContextMenuEvent.bind(this))
|
||||||
|
// 副作用处理
|
||||||
|
document.addEventListener('mousedown', this._handleEffect.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private _proxyContextMenuEvent(evt: MouseEvent) {
|
||||||
|
const context = this._getContext()
|
||||||
|
let renderList: IRegisterContextMenu[] = []
|
||||||
|
let isRegisterContextMenu = false
|
||||||
|
for (let c = 0; c < this.contextMenuList.length; c++) {
|
||||||
|
const menu = this.contextMenuList[c]
|
||||||
|
if (menu.isDivider) {
|
||||||
|
renderList.push(menu)
|
||||||
|
} else {
|
||||||
|
const isMatch = menu.when?.(context)
|
||||||
|
if (isMatch) {
|
||||||
|
renderList.push(menu)
|
||||||
|
isRegisterContextMenu = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isRegisterContextMenu) {
|
||||||
|
this.dispose()
|
||||||
|
this._render({
|
||||||
|
contextMenuList: renderList,
|
||||||
|
left: evt.x,
|
||||||
|
top: evt.y,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
evt.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleEffect(evt: MouseEvent) {
|
||||||
|
if (this.contextMenuContainerList.length) {
|
||||||
|
// 点击非右键菜单内
|
||||||
|
const contextMenuDom = findParent(
|
||||||
|
evt.target as Element,
|
||||||
|
(node: Node & Element) => !!node && node.nodeType === 1
|
||||||
|
&& node.getAttribute(EDITOR_COMPONENT) === EditorComponent.CONTEXTMENU,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
if (!contextMenuDom) {
|
||||||
|
this.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getContext(): IContextMenuContext {
|
||||||
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
|
// 是否存在焦点
|
||||||
|
const editorTextFocus = startIndex !== 0 || endIndex !== 0
|
||||||
|
// 是否存在选区
|
||||||
|
const editorHasSelection = editorTextFocus && startIndex !== endIndex
|
||||||
|
// 是否在表格内
|
||||||
|
const positionContext = this.position.getPositionContext()
|
||||||
|
const isInTable = positionContext.isTable
|
||||||
|
return { editorHasSelection, editorTextFocus, isInTable }
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createContextMenuContainer(): HTMLDivElement {
|
||||||
|
const contextMenuContainer = document.createElement('div')
|
||||||
|
contextMenuContainer.classList.add('contextmenu-container')
|
||||||
|
contextMenuContainer.setAttribute(EDITOR_COMPONENT, EditorComponent.CONTEXTMENU)
|
||||||
|
this.container.append(contextMenuContainer)
|
||||||
|
return contextMenuContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
private _render(payload: IRenderPayload): HTMLDivElement {
|
||||||
|
const { contextMenuList, left, top, parentMenuConatiner } = payload
|
||||||
|
const contextMenuContainer = this._createContextMenuContainer()
|
||||||
|
const contextMenuContent = document.createElement('div')
|
||||||
|
contextMenuContent.classList.add('contextmenu-content')
|
||||||
|
// 直接子菜单
|
||||||
|
let childMenuContainer: HTMLDivElement | null = null
|
||||||
|
// 父菜单添加子菜单映射关系
|
||||||
|
if (parentMenuConatiner) {
|
||||||
|
this.contextMenuRelationShip.set(parentMenuConatiner, contextMenuContainer)
|
||||||
|
}
|
||||||
|
for (let c = 0; c < contextMenuList.length; c++) {
|
||||||
|
const menu = contextMenuList[c]
|
||||||
|
if (menu.isDivider) {
|
||||||
|
// 首尾分隔符不渲染
|
||||||
|
if (c !== 0 && c !== contextMenuList.length - 1) {
|
||||||
|
const divider = document.createElement('div')
|
||||||
|
divider.classList.add('contextmenu-divider')
|
||||||
|
contextMenuContent.append(divider)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const menuItem = document.createElement('div')
|
||||||
|
menuItem.classList.add('contextmenu-item')
|
||||||
|
// 菜单事件
|
||||||
|
if (menu.childMenus) {
|
||||||
|
menuItem.classList.add('contextmenu-sub-item')
|
||||||
|
menuItem.onmouseenter = () => {
|
||||||
|
this._setHoverStatus(menuItem, true)
|
||||||
|
this._removeSubMenu(contextMenuContainer)
|
||||||
|
// 子菜单
|
||||||
|
const subMenuRect = menuItem.getBoundingClientRect()
|
||||||
|
const left = subMenuRect.left + subMenuRect.width
|
||||||
|
const top = subMenuRect.top
|
||||||
|
childMenuContainer = this._render({
|
||||||
|
contextMenuList: menu.childMenus!,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
parentMenuConatiner: contextMenuContainer
|
||||||
|
})
|
||||||
|
}
|
||||||
|
menuItem.onmouseleave = (evt) => {
|
||||||
|
// 移动到子菜单选项选中状态不变化
|
||||||
|
if (!childMenuContainer || !childMenuContainer.contains(evt.relatedTarget as Node)) {
|
||||||
|
this._setHoverStatus(menuItem, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
menuItem.onmouseenter = () => {
|
||||||
|
this._setHoverStatus(menuItem, true)
|
||||||
|
this._removeSubMenu(contextMenuContainer)
|
||||||
|
}
|
||||||
|
menuItem.onmouseleave = () => {
|
||||||
|
this._setHoverStatus(menuItem, false)
|
||||||
|
}
|
||||||
|
menuItem.onclick = () => {
|
||||||
|
if (menu.callback) {
|
||||||
|
menu.callback(this.command)
|
||||||
|
}
|
||||||
|
this.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 图标
|
||||||
|
const icon = document.createElement('i')
|
||||||
|
menuItem.append(icon)
|
||||||
|
if (menu.icon) {
|
||||||
|
icon.classList.add(`contextmenu-${menu.icon}`)
|
||||||
|
}
|
||||||
|
// 文本
|
||||||
|
const span = document.createElement('span')
|
||||||
|
span.append(document.createTextNode(menu.name!))
|
||||||
|
menuItem.append(span)
|
||||||
|
// 快捷方式提示
|
||||||
|
if (menu.shortCut) {
|
||||||
|
const span = document.createElement('span')
|
||||||
|
span.classList.add('shortcut')
|
||||||
|
span.append(document.createTextNode(menu.shortCut))
|
||||||
|
menuItem.append(span)
|
||||||
|
}
|
||||||
|
contextMenuContent.append(menuItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contextMenuContainer.append(contextMenuContent)
|
||||||
|
contextMenuContainer.style.display = 'block'
|
||||||
|
contextMenuContainer.style.left = `${left}px`
|
||||||
|
contextMenuContainer.style.top = `${top}px`
|
||||||
|
this.contextMenuContainerList.push(contextMenuContainer)
|
||||||
|
return contextMenuContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeSubMenu(payload: HTMLDivElement) {
|
||||||
|
const childMenu = this.contextMenuRelationShip.get(payload)
|
||||||
|
if (childMenu) {
|
||||||
|
this._removeSubMenu(childMenu)
|
||||||
|
childMenu.remove()
|
||||||
|
this.contextMenuRelationShip.delete(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setHoverStatus(payload: HTMLDivElement, status: boolean) {
|
||||||
|
if (status) {
|
||||||
|
payload.parentNode?.querySelectorAll('.contextmenu-item')
|
||||||
|
.forEach(child => child.classList.remove('hover'))
|
||||||
|
payload.classList.add('hover')
|
||||||
|
} else {
|
||||||
|
payload.classList.remove('hover')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerContextMenuList(payload: IRegisterContextMenu[]) {
|
||||||
|
this.contextMenuList.push(...payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
this.contextMenuContainerList.forEach(child => child.remove())
|
||||||
|
this.contextMenuContainerList = []
|
||||||
|
this.contextMenuRelationShip.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { IRegisterContextMenu } from "../../../interface/contextmenu/ContextMenu"
|
||||||
|
import { Command } from "../../command/Command"
|
||||||
|
|
||||||
|
export const globalMenus: IRegisterContextMenu[] = [
|
||||||
|
{
|
||||||
|
name: '剪切',
|
||||||
|
shortCut: 'Ctrl + X',
|
||||||
|
when: (payload) => {
|
||||||
|
return payload.editorHasSelection
|
||||||
|
},
|
||||||
|
callback: (command: Command) => {
|
||||||
|
command.executeCut()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '复制',
|
||||||
|
shortCut: 'Ctrl + C',
|
||||||
|
when: (payload) => {
|
||||||
|
return payload.editorHasSelection
|
||||||
|
},
|
||||||
|
callback: (command: Command) => {
|
||||||
|
command.executeCopy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '粘贴',
|
||||||
|
shortCut: 'Ctrl + V',
|
||||||
|
when: (payload) => {
|
||||||
|
return payload.editorTextFocus
|
||||||
|
},
|
||||||
|
callback: (command: Command) => {
|
||||||
|
command.executePaste()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '全选',
|
||||||
|
shortCut: 'Ctrl + A',
|
||||||
|
when: (payload) => {
|
||||||
|
return payload.editorTextFocus
|
||||||
|
},
|
||||||
|
callback: (command: Command) => {
|
||||||
|
command.executeSelectAll()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isDivider: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'print',
|
||||||
|
name: '打印',
|
||||||
|
when: () => true,
|
||||||
|
callback: (command: Command) => {
|
||||||
|
command.executePrint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -40,7 +40,11 @@ export class CursorAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _paste(evt: ClipboardEvent) {
|
private _paste(evt: ClipboardEvent) {
|
||||||
this.canvasEvent.paste(evt)
|
const text = evt.clipboardData?.getData('text')
|
||||||
|
if (text) {
|
||||||
|
this.canvasEvent.input(text)
|
||||||
|
}
|
||||||
|
evt.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compositionstart() {
|
private _compositionstart() {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export class Draw {
|
|||||||
private elementList: IElement[]
|
private elementList: IElement[]
|
||||||
private listener: Listener
|
private listener: Listener
|
||||||
|
|
||||||
|
private canvasEvent: CanvasEvent
|
||||||
private cursor: Cursor
|
private cursor: Cursor
|
||||||
private range: RangeManager
|
private range: RangeManager
|
||||||
private margin: Margin
|
private margin: Margin
|
||||||
@@ -93,10 +94,10 @@ export class Draw {
|
|||||||
this.pageNumber = new PageNumber(this)
|
this.pageNumber = new PageNumber(this)
|
||||||
new GlobalObserver(this)
|
new GlobalObserver(this)
|
||||||
|
|
||||||
const canvasEvent = new CanvasEvent(this)
|
this.canvasEvent = new CanvasEvent(this)
|
||||||
this.cursor = new Cursor(this, canvasEvent)
|
this.cursor = new Cursor(this, this.canvasEvent)
|
||||||
canvasEvent.register()
|
this.canvasEvent.register()
|
||||||
const globalEvent = new GlobalEvent(this, canvasEvent)
|
const globalEvent = new GlobalEvent(this, this.canvasEvent)
|
||||||
globalEvent.register()
|
globalEvent.register()
|
||||||
|
|
||||||
this.rowList = []
|
this.rowList = []
|
||||||
@@ -225,6 +226,10 @@ export class Draw {
|
|||||||
return this.elementList
|
return this.elementList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCanvasEvent(): CanvasEvent {
|
||||||
|
return this.canvasEvent
|
||||||
|
}
|
||||||
|
|
||||||
public getListener(): Listener {
|
public getListener(): Listener {
|
||||||
return this.listener
|
return this.listener
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ZERO } from "../../dataset/constant/Common"
|
import { ZERO } from "../../dataset/constant/Common"
|
||||||
import { ElementStyleKey } from "../../dataset/enum/ElementStyle"
|
import { ElementStyleKey } from "../../dataset/enum/ElementStyle"
|
||||||
|
import { MouseEventButton } from "../../dataset/enum/Event"
|
||||||
import { KeyMap } from "../../dataset/enum/Keymap"
|
import { KeyMap } from "../../dataset/enum/Keymap"
|
||||||
import { IElement } from "../../interface/Element"
|
import { IElement } from "../../interface/Element"
|
||||||
import { writeTextByElementList } from "../../utils/clipboard"
|
import { writeTextByElementList } from "../../utils/clipboard"
|
||||||
@@ -106,6 +107,7 @@ export class CanvasEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public mousedown(evt: MouseEvent) {
|
public mousedown(evt: MouseEvent) {
|
||||||
|
if (evt.button === MouseEventButton.RIGHT) return
|
||||||
const target = evt.target as HTMLDivElement
|
const target = evt.target as HTMLDivElement
|
||||||
const pageIndex = target.dataset.index
|
const pageIndex = target.dataset.index
|
||||||
// 设置pageNo
|
// 设置pageNo
|
||||||
@@ -301,24 +303,11 @@ export class CanvasEvent {
|
|||||||
this.historyManager.redo()
|
this.historyManager.redo()
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
} else if (evt.ctrlKey && evt.key === KeyMap.C) {
|
} else if (evt.ctrlKey && evt.key === KeyMap.C) {
|
||||||
if (!isCollspace) {
|
this.copy()
|
||||||
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
|
||||||
}
|
|
||||||
} else if (evt.ctrlKey && evt.key === KeyMap.X) {
|
} else if (evt.ctrlKey && evt.key === KeyMap.X) {
|
||||||
if (!isCollspace) {
|
this.cut()
|
||||||
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
|
||||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
|
||||||
const curIndex = startIndex
|
|
||||||
this.range.setRange(curIndex, curIndex)
|
|
||||||
this.draw.render({ curIndex })
|
|
||||||
}
|
|
||||||
} else if (evt.ctrlKey && evt.key === KeyMap.A) {
|
} else if (evt.ctrlKey && evt.key === KeyMap.A) {
|
||||||
this.range.setRange(0, position.length - 1)
|
this.selectAll()
|
||||||
this.draw.render({
|
|
||||||
isSubmitHistory: false,
|
|
||||||
isSetCursor: false,
|
|
||||||
isComputeRowList: false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,6 +315,7 @@ export class CanvasEvent {
|
|||||||
if (!this.cursor) return
|
if (!this.cursor) return
|
||||||
const cursorPosition = this.position.getCursorPosition()
|
const cursorPosition = this.position.getCursorPosition()
|
||||||
if (!data || !cursorPosition || this.isCompositing) return
|
if (!data || !cursorPosition || this.isCompositing) return
|
||||||
|
const text = data.replaceAll(`\n`, ZERO)
|
||||||
const elementList = this.draw.getElementList()
|
const elementList = this.draw.getElementList()
|
||||||
const agentDom = this.cursor.getAgentDom()
|
const agentDom = this.cursor.getAgentDom()
|
||||||
agentDom.value = ''
|
agentDom.value = ''
|
||||||
@@ -339,7 +329,7 @@ export class CanvasEvent {
|
|||||||
const { tdId, trId, tableId } = positionContext
|
const { tdId, trId, tableId } = positionContext
|
||||||
restArg = { tdId, trId, tableId }
|
restArg = { tdId, trId, tableId }
|
||||||
}
|
}
|
||||||
const inputData: IElement[] = data.split('').map(value => ({
|
const inputData: IElement[] = text.split('').map(value => ({
|
||||||
value,
|
value,
|
||||||
...restArg
|
...restArg
|
||||||
}))
|
}))
|
||||||
@@ -359,10 +349,34 @@ export class CanvasEvent {
|
|||||||
this.draw.render({ curIndex })
|
this.draw.render({ curIndex })
|
||||||
}
|
}
|
||||||
|
|
||||||
public paste(evt: ClipboardEvent) {
|
public cut() {
|
||||||
const text = evt.clipboardData?.getData('text')
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
this.input(text?.replaceAll(`\n`, ZERO) || '')
|
const elementList = this.draw.getElementList()
|
||||||
evt.preventDefault()
|
if (startIndex !== endIndex) {
|
||||||
|
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
||||||
|
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||||
|
const curIndex = startIndex
|
||||||
|
this.range.setRange(curIndex, curIndex)
|
||||||
|
this.draw.render({ curIndex })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public copy() {
|
||||||
|
const { startIndex, endIndex } = this.range.getRange()
|
||||||
|
const elementList = this.draw.getElementList()
|
||||||
|
if (startIndex !== endIndex) {
|
||||||
|
writeTextByElementList(elementList.slice(startIndex + 1, endIndex + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectAll() {
|
||||||
|
const position = this.position.getPositionList()
|
||||||
|
this.range.setRange(0, position.length - 1)
|
||||||
|
this.draw.render({
|
||||||
|
isSubmitHistory: false,
|
||||||
|
isSetCursor: false,
|
||||||
|
isComputeRowList: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public compositionstart() {
|
public compositionstart() {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { IRegisterContextMenu } from "../../interface/contextmenu/ContextMenu"
|
||||||
|
import { ContextMenu } from "../contextmenu/ContextMenu"
|
||||||
|
|
||||||
|
interface IRegisterPayload {
|
||||||
|
contextMenu: ContextMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Register {
|
||||||
|
|
||||||
|
public contextMenuList: (payload: IRegisterContextMenu[]) => void
|
||||||
|
|
||||||
|
constructor(payload: IRegisterPayload) {
|
||||||
|
const { contextMenu } = payload
|
||||||
|
this.contextMenuList = contextMenu.registerContextMenuList.bind(contextMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
export enum EditorComponent {
|
export enum EditorComponent {
|
||||||
MENU = 'menu',
|
MENU = 'menu',
|
||||||
MAIN = 'main',
|
MAIN = 'main',
|
||||||
FOOTER = 'footer'
|
FOOTER = 'footer',
|
||||||
|
CONTEXTMENU = 'contextmenu'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EditorContext {
|
export enum EditorContext {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum MouseEventButton {
|
||||||
|
LEFT = 0,
|
||||||
|
CENTER = 1,
|
||||||
|
RIGHT = 2
|
||||||
|
}
|
||||||
+14
-1
@@ -8,11 +8,16 @@ import { Listener } from './core/listener/Listener'
|
|||||||
import { RowFlex } from './dataset/enum/Row'
|
import { RowFlex } from './dataset/enum/Row'
|
||||||
import { ElementType } from './dataset/enum/Element'
|
import { ElementType } from './dataset/enum/Element'
|
||||||
import { formatElementList } from './utils/element'
|
import { formatElementList } from './utils/element'
|
||||||
|
import { Register } from './core/register/Register'
|
||||||
|
import { globalMenus } from './core/contextmenu/menus/GlobalMenus'
|
||||||
|
import { ContextMenu } from './core/contextmenu/ContextMenu'
|
||||||
|
import { IRegisterContextMenu } from './interface/contextmenu/ContextMenu'
|
||||||
|
|
||||||
export default class Editor {
|
export default class Editor {
|
||||||
|
|
||||||
public command: Command
|
public command: Command
|
||||||
public listener: Listener
|
public listener: Listener
|
||||||
|
public register: Register
|
||||||
|
|
||||||
constructor(container: HTMLDivElement, elementList: IElement[], options: IEditorOption = {}) {
|
constructor(container: HTMLDivElement, elementList: IElement[], options: IEditorOption = {}) {
|
||||||
const editorOptions: Required<IEditorOption> = {
|
const editorOptions: Required<IEditorOption> = {
|
||||||
@@ -52,6 +57,13 @@ export default class Editor {
|
|||||||
const draw = new Draw(container, editorOptions, elementList, this.listener)
|
const draw = new Draw(container, editorOptions, elementList, this.listener)
|
||||||
// 命令
|
// 命令
|
||||||
this.command = new Command(new CommandAdapt(draw))
|
this.command = new Command(new CommandAdapt(draw))
|
||||||
|
// 菜单
|
||||||
|
const contextMenu = new ContextMenu(draw, this.command)
|
||||||
|
// 注册
|
||||||
|
this.register = new Register({
|
||||||
|
contextMenu
|
||||||
|
})
|
||||||
|
this.register.contextMenuList(globalMenus)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -65,5 +77,6 @@ export {
|
|||||||
|
|
||||||
// 对外类型
|
// 对外类型
|
||||||
export type {
|
export type {
|
||||||
IElement
|
IElement,
|
||||||
|
IRegisterContextMenu
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export interface IContextMenuContext {
|
||||||
|
editorHasSelection: boolean;
|
||||||
|
editorTextFocus: boolean;
|
||||||
|
isInTable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRegisterContextMenu {
|
||||||
|
isDivider?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
name?: string;
|
||||||
|
shortCut?: string;
|
||||||
|
when?: (payload: IContextMenuContext) => boolean;
|
||||||
|
callback?: Function;
|
||||||
|
childMenus?: IRegisterContextMenu[];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user