diff --git a/src/editor/assets/css/index.css b/src/editor/assets/css/index.css index 8384f8d..1036a24 100644 --- a/src/editor/assets/css/index.css +++ b/src/editor/assets/css/index.css @@ -223,7 +223,7 @@ } .contextmenu-content .contextmenu-item { - width: 180px; + min-width: 140px; padding: 0 32px 0 16px; height: 30px; display: flex; @@ -248,6 +248,7 @@ flex: 1; text-align: right; line-height: 30px; + margin-left: 20px; } .contextmenu-content .contextmenu-item i { @@ -269,4 +270,40 @@ .contextmenu-print { background-image: url(../../assets/images/print.svg); +} + +.contextmenu-insert-row-col { + background-image: url(../../assets/images/insert-row-col.svg); +} + +.contextmenu-insert-top-row { + background-image: url(../../assets/images/insert-top-row.svg); +} + +.contextmenu-insert-bottom-row { + background-image: url(../../assets/images/insert-bottom-row.svg); +} + +.contextmenu-insert-left-col { + background-image: url(../../assets/images/insert-left-col.svg); +} + +.contextmenu-insert-right-col { + background-image: url(../../assets/images/insert-right-col.svg); +} + +.contextmenu-delete-row-col { + background-image: url(../../assets/images/delete-row-col.svg); +} + +.contextmenu-delete-row { + background-image: url(../../assets/images/delete-row.svg); +} + +.contextmenu-delete-col { + background-image: url(../../assets/images/delete-col.svg); +} + +.contextmenu-delete-table { + background-image: url(../../assets/images/delete-table.svg); } \ No newline at end of file diff --git a/src/editor/assets/images/delete-col.svg b/src/editor/assets/images/delete-col.svg new file mode 100644 index 0000000..5e22407 --- /dev/null +++ b/src/editor/assets/images/delete-col.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/delete-row-col.svg b/src/editor/assets/images/delete-row-col.svg new file mode 100644 index 0000000..1a46462 --- /dev/null +++ b/src/editor/assets/images/delete-row-col.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/delete-row.svg b/src/editor/assets/images/delete-row.svg new file mode 100644 index 0000000..98ffc5c --- /dev/null +++ b/src/editor/assets/images/delete-row.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/delete-table.svg b/src/editor/assets/images/delete-table.svg new file mode 100644 index 0000000..73c6b76 --- /dev/null +++ b/src/editor/assets/images/delete-table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/insert-bottom-row.svg b/src/editor/assets/images/insert-bottom-row.svg new file mode 100644 index 0000000..f18d203 --- /dev/null +++ b/src/editor/assets/images/insert-bottom-row.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/insert-left-col.svg b/src/editor/assets/images/insert-left-col.svg new file mode 100644 index 0000000..492cec3 --- /dev/null +++ b/src/editor/assets/images/insert-left-col.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/insert-right-col.svg b/src/editor/assets/images/insert-right-col.svg new file mode 100644 index 0000000..38d8306 --- /dev/null +++ b/src/editor/assets/images/insert-right-col.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/insert-row-col.svg b/src/editor/assets/images/insert-row-col.svg new file mode 100644 index 0000000..cd80e5f --- /dev/null +++ b/src/editor/assets/images/insert-row-col.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/insert-top-row.svg b/src/editor/assets/images/insert-top-row.svg new file mode 100644 index 0000000..1ad48b6 --- /dev/null +++ b/src/editor/assets/images/insert-top-row.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/core/command/Command.ts b/src/editor/core/command/Command.ts index faded6f..97968d0 100644 --- a/src/editor/core/command/Command.ts +++ b/src/editor/core/command/Command.ts @@ -26,6 +26,13 @@ export class Command { private static right: Function private static rowMargin: Function private static insertTable: Function + private static insertTableTopRow: Function + private static insertTableBottomRow: Function + private static insertTableLeftCol: Function + private static insertTableRightCol: Function + private static deleteTableRow: Function + private static deleteTableCol: Function + private static deleteTable: Function private static image: Function private static search: Function private static print: Function @@ -56,6 +63,13 @@ export class Command { Command.right = adapt.rowFlex.bind(adapt) Command.rowMargin = adapt.rowMargin.bind(adapt) Command.insertTable = adapt.insertTable.bind(adapt) + Command.insertTableTopRow = adapt.insertTableTopRow.bind(adapt) + Command.insertTableBottomRow = adapt.insertTableBottomRow.bind(adapt) + Command.insertTableLeftCol = adapt.insertTableLeftCol.bind(adapt) + Command.insertTableRightCol = adapt.insertTableRightCol.bind(adapt) + Command.deleteTableRow = adapt.deleteTableRow.bind(adapt) + Command.deleteTableCol = adapt.deleteTableCol.bind(adapt) + Command.deleteTable = adapt.deleteTable.bind(adapt) Command.image = adapt.image.bind(adapt) Command.search = adapt.search.bind(adapt) Command.print = adapt.print.bind(adapt) @@ -156,6 +170,34 @@ export class Command { return Command.insertTable(row, col) } + public executeInsertTableTopRow() { + return Command.insertTableTopRow() + } + + public executeInsertTableBottomRow() { + return Command.insertTableBottomRow() + } + + public executeInsertTableLeftCol() { + return Command.insertTableLeftCol() + } + + public executeInsertTableRightCol() { + return Command.insertTableRightCol() + } + + public executDeleteTableRow() { + return Command.deleteTableRow() + } + + public executDeleteTableCol() { + return Command.deleteTableCol() + } + + public executDeleteTable() { + return Command.deleteTable() + } + public executeImage(payload: IDrawImagePayload) { return Command.image(payload) } diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 1199027..06557a8 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -15,6 +15,7 @@ import { getUUID } from "../../utils" import { formatElementList } from "../../utils/element" import { printImageBase64 } from "../../utils/print" import { Draw } from "../draw/Draw" +import { TableTool } from "../draw/particle/table/TableTool" import { CanvasEvent } from "../event/CanvasEvent" import { HistoryManager } from "../history/HistoryManager" import { Position } from "../position/Position" @@ -22,11 +23,14 @@ import { RangeManager } from "../range/RangeManager" export class CommandAdapt { + private readonly defaultWidth: number = 40 + private draw: Draw private range: RangeManager private position: Position private historyManager: HistoryManager private canvasEvent: CanvasEvent + private tableTool: TableTool private options: Required constructor(draw: Draw) { @@ -35,6 +39,7 @@ export class CommandAdapt { this.position = draw.getPosition() this.historyManager = draw.getHistoryManager() this.canvasEvent = draw.getCanvasEvent() + this.tableTool = draw.getTableTool() this.options = draw.getOptions() } @@ -291,6 +296,364 @@ export class CommandAdapt { this.draw.render({ curIndex, isSetCursor: false }) } + public insertTableTopRow() { + const positionContext = this.position.getPositionContext() + if (!positionContext.isTable) return + const { index, trIndex, tableId } = positionContext + const originalElementList = this.draw.getOriginalElementList() + const element = originalElementList[index!] + const curTrList = element.trList! + const curTr = curTrList[trIndex!] + // 之前跨行的增加跨行数 + if (curTr.tdList.length < element.colgroup!.length) { + const curTrNo = curTr.tdList[0].rowIndex! + for (let t = 0; t < trIndex!; t++) { + const tr = curTrList[t] + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d] + if (td.rowspan > 1 && td.rowIndex! + td.rowspan >= curTrNo + 1) { + td.rowspan += 1 + } + } + } + } + // 增加当前行 + const newTrId = getUUID() + const newTr: ITr = { + height: curTr.height, + id: newTrId, + tdList: [] + } + for (let t = 0; t < curTr.tdList.length; t++) { + const curTd = curTr.tdList[t] + const newTdId = getUUID() + newTr.tdList.push({ + id: newTdId, + rowspan: 1, + colspan: curTd.colspan, + value: [{ + value: ZERO, + size: 16, + tableId, + trId: newTrId, + tdId: newTdId + }] + }) + } + curTrList.splice(trIndex!, 0, newTr) + // 重新设置上下文 + this.position.setPositionContext({ + isTable: true, + index, + trIndex, + tdIndex: 0, + tdId: newTr.tdList[0].id, + trId: newTr.id, + tableId + }) + this.range.setRange(0, 0) + // 重新渲染 + this.draw.render({ curIndex: 0 }) + const position = this.position.getOriginalPositionList() + this.tableTool.render(element, position[index!]) + } + + public insertTableBottomRow() { + const positionContext = this.position.getPositionContext() + if (!positionContext.isTable) return + const { index, trIndex, tableId } = positionContext + const originalElementList = this.draw.getOriginalElementList() + const element = originalElementList[index!] + const curTrList = element.trList! + const curTr = curTrList[trIndex!] + const anchorTr = curTrList.length - 1 === trIndex + ? curTr + : curTrList[trIndex! + 1] + // 之前/当前行跨行的增加跨行数 + if (anchorTr.tdList.length < element.colgroup!.length) { + const curTrNo = anchorTr.tdList[0].rowIndex! + for (let t = 0; t < trIndex! + 1; t++) { + const tr = curTrList[t] + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d] + if (td.rowspan > 1 && td.rowIndex! + td.rowspan >= curTrNo + 1) { + td.rowspan += 1 + } + } + } + } + // 增加当前行 + const newTrId = getUUID() + const newTr: ITr = { + height: anchorTr.height, + id: newTrId, + tdList: [] + } + for (let t = 0; t < anchorTr.tdList.length; t++) { + const curTd = anchorTr.tdList[t] + const newTdId = getUUID() + newTr.tdList.push({ + id: newTdId, + rowspan: 1, + colspan: curTd.colspan, + value: [{ + value: ZERO, + size: 16, + tableId, + trId: newTrId, + tdId: newTdId + }] + }) + } + curTrList.splice(trIndex! + 1, 0, newTr) + // 重新设置上下文 + this.position.setPositionContext({ + isTable: true, + index, + trIndex: trIndex! + 1, + tdIndex: 0, + tdId: newTr.tdList[0].id, + trId: newTr.id, + tableId + }) + this.range.setRange(0, 0) + // 重新渲染 + this.draw.render({ curIndex: 0 }) + const position = this.position.getOriginalPositionList() + this.tableTool.render(element, position[index!]) + } + + public insertTableLeftCol() { + const positionContext = this.position.getPositionContext() + if (!positionContext.isTable) return + const { index, tdIndex, tableId } = positionContext + const originalElementList = this.draw.getOriginalElementList() + const element = originalElementList[index!] + const curTrList = element.trList! + const curTdIndex = tdIndex! + // 增加列 + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t] + const tdId = getUUID() + tr.tdList.splice(curTdIndex, 0, { + id: tdId, + rowspan: 1, + colspan: 1, + value: [{ + value: ZERO, + size: 16, + tableId, + trId: tr.id, + tdId + }] + }) + } + // 重新计算宽度 + const colgroup = element.colgroup! + colgroup.splice(curTdIndex, 0, { + width: this.defaultWidth + }) + const colgroupWidth = colgroup.reduce((pre, cur) => pre + cur.width, 0) + const width = this.draw.getOriginalInnerWidth() + if (colgroupWidth > width) { + const adjustWidth = (colgroupWidth - width) / colgroup.length + for (let g = 0; g < colgroup.length; g++) { + const group = colgroup[g] + group.width -= adjustWidth + } + } + // 重新设置上下文 + this.position.setPositionContext({ + isTable: true, + index, + trIndex: 0, + tdIndex: curTdIndex, + tdId: curTrList[0].tdList[curTdIndex].id, + trId: curTrList[0].id, + tableId + }) + this.range.setRange(0, 0) + // 重新渲染 + this.draw.render({ curIndex: 0 }) + const position = this.position.getOriginalPositionList() + this.tableTool.render(element, position[index!]) + } + + public insertTableRightCol() { + const positionContext = this.position.getPositionContext() + if (!positionContext.isTable) return + const { index, tdIndex, tableId } = positionContext + const originalElementList = this.draw.getOriginalElementList() + const element = originalElementList[index!] + const curTrList = element.trList! + const curTdIndex = tdIndex! + 1 + // 增加列 + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t] + const tdId = getUUID() + tr.tdList.splice(curTdIndex, 0, { + id: tdId, + rowspan: 1, + colspan: 1, + value: [{ + value: ZERO, + size: 16, + tableId, + trId: tr.id, + tdId + }] + }) + } + // 重新计算宽度 + const colgroup = element.colgroup! + colgroup.splice(curTdIndex, 0, { + width: this.defaultWidth + }) + const colgroupWidth = colgroup.reduce((pre, cur) => pre + cur.width, 0) + const width = this.draw.getOriginalInnerWidth() + if (colgroupWidth > width) { + const adjustWidth = (colgroupWidth - width) / colgroup.length + for (let g = 0; g < colgroup.length; g++) { + const group = colgroup[g] + group.width -= adjustWidth + } + } + // 重新设置上下文 + this.position.setPositionContext({ + isTable: true, + index, + trIndex: 0, + tdIndex: curTdIndex, + tdId: curTrList[0].tdList[curTdIndex].id, + trId: curTrList[0].id, + tableId + }) + this.range.setRange(0, 0) + // 重新渲染 + this.draw.render({ curIndex: 0 }) + const position = this.position.getOriginalPositionList() + this.tableTool.render(element, position[index!]) + } + + public deleteTableRow() { + const positionContext = this.position.getPositionContext() + if (!positionContext.isTable) return + const { index, trIndex } = positionContext + const originalElementList = this.draw.getOriginalElementList() + const element = originalElementList[index!] + const curTrList = element.trList! + const curTr = curTrList[trIndex!] + // 如果是最后一行,直接删除整个表格 + if (curTrList.length <= 1) { + this.deleteTable() + return + } + // 补跨行 + for (let d = 0; d < curTr.tdList.length; d++) { + const td = curTr.tdList[d] + if (td.rowspan > 1) { + let start = trIndex! + 1 + while (start < trIndex! + td.rowspan) { + const tdId = getUUID() + const tr = curTrList[start] + tr.tdList.splice(d, 0, { + id: tdId, + rowspan: 1, + colspan: 1, + value: [{ + value: ZERO, + size: 16, + tableId: element.id, + trId: tr.id, + tdId + }] + }) + start += 1 + } + } + } + // 删除当前行 + curTrList.splice(trIndex!, 1) + // 重新设置上下文 + this.position.setPositionContext({ + isTable: false + }) + this.range.setRange(0, 0) + // 重新渲染 + this.draw.render({ isSetCursor: false }) + this.tableTool.dispose() + } + + public deleteTableCol() { + const positionContext = this.position.getPositionContext() + if (!positionContext.isTable) return + const { index, tdIndex, trIndex } = positionContext + const originalElementList = this.draw.getOriginalElementList() + const element = originalElementList[index!] + const curTrList = element.trList! + const curTd = curTrList[trIndex!].tdList[tdIndex!] + const curColIndex = curTd.colIndex! + // 如果是最后一列,直接删除整个表格 + const moreTdTr = curTrList.find(tr => tr.tdList.length > 1) + if (!moreTdTr) { + this.deleteTable() + return + } + // 跨列处理 + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t] + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d] + if (td.colspan > 1) { + const tdColIndex = td.colIndex! + // 交叉减去一列 + if (tdColIndex <= curColIndex && tdColIndex + td.colspan - 1 >= curColIndex) { + td.colspan -= 1 + } + } + } + } + // 删除当前列 + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t] + let start = -1 + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d] + if (td.colIndex === curColIndex) { + start = d + } + } + if (~start) { + tr.tdList.splice(start, 1) + } + } + element.colgroup?.splice(curColIndex, 1) + // 重新设置上下文 + this.position.setPositionContext({ + isTable: false + }) + this.range.setRange(0, 0) + // 重新渲染 + this.draw.render({ isSetCursor: false }) + this.tableTool.dispose() + } + + public deleteTable() { + const positionContext = this.position.getPositionContext() + if (positionContext.isTable) { + const originalElementList = this.draw.getOriginalElementList() + originalElementList.splice(positionContext.index!, 1) + const curIndex = positionContext.index! - 1 + this.position.setPositionContext({ + isTable: false, + index: curIndex + }) + this.range.setRange(curIndex, curIndex) + this.draw.render({ curIndex }) + this.tableTool.dispose() + } + } + public image(payload: IDrawImagePayload) { const { startIndex, endIndex } = this.range.getRange() if (startIndex === 0 && endIndex === 0) return diff --git a/src/editor/core/contextmenu/menus/tableMenus.ts b/src/editor/core/contextmenu/menus/tableMenus.ts new file mode 100644 index 0000000..9da7bdc --- /dev/null +++ b/src/editor/core/contextmenu/menus/tableMenus.ts @@ -0,0 +1,78 @@ +import { IRegisterContextMenu } from "../../../interface/contextmenu/ContextMenu" +import { Command } from "../../command/Command" + +export const tableMenus: IRegisterContextMenu[] = [ + { + isDivider: true + }, + { + name: '插入行列', + icon: 'insert-row-col', + when: (payload) => { + return payload.isInTable + }, + childMenus: [ + { + name: '上方插入1行', + icon: 'insert-top-row', + when: () => true, + callback: (command: Command) => { + command.executeInsertTableTopRow() + } + }, { + name: '下方插入1行', + icon: 'insert-bottom-row', + when: () => true, + callback: (command: Command) => { + command.executeInsertTableBottomRow() + } + }, { + name: '左侧插入1列', + icon: 'insert-left-col', + when: () => true, + callback: (command: Command) => { + command.executeInsertTableLeftCol() + } + }, { + name: '右侧插入1列', + icon: 'insert-right-col', + when: () => true, + callback: (command: Command) => { + command.executeInsertTableRightCol() + } + } + ] + }, + { + name: '删除行列', + icon: 'delete-row-col', + when: (payload) => { + return payload.isInTable + }, + childMenus: [ + { + name: '删除1行', + icon: 'delete-row', + when: () => true, + callback: (command: Command) => { + command.executDeleteTableRow() + } + }, + { + name: '删除一列', + icon: 'delete-col', + when: () => true, + callback: (command: Command) => { + command.executDeleteTableCol() + } + }, { + name: '删除整个表格', + icon: 'delete-table', + when: () => true, + callback: (command: Command) => { + command.executDeleteTable() + } + } + ] + } +] \ No newline at end of file diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 68e2f78..b9adc50 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -123,6 +123,11 @@ export class Draw { return width - margins[1] - margins[3] } + public getOriginalInnerWidth(): number { + const { width, margins } = this.options + return width - margins[1] - margins[3] + } + public getMargins(): number[] { return this.options.margins.map(m => m * this.options.scale) } diff --git a/src/editor/core/draw/particle/table/TableParticle.ts b/src/editor/core/draw/particle/table/TableParticle.ts index caca0cc..c1a5fd5 100644 --- a/src/editor/core/draw/particle/table/TableParticle.ts +++ b/src/editor/core/draw/particle/table/TableParticle.ts @@ -69,7 +69,7 @@ export class TableParticle { let colIndex = 0 const preTd = tr.tdList[d - 1] if (preTd) { - colIndex = preTd.colIndex! + (offsetXIndex || 1) + colIndex = preTd.colIndex! + offsetXIndex + 1 if (preTd.colspan > 1) { colIndex += preTd.colspan - 1 } diff --git a/src/editor/index.ts b/src/editor/index.ts index 51705a6..a225ef1 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -11,6 +11,7 @@ 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 { tableMenus } from './core/contextmenu/menus/tableMenus' import { IRegisterContextMenu } from './interface/contextmenu/ContextMenu' export default class Editor { @@ -64,6 +65,7 @@ export default class Editor { contextMenu }) this.register.contextMenuList(globalMenus) + this.register.contextMenuList(tableMenus) } }