feat:add editor mode

pr675
黄云飞 4 years ago
parent f03e610461
commit 6be3adb16a

@ -6,6 +6,7 @@ import { CommandAdapt } from "./CommandAdapt"
export class Command {
private static mode: Function
private static cut: Function
private static copy: Function
private static paste: Function
@ -53,6 +54,7 @@ export class Command {
private static insertElementList: Function
constructor(adapt: CommandAdapt) {
Command.mode = adapt.mode.bind(adapt)
Command.cut = adapt.cut.bind(adapt)
Command.copy = adapt.copy.bind(adapt)
Command.paste = adapt.paste.bind(adapt)
@ -101,6 +103,10 @@ export class Command {
}
// 全局命令
public executeMode() {
return Command.mode()
}
public executeCut() {
return Command.cut()
}

@ -1,7 +1,7 @@
import { WRAP, ZERO } from "../../dataset/constant/Common"
import { EDITOR_ELEMENT_STYLE_ATTR, TEXTLIKE_ELEMENT_TYPE } from "../../dataset/constant/Element"
import { defaultWatermarkOption } from "../../dataset/constant/Watermark"
import { EditorContext } from "../../dataset/enum/Editor"
import { EditorContext, EditorMode } from "../../dataset/enum/Editor"
import { ElementType } from "../../dataset/enum/Element"
import { ElementStyleKey } from "../../dataset/enum/ElementStyle"
import { RowFlex } from "../../dataset/enum/Row"
@ -45,7 +45,19 @@ export class CommandAdapt {
this.options = draw.getOptions()
}
public mode(payload: EditorMode) {
const mode = this.draw.getMode()
if (mode === payload) return
this.draw.setMode(payload)
this.draw.render({
isSetCursor: false,
isSubmitHistory: false
})
}
public cut() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
this.canvasEvent.cut()
}
@ -54,6 +66,8 @@ export class CommandAdapt {
}
public async paste() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const text = await navigator.clipboard.readText()
if (text) {
this.canvasEvent.input(text)
@ -65,14 +79,20 @@ export class CommandAdapt {
}
public undo() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
this.historyManager.undo()
}
public redo() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
this.historyManager.redo()
}
public painter() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
const painterStyle: IElementStyle = {}
@ -89,6 +109,8 @@ export class CommandAdapt {
}
public format() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
selection.forEach(el => {
@ -103,6 +125,8 @@ export class CommandAdapt {
}
public font(payload: string) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
selection.forEach(el => {
@ -112,6 +136,8 @@ export class CommandAdapt {
}
public sizeAdd() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
const lessThanMaxSizeIndex = selection.findIndex(s => !s.size || s.size + 2 <= 72)
@ -128,6 +154,8 @@ export class CommandAdapt {
}
public sizeMinus() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
const greaterThanMaxSizeIndex = selection.findIndex(s => !s.size || s.size - 2 >= 8)
@ -144,6 +172,8 @@ export class CommandAdapt {
}
public bold() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
const noBoldIndex = selection.findIndex(s => !s.bold)
@ -154,6 +184,8 @@ export class CommandAdapt {
}
public italic() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
const noItalicIndex = selection.findIndex(s => !s.italic)
@ -164,6 +196,8 @@ export class CommandAdapt {
}
public underline() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
const noUnderlineIndex = selection.findIndex(s => !s.underline)
@ -174,6 +208,8 @@ export class CommandAdapt {
}
public strikeout() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
const noStrikeoutIndex = selection.findIndex(s => !s.strikeout)
@ -184,6 +220,8 @@ export class CommandAdapt {
}
public superscript() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
const superscriptIndex = selection.findIndex(s => s.type === ElementType.SUPERSCRIPT)
@ -205,6 +243,8 @@ export class CommandAdapt {
}
public subscript() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
const subscriptIndex = selection.findIndex(s => s.type === ElementType.SUBSCRIPT)
@ -226,6 +266,8 @@ export class CommandAdapt {
}
public color(payload: string) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
selection.forEach(el => {
@ -235,6 +277,8 @@ export class CommandAdapt {
}
public highlight(payload: string) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const selection = this.range.getSelection()
if (!selection) return
selection.forEach(el => {
@ -244,6 +288,8 @@ export class CommandAdapt {
}
public rowFlex(payload: RowFlex) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const { startIndex, endIndex } = this.range.getRange()
if (!~startIndex && !~endIndex) return
const pageNo = this.draw.getPageNo()
@ -268,6 +314,8 @@ export class CommandAdapt {
}
public rowMargin(payload: number) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const { startIndex, endIndex } = this.range.getRange()
if (!~startIndex && !~endIndex) return
const pageNo = this.draw.getPageNo()
@ -292,6 +340,8 @@ export class CommandAdapt {
}
public insertTable(row: number, col: number) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const { startIndex, endIndex } = this.range.getRange()
if (!~startIndex && !~endIndex) return
const elementList = this.draw.getElementList()
@ -341,6 +391,8 @@ export class CommandAdapt {
}
public insertTableTopRow() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return
const { index, trIndex, tableId } = positionContext
@ -403,6 +455,8 @@ export class CommandAdapt {
}
public insertTableBottomRow() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return
const { index, trIndex, tableId } = positionContext
@ -468,6 +522,8 @@ export class CommandAdapt {
}
public insertTableLeftCol() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return
const { index, tdIndex, tableId } = positionContext
@ -524,6 +580,8 @@ export class CommandAdapt {
}
public insertTableRightCol() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return
const { index, tdIndex, tableId } = positionContext
@ -580,6 +638,8 @@ export class CommandAdapt {
}
public deleteTableRow() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return
const { index, trIndex } = positionContext
@ -631,6 +691,8 @@ export class CommandAdapt {
}
public deleteTableCol() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return
const { index, tdIndex, trIndex } = positionContext
@ -687,6 +749,8 @@ export class CommandAdapt {
}
public deleteTable() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return
const originalElementList = this.draw.getOriginalElementList()
@ -702,6 +766,8 @@ export class CommandAdapt {
}
public mergeTableCell() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return
const { isCrossRowCol, startTdIndex, endTdIndex, startTrIndex, endTrIndex } = this.range.getRange()
@ -809,6 +875,8 @@ export class CommandAdapt {
}
public cancelMergeTableCell() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const positionContext = this.position.getPositionContext()
if (!positionContext.isTable) return
const { index, tdIndex, trIndex } = positionContext
@ -869,6 +937,8 @@ export class CommandAdapt {
}
public hyperlink(payload: IElement) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const { startIndex, endIndex } = this.range.getRange()
if (!~startIndex && !~endIndex) return
const elementList = this.draw.getElementList()
@ -893,6 +963,8 @@ export class CommandAdapt {
}
public separator(payload: number[]) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const { startIndex, endIndex } = this.range.getRange()
if (!~startIndex && !~endIndex) return
const elementList = this.draw.getElementList()
@ -930,6 +1002,8 @@ export class CommandAdapt {
}
public addWatermark(payload: IWatermark) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const options = this.draw.getOptions()
const { color, size, opacity, font } = defaultWatermarkOption
options.watermark.data = payload.data
@ -944,6 +1018,8 @@ export class CommandAdapt {
}
public deleteWatermark() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const options = this.draw.getOptions()
if (options.watermark && options.watermark.data) {
options.watermark = { ...defaultWatermarkOption }
@ -955,6 +1031,8 @@ export class CommandAdapt {
}
public image(payload: IDrawImagePayload) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const { startIndex, endIndex } = this.range.getRange()
if (!~startIndex && !~endIndex) return
const elementList = this.draw.getElementList()
@ -1102,6 +1180,8 @@ export class CommandAdapt {
}
public insertElementList(payload: IElement[]) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
if (!payload.length) return
const { startIndex, endIndex } = this.range.getRange()
if (!~startIndex && !~endIndex) return

@ -16,6 +16,7 @@ interface IRenderPayload {
export class ContextMenu {
private draw: Draw
private command: Command
private range: RangeManager
private position: Position
@ -25,6 +26,7 @@ export class ContextMenu {
private contextMenuRelationShip: Map<HTMLDivElement, HTMLDivElement>
constructor(draw: Draw, command: Command) {
this.draw = draw
this.command = command
this.range = draw.getRange()
this.position = draw.getPosition()
@ -81,6 +83,8 @@ export class ContextMenu {
}
private _getContext(): IContextMenuContext {
// 是否是只读模式
const isReadonly = this.draw.isReadonly()
const { isCrossRowCol: crossRowCol, startIndex, endIndex } = this.range.getRange()
// 是否存在焦点
const editorTextFocus = !!(~startIndex || ~endIndex)
@ -92,6 +96,7 @@ export class ContextMenu {
// 是否存在跨行/列
const isCrossRowCol = isInTable && !!crossRowCol
return {
isReadonly,
editorHasSelection,
editorTextFocus,
isInTable,

@ -6,7 +6,7 @@ export const globalMenus: IRegisterContextMenu[] = [
name: '剪切',
shortCut: 'Ctrl + X',
when: (payload) => {
return payload.editorHasSelection
return !payload.isReadonly && payload.editorHasSelection
},
callback: (command: Command) => {
command.executeCut()
@ -26,7 +26,7 @@ export const globalMenus: IRegisterContextMenu[] = [
name: '粘贴',
shortCut: 'Ctrl + V',
when: (payload) => {
return payload.editorTextFocus
return !payload.isReadonly && payload.editorTextFocus
},
callback: (command: Command) => {
command.executePaste()

@ -9,7 +9,7 @@ export const tableMenus: IRegisterContextMenu[] = [
name: '插入行列',
icon: 'insert-row-col',
when: (payload) => {
return payload.isInTable
return !payload.isReadonly && payload.isInTable
},
childMenus: [
{
@ -50,7 +50,7 @@ export const tableMenus: IRegisterContextMenu[] = [
name: '删除行列',
icon: 'delete-row-col',
when: (payload) => {
return payload.isInTable
return !payload.isReadonly && payload.isInTable
},
childMenus: [
{
@ -83,7 +83,7 @@ export const tableMenus: IRegisterContextMenu[] = [
name: '合并单元格',
icon: 'merge-cell',
when: (payload) => {
return payload.isCrossRowCol
return !payload.isReadonly && payload.isCrossRowCol
},
callback: (command: Command) => {
command.executeMergeTableCell()
@ -93,7 +93,7 @@ export const tableMenus: IRegisterContextMenu[] = [
name: '取消合并',
icon: 'merge-cancel-cell',
when: (payload) => {
return payload.isInTable
return !payload.isReadonly && payload.isInTable
},
callback: (command: Command) => {
command.executeCancelMergeTableCell()

@ -35,6 +35,7 @@ export class Cursor {
}
public drawCursor() {
const isReadonly = this.draw.isReadonly()
const cursorPosition = this.position.getCursorPosition()
if (!cursorPosition) return
// 设置光标代理
@ -60,7 +61,7 @@ export class Cursor {
// 模拟光标显示
this.cursorDom.style.left = `${cursorLeft}px`
this.cursorDom.style.top = `${cursorTop}px`
this.cursorDom.style.display = 'block'
this.cursorDom.style.display = isReadonly ? 'none' : 'block'
this.cursorDom.style.height = `${cursorHeight}px`
setTimeout(() => {
this.cursorDom.classList.add('cursor--animation')

@ -33,6 +33,7 @@ import { SubscriptParticle } from "./particle/Subscript"
import { SeparatorParticle } from "./particle/Separator"
import { PageBreakParticle } from "./particle/PageBreak"
import { Watermark } from "./frame/Watermark"
import { EditorMode } from "../../dataset/enum/Editor"
export class Draw {
@ -41,6 +42,7 @@ export class Draw {
private pageList: HTMLCanvasElement[]
private ctxList: CanvasRenderingContext2D[]
private pageNo: number
private mode: EditorMode
private options: Required<IEditorOption>
private position: Position
private elementList: IElement[]
@ -85,6 +87,7 @@ export class Draw {
this.pageList = []
this.ctxList = []
this.pageNo = 0
this.mode = options.defaultMode
this.options = options
this.elementList = elementList
this.listener = listener
@ -131,6 +134,18 @@ export class Draw {
this.render({ isSetCursor: false })
}
public getMode(): EditorMode {
return this.mode
}
public setMode(payload: EditorMode) {
this.mode = payload
}
public isReadonly() {
return this.mode === EditorMode.READONLY
}
public getWidth(): number {
return Math.floor(this.options.width * this.options.scale)
}
@ -650,7 +665,9 @@ export class Draw {
} else if (element.type === ElementType.SEPARATOR) {
this.separatorParticle.render(ctx, element, x, y)
} else if (element.type === ElementType.PAGE_BREAK) {
this.pageBreakParticle.render(ctx, element, x, y)
if (this.mode !== EditorMode.CLEAN) {
this.pageBreakParticle.render(ctx, element, x, y)
}
} else {
this.textParticle.record(ctx, element, x, y + offsetY)
}

@ -146,6 +146,7 @@ export class CanvasEvent {
public mousedown(evt: MouseEvent) {
if (evt.button === MouseEventButton.RIGHT) return
const isReadonly = this.draw.isReadonly()
const target = evt.target as HTMLDivElement
const pageIndex = target.dataset.index
// 设置pageNo
@ -207,12 +208,12 @@ export class CanvasEvent {
const curElement = elementList[curIndex]
// 图片尺寸拖拽组件
this.imageParticle.clearResizer()
if (isDirectHitImage) {
if (isDirectHitImage && !isReadonly) {
this.imageParticle.drawResizer(curElement, positionList[curIndex])
}
// 表格工具组件
this.tableTool.dispose()
if (isTable) {
if (isTable && !isReadonly) {
const originalElementList = this.draw.getOriginalElementList()
const originalPositionList = this.position.getOriginalPositionList()
this.tableTool.render(originalElementList[index], originalPositionList[index])
@ -232,6 +233,7 @@ export class CanvasEvent {
}
public keydown(evt: KeyboardEvent) {
const isReadonly = this.draw.isReadonly()
const cursorPosition = this.position.getCursorPosition()
if (!cursorPosition) return
const elementList = this.draw.getElementList()
@ -240,6 +242,7 @@ export class CanvasEvent {
const { startIndex, endIndex } = this.range.getRange()
const isCollapsed = startIndex === endIndex
if (evt.key === KeyMap.Backspace) {
if (isReadonly) return
// 判断是否允许删除
if (elementList[index].value === ZERO && index === 0) {
evt.preventDefault()
@ -254,6 +257,7 @@ export class CanvasEvent {
this.range.setRange(curIndex, curIndex)
this.draw.render({ curIndex })
} else if (evt.key === KeyMap.Delete) {
if (isReadonly) return
if (!isCollapsed) {
elementList.splice(startIndex + 1, endIndex - startIndex)
} else {
@ -263,6 +267,7 @@ export class CanvasEvent {
this.range.setRange(curIndex, curIndex)
this.draw.render({ curIndex })
} else if (evt.key === KeyMap.Enter) {
if (isReadonly) return
// 表格需要上下文信息
const positionContext = this.position.getPositionContext()
let restArg = {}
@ -283,6 +288,7 @@ export class CanvasEvent {
this.range.setRange(curIndex, curIndex)
this.draw.render({ curIndex })
} else if (evt.key === KeyMap.Left) {
if (isReadonly) return
if (index > 0) {
const curIndex = index - 1
this.range.setRange(curIndex, curIndex)
@ -293,6 +299,7 @@ export class CanvasEvent {
})
}
} else if (evt.key === KeyMap.Right) {
if (isReadonly) return
if (index < position.length - 1) {
const curIndex = index + 1
this.range.setRange(curIndex, curIndex)
@ -303,6 +310,7 @@ export class CanvasEvent {
})
}
} else if (evt.key === KeyMap.Up || evt.key === KeyMap.Down) {
if (isReadonly) return
const { rowNo, index, coordinate: { leftTop, rightTop } } = cursorPosition
if ((evt.key === KeyMap.Up && rowNo !== 0) || (evt.key === KeyMap.Down && rowNo !== this.draw.getRowCount())) {
// 下一个光标点所在行位置集合
@ -344,18 +352,22 @@ export class CanvasEvent {
})
}
} else if (evt.ctrlKey && evt.key === KeyMap.Z) {
if (isReadonly) return
this.historyManager.undo()
evt.preventDefault()
} else if (evt.ctrlKey && evt.key === KeyMap.Y) {
if (isReadonly) return
this.historyManager.redo()
evt.preventDefault()
} else if (evt.ctrlKey && evt.key === KeyMap.C) {
this.copy()
} else if (evt.ctrlKey && evt.key === KeyMap.X) {
if (isReadonly) return
this.cut()
} else if (evt.ctrlKey && evt.key === KeyMap.A) {
this.selectAll()
} else if (evt.ctrlKey && evt.key === KeyMap.S) {
if (isReadonly) return
const saved = this.listener.saved
if (saved) {
saved(this.save())
@ -365,6 +377,8 @@ export class CanvasEvent {
}
public input(data: string) {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
if (!this.cursor) return
const cursorPosition = this.position.getCursorPosition()
if (!data || !cursorPosition || this.isCompositing) return
@ -423,6 +437,8 @@ export class CanvasEvent {
}
public cut() {
const isReadonly = this.draw.isReadonly()
if (isReadonly) return
const { startIndex, endIndex } = this.range.getRange()
const elementList = this.draw.getElementList()
if (startIndex !== endIndex) {

@ -9,4 +9,10 @@ export enum EditorComponent {
export enum EditorContext {
PAGE = 'page',
TABLE = 'table'
}
export enum EditorMode {
EDIT = 'edit',
CLEAN = 'clean',
READONLY = 'readonly'
}

@ -13,7 +13,7 @@ import { globalMenus } from './core/contextmenu/menus/GlobalMenus'
import { ContextMenu } from './core/contextmenu/ContextMenu'
import { tableMenus } from './core/contextmenu/menus/tableMenus'
import { IContextMenuContext, IRegisterContextMenu } from './interface/contextmenu/ContextMenu'
import { EditorComponent } from './dataset/enum/Editor'
import { EditorComponent, EditorMode } from './dataset/enum/Editor'
import { EDITOR_COMPONENT } from './dataset/constant/Editor'
import { IHeader } from './interface/Header'
import { IWatermark } from './interface/Watermark'
@ -36,6 +36,7 @@ export default class Editor {
...options.watermark
}
const editorOptions: Required<IEditorOption> = {
defaultMode: EditorMode.EDIT,
defaultType: 'TEXT',
defaultFont: 'Yahei',
defaultSize: 16,
@ -92,6 +93,7 @@ export default class Editor {
export {
Editor,
RowFlex,
EditorMode,
ElementType,
EditorComponent,
EDITOR_COMPONENT

@ -1,8 +1,10 @@
import { IElement } from ".."
import { EditorMode } from "../dataset/enum/Editor"
import { IHeader } from "./Header"
import { IWatermark } from "./Watermark"
export interface IEditorOption {
defaultMode?: EditorMode;
defaultType?: string;
defaultFont?: string;
defaultSize?: number;

@ -1,4 +1,5 @@
export interface IContextMenuContext {
isReadonly: boolean;
editorHasSelection: boolean;
editorTextFocus: boolean;
isInTable: boolean;

Loading…
Cancel
Save