feat: add element group
This commit is contained in:
@@ -761,3 +761,33 @@ Usage:
|
|||||||
```javascript
|
```javascript
|
||||||
instance.command.executeSetHTML(payload: Partial<IEditorHTML)
|
instance.command.executeSetHTML(payload: Partial<IEditorHTML)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## executeSetGroup
|
||||||
|
|
||||||
|
Feature: Set group
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
instance.command.executeSetGroup()
|
||||||
|
```
|
||||||
|
|
||||||
|
## executeDeleteGroup
|
||||||
|
|
||||||
|
Feature: Delete group
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
instance.command.executeDeleteGroup(groupId: string)
|
||||||
|
```
|
||||||
|
|
||||||
|
## executeLocationGroup
|
||||||
|
|
||||||
|
Feature: Positioning group position
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
instance.command.executeLocationGroup(groupId: string)
|
||||||
|
```
|
||||||
|
|||||||
@@ -151,10 +151,20 @@ const {
|
|||||||
|
|
||||||
## getLocale
|
## getLocale
|
||||||
|
|
||||||
功能:Get current locale
|
Feature: Get current locale
|
||||||
|
|
||||||
用法:
|
Usage:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const locale = await instance.command.getLocale()
|
const locale = await instance.command.getLocale()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## getGroupIds
|
||||||
|
|
||||||
|
Feature: Get all group ids
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const groupIds = await instance.command.getGroupIds()
|
||||||
|
```
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ interface IEditorOption {
|
|||||||
cursor?: ICursorOption // Cursor style. {width?: number; color?: string; dragWidth?: number; dragColor?: string;}
|
cursor?: ICursorOption // Cursor style. {width?: number; color?: string; dragWidth?: number; dragColor?: string;}
|
||||||
title?: ITitleOption // Title configuration.{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;}
|
title?: ITitleOption // Title configuration.{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;}
|
||||||
placeholder?: IPlaceholder // Placeholder text
|
placeholder?: IPlaceholder // Placeholder text
|
||||||
|
group?: IGroup // Group option. {opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ interface IElement {
|
|||||||
};
|
};
|
||||||
rowMargin?: number;
|
rowMargin?: number;
|
||||||
letterSpacing?: number;
|
letterSpacing?: number;
|
||||||
|
// groupIds
|
||||||
|
groupIds?: string[];
|
||||||
// table
|
// table
|
||||||
colgroup?: {
|
colgroup?: {
|
||||||
width: number;
|
width: number;
|
||||||
|
|||||||
@@ -763,3 +763,33 @@ instance.command.executeWordTool()
|
|||||||
```javascript
|
```javascript
|
||||||
instance.command.executeSetHTML(payload: Partial<IEditorHTML)
|
instance.command.executeSetHTML(payload: Partial<IEditorHTML)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## executeSetGroup
|
||||||
|
|
||||||
|
功能:设置成组
|
||||||
|
|
||||||
|
用法:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
instance.command.executeSetGroup()
|
||||||
|
```
|
||||||
|
|
||||||
|
## executeDeleteGroup
|
||||||
|
|
||||||
|
功能:删除成组
|
||||||
|
|
||||||
|
用法:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
instance.command.executeDeleteGroup(groupId: string)
|
||||||
|
```
|
||||||
|
|
||||||
|
## executeLocationGroup
|
||||||
|
|
||||||
|
功能:定位成组位置
|
||||||
|
|
||||||
|
用法:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
instance.command.executeLocationGroup(groupId: string)
|
||||||
|
```
|
||||||
|
|||||||
@@ -158,3 +158,13 @@ const {
|
|||||||
```javascript
|
```javascript
|
||||||
const locale = await instance.command.getLocale()
|
const locale = await instance.command.getLocale()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## getGroupIds
|
||||||
|
|
||||||
|
功能:获取所有成组 id
|
||||||
|
|
||||||
|
用法:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const groupIds = await instance.command.getGroupIds()
|
||||||
|
```
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ interface IEditorOption {
|
|||||||
cursor?: ICursorOption // 光标样式。{width?: number; color?: string; dragWidth?: number; dragColor?: string;}
|
cursor?: ICursorOption // 光标样式。{width?: number; color?: string; dragWidth?: number; dragColor?: string;}
|
||||||
title?: ITitleOption // 标题配置。{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;}
|
title?: ITitleOption // 标题配置。{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;}
|
||||||
placeholder?: IPlaceholder // 编辑器空白占位文本
|
placeholder?: IPlaceholder // 编辑器空白占位文本
|
||||||
|
group?: IGroup // 成组配置。{opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ interface IElement {
|
|||||||
};
|
};
|
||||||
rowMargin?: number;
|
rowMargin?: number;
|
||||||
letterSpacing?: number;
|
letterSpacing?: number;
|
||||||
|
// 组信息-可用于批注等其他成组使用场景
|
||||||
|
groupIds?: string[];
|
||||||
// 表格
|
// 表格
|
||||||
colgroup?: {
|
colgroup?: {
|
||||||
width: number;
|
width: number;
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ export class Command {
|
|||||||
public executeLocationCatalog: CommandAdapt['locationCatalog']
|
public executeLocationCatalog: CommandAdapt['locationCatalog']
|
||||||
public executeWordTool: CommandAdapt['wordTool']
|
public executeWordTool: CommandAdapt['wordTool']
|
||||||
public executeSetHTML: CommandAdapt['setHTML']
|
public executeSetHTML: CommandAdapt['setHTML']
|
||||||
|
public executeSetGroup: CommandAdapt['setGroup']
|
||||||
|
public executeDeleteGroup: CommandAdapt['deleteGroup']
|
||||||
|
public executeLocationGroup: CommandAdapt['locationGroup']
|
||||||
public getCatalog: CommandAdapt['getCatalog']
|
public getCatalog: CommandAdapt['getCatalog']
|
||||||
public getImage: CommandAdapt['getImage']
|
public getImage: CommandAdapt['getImage']
|
||||||
public getValue: CommandAdapt['getValue']
|
public getValue: CommandAdapt['getValue']
|
||||||
@@ -89,6 +92,7 @@ export class Command {
|
|||||||
public getPaperMargin: CommandAdapt['getPaperMargin']
|
public getPaperMargin: CommandAdapt['getPaperMargin']
|
||||||
public getSearchNavigateInfo: CommandAdapt['getSearchNavigateInfo']
|
public getSearchNavigateInfo: CommandAdapt['getSearchNavigateInfo']
|
||||||
public getLocale: CommandAdapt['getLocale']
|
public getLocale: CommandAdapt['getLocale']
|
||||||
|
public getGroupIds: CommandAdapt['getGroupIds']
|
||||||
|
|
||||||
constructor(adapt: CommandAdapt) {
|
constructor(adapt: CommandAdapt) {
|
||||||
// 全局命令
|
// 全局命令
|
||||||
@@ -173,7 +177,9 @@ export class Command {
|
|||||||
this.executeLocationCatalog = adapt.locationCatalog.bind(adapt)
|
this.executeLocationCatalog = adapt.locationCatalog.bind(adapt)
|
||||||
this.executeWordTool = adapt.wordTool.bind(adapt)
|
this.executeWordTool = adapt.wordTool.bind(adapt)
|
||||||
this.executeSetHTML = adapt.setHTML.bind(adapt)
|
this.executeSetHTML = adapt.setHTML.bind(adapt)
|
||||||
|
this.executeSetGroup = adapt.setGroup.bind(adapt)
|
||||||
|
this.executeDeleteGroup = adapt.deleteGroup.bind(adapt)
|
||||||
|
this.executeLocationGroup = adapt.locationGroup.bind(adapt)
|
||||||
// 获取
|
// 获取
|
||||||
this.getImage = adapt.getImage.bind(adapt)
|
this.getImage = adapt.getImage.bind(adapt)
|
||||||
this.getValue = adapt.getValue.bind(adapt)
|
this.getValue = adapt.getValue.bind(adapt)
|
||||||
@@ -188,5 +194,6 @@ export class Command {
|
|||||||
this.getPaperMargin = adapt.getPaperMargin.bind(adapt)
|
this.getPaperMargin = adapt.getPaperMargin.bind(adapt)
|
||||||
this.getSearchNavigateInfo = adapt.getSearchNavigateInfo.bind(adapt)
|
this.getSearchNavigateInfo = adapt.getSearchNavigateInfo.bind(adapt)
|
||||||
this.getLocale = adapt.getLocale.bind(adapt)
|
this.getLocale = adapt.getLocale.bind(adapt)
|
||||||
|
this.getGroupIds = adapt.getGroupIds.bind(adapt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,7 +321,10 @@ export class CommandAdapt {
|
|||||||
selection.forEach(el => {
|
selection.forEach(el => {
|
||||||
el.underline = !!~noUnderlineIndex
|
el.underline = !!~noUnderlineIndex
|
||||||
})
|
})
|
||||||
this.draw.render({ isSetCursor: false })
|
this.draw.render({
|
||||||
|
isSetCursor: false,
|
||||||
|
isCompute: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public strikeout() {
|
public strikeout() {
|
||||||
@@ -333,7 +336,10 @@ export class CommandAdapt {
|
|||||||
selection.forEach(el => {
|
selection.forEach(el => {
|
||||||
el.strikeout = !!~noStrikeoutIndex
|
el.strikeout = !!~noStrikeoutIndex
|
||||||
})
|
})
|
||||||
this.draw.render({ isSetCursor: false })
|
this.draw.render({
|
||||||
|
isSetCursor: false,
|
||||||
|
isCompute: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public superscript() {
|
public superscript() {
|
||||||
@@ -1922,4 +1928,45 @@ export class CommandAdapt {
|
|||||||
footer: getElementList(footer)
|
footer: getElementList(footer)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setGroup(): string | null {
|
||||||
|
const isReadonly = this.draw.isReadonly()
|
||||||
|
if (isReadonly) return null
|
||||||
|
return this.draw.getGroup().setGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteGroup(groupId: string) {
|
||||||
|
const isReadonly = this.draw.isReadonly()
|
||||||
|
if (isReadonly) return
|
||||||
|
this.draw.getGroup().deleteGroup(groupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGroupIds(): Promise<string[]> {
|
||||||
|
return this.draw.getWorkerManager().getGroupIds()
|
||||||
|
}
|
||||||
|
|
||||||
|
public locationGroup(groupId: string) {
|
||||||
|
const elementList = this.draw.getOriginalMainElementList()
|
||||||
|
const context = this.draw
|
||||||
|
.getGroup()
|
||||||
|
.getContextByGroupId(elementList, groupId)
|
||||||
|
if (!context) return
|
||||||
|
const { isTable, index, trIndex, tdIndex, tdId, trId, tableId, endIndex } =
|
||||||
|
context
|
||||||
|
this.position.setPositionContext({
|
||||||
|
isTable,
|
||||||
|
index,
|
||||||
|
trIndex,
|
||||||
|
tdIndex,
|
||||||
|
tdId,
|
||||||
|
trId,
|
||||||
|
tableId
|
||||||
|
})
|
||||||
|
this.range.setRange(endIndex, endIndex)
|
||||||
|
this.draw.render({
|
||||||
|
curIndex: endIndex,
|
||||||
|
isCompute: false,
|
||||||
|
isSubmitHistory: false
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ import { Placeholder } from './frame/Placeholder'
|
|||||||
import { WORD_LIKE_REG } from '../../dataset/constant/Regular'
|
import { WORD_LIKE_REG } from '../../dataset/constant/Regular'
|
||||||
import { EventBus } from '../event/eventbus/EventBus'
|
import { EventBus } from '../event/eventbus/EventBus'
|
||||||
import { EventBusMap } from '../../interface/EventBus'
|
import { EventBusMap } from '../../interface/EventBus'
|
||||||
|
import { Group } from './interactive/Group'
|
||||||
|
|
||||||
export class Draw {
|
export class Draw {
|
||||||
private container: HTMLDivElement
|
private container: HTMLDivElement
|
||||||
@@ -106,6 +107,7 @@ export class Draw {
|
|||||||
private margin: Margin
|
private margin: Margin
|
||||||
private background: Background
|
private background: Background
|
||||||
private search: Search
|
private search: Search
|
||||||
|
private group: Group
|
||||||
private underline: Underline
|
private underline: Underline
|
||||||
private strikeout: Strikeout
|
private strikeout: Strikeout
|
||||||
private highlight: Highlight
|
private highlight: Highlight
|
||||||
@@ -175,6 +177,7 @@ export class Draw {
|
|||||||
this.margin = new Margin(this)
|
this.margin = new Margin(this)
|
||||||
this.background = new Background(this)
|
this.background = new Background(this)
|
||||||
this.search = new Search(this)
|
this.search = new Search(this)
|
||||||
|
this.group = new Group(this)
|
||||||
this.underline = new Underline(this)
|
this.underline = new Underline(this)
|
||||||
this.strikeout = new Strikeout(this)
|
this.strikeout = new Strikeout(this)
|
||||||
this.highlight = new Highlight(this)
|
this.highlight = new Highlight(this)
|
||||||
@@ -451,6 +454,10 @@ export class Draw {
|
|||||||
return this.search
|
return this.search
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getGroup(): Group {
|
||||||
|
return this.group
|
||||||
|
}
|
||||||
|
|
||||||
public getHistoryManager(): HistoryManager {
|
public getHistoryManager(): HistoryManager {
|
||||||
return this.historyManager
|
return this.historyManager
|
||||||
}
|
}
|
||||||
@@ -1443,8 +1450,13 @@ export class Draw {
|
|||||||
const { rowList, pageNo, elementList, positionList, startIndex, zone } =
|
const { rowList, pageNo, elementList, positionList, startIndex, zone } =
|
||||||
payload
|
payload
|
||||||
const isPrintMode = this.mode === EditorMode.PRINT
|
const isPrintMode = this.mode === EditorMode.PRINT
|
||||||
const { scale, tdPadding, defaultBasicRowMarginHeight, defaultRowMargin } =
|
const {
|
||||||
this.options
|
scale,
|
||||||
|
tdPadding,
|
||||||
|
defaultBasicRowMarginHeight,
|
||||||
|
defaultRowMargin,
|
||||||
|
group
|
||||||
|
} = this.options
|
||||||
const { isCrossRowCol, tableId } = this.range.getRange()
|
const { isCrossRowCol, tableId } = this.range.getRange()
|
||||||
let index = startIndex
|
let index = startIndex
|
||||||
for (let i = 0; i < rowList.length; i++) {
|
for (let i = 0; i < rowList.length; i++) {
|
||||||
@@ -1610,6 +1622,10 @@ export class Draw {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 组信息记录
|
||||||
|
if (!group.disabled && element.groupIds) {
|
||||||
|
this.group.recordFillInfo(element, x, y, metrics.width, curRow.height)
|
||||||
|
}
|
||||||
index++
|
index++
|
||||||
// 绘制表格内元素
|
// 绘制表格内元素
|
||||||
if (element.type === ElementType.TABLE) {
|
if (element.type === ElementType.TABLE) {
|
||||||
@@ -1641,6 +1657,8 @@ export class Draw {
|
|||||||
}
|
}
|
||||||
// 绘制富文本及文字
|
// 绘制富文本及文字
|
||||||
this._drawRichText(ctx)
|
this._drawRichText(ctx)
|
||||||
|
// 绘制批注样式
|
||||||
|
this.group.render(ctx)
|
||||||
// 绘制选区
|
// 绘制选区
|
||||||
if (!isPrintMode) {
|
if (!isPrintMode) {
|
||||||
if (rangeRecord.width && rangeRecord.height) {
|
if (rangeRecord.width && rangeRecord.height) {
|
||||||
@@ -1767,7 +1785,8 @@ export class Draw {
|
|||||||
isSetCursor = true,
|
isSetCursor = true,
|
||||||
isCompute = true,
|
isCompute = true,
|
||||||
isLazy = true,
|
isLazy = true,
|
||||||
isInit = false
|
isInit = false,
|
||||||
|
isSourceHistory = false
|
||||||
} = payload || {}
|
} = payload || {}
|
||||||
let { curIndex } = payload || {}
|
let { curIndex } = payload || {}
|
||||||
const innerWidth = this.getInnerWidth()
|
const innerWidth = this.getInnerWidth()
|
||||||
@@ -1861,7 +1880,11 @@ export class Draw {
|
|||||||
self.footer.setElementList(deepClone(oldFooterElementList))
|
self.footer.setElementList(deepClone(oldFooterElementList))
|
||||||
self.elementList = deepClone(oldElementList)
|
self.elementList = deepClone(oldElementList)
|
||||||
self.range.setRange(startIndex, endIndex)
|
self.range.setRange(startIndex, endIndex)
|
||||||
self.render({ curIndex, isSubmitHistory: false })
|
self.render({
|
||||||
|
curIndex,
|
||||||
|
isSubmitHistory: false,
|
||||||
|
isSourceHistory: true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 信息变动回调
|
// 信息变动回调
|
||||||
@@ -1882,7 +1905,7 @@ export class Draw {
|
|||||||
this.eventBus.emit('pageSizeChange', this.pageRowList.length)
|
this.eventBus.emit('pageSizeChange', this.pageRowList.length)
|
||||||
}
|
}
|
||||||
// 文档内容改变
|
// 文档内容改变
|
||||||
if (isSubmitHistory && !isInit) {
|
if ((isSubmitHistory || isSourceHistory) && !isInit) {
|
||||||
if (this.listener.contentChange) {
|
if (this.listener.contentChange) {
|
||||||
this.listener.contentChange()
|
this.listener.contentChange()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { EditorZone } from '../../../dataset/enum/Editor'
|
||||||
|
import { ElementType } from '../../../dataset/enum/Element'
|
||||||
|
import { DeepRequired } from '../../../interface/Common'
|
||||||
|
import { IEditorOption } from '../../../interface/Editor'
|
||||||
|
import { IElement, IElementFillRect } from '../../../interface/Element'
|
||||||
|
import { IPositionContext } from '../../../interface/Position'
|
||||||
|
import { IRange } from '../../../interface/Range'
|
||||||
|
import { getUUID } from '../../../utils'
|
||||||
|
import { RangeManager } from '../../range/RangeManager'
|
||||||
|
import { Draw } from '../Draw'
|
||||||
|
|
||||||
|
export class Group {
|
||||||
|
private draw: Draw
|
||||||
|
private options: DeepRequired<IEditorOption>
|
||||||
|
private range: RangeManager
|
||||||
|
private fillRectMap: Map<string, IElementFillRect>
|
||||||
|
|
||||||
|
constructor(draw: Draw) {
|
||||||
|
this.draw = draw
|
||||||
|
this.options = draw.getOptions()
|
||||||
|
this.range = draw.getRange()
|
||||||
|
this.fillRectMap = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
public setGroup(): string | null {
|
||||||
|
if (
|
||||||
|
this.draw.isReadonly() ||
|
||||||
|
this.draw.getZone().getZone() !== EditorZone.MAIN
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const selection = this.range.getSelection()
|
||||||
|
if (!selection) return null
|
||||||
|
const groupId = getUUID()
|
||||||
|
selection.forEach(el => {
|
||||||
|
if (!Array.isArray(el.groupIds)) {
|
||||||
|
el.groupIds = []
|
||||||
|
}
|
||||||
|
el.groupIds.push(groupId)
|
||||||
|
})
|
||||||
|
this.draw.render({
|
||||||
|
isSetCursor: false,
|
||||||
|
isCompute: false
|
||||||
|
})
|
||||||
|
return groupId
|
||||||
|
}
|
||||||
|
|
||||||
|
public getElementListByGroupId(
|
||||||
|
elementList: IElement[],
|
||||||
|
groupId: string
|
||||||
|
): IElement[] {
|
||||||
|
const groupElementList: IElement[] = []
|
||||||
|
for (let e = 0; e < elementList.length; e++) {
|
||||||
|
const element = elementList[e]
|
||||||
|
if (element.type === ElementType.TABLE) {
|
||||||
|
const trList = element.trList!
|
||||||
|
for (let r = 0; r < trList.length; r++) {
|
||||||
|
const tr = trList[r]
|
||||||
|
for (let d = 0; d < tr.tdList.length; d++) {
|
||||||
|
const td = tr.tdList[d]
|
||||||
|
const tdGroupElementList = this.getElementListByGroupId(
|
||||||
|
td.value,
|
||||||
|
groupId
|
||||||
|
)
|
||||||
|
if (tdGroupElementList.length) {
|
||||||
|
groupElementList.push(...tdGroupElementList)
|
||||||
|
return groupElementList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (element?.groupIds?.includes(groupId)) {
|
||||||
|
groupElementList.push(element)
|
||||||
|
const nextElement = elementList[e + 1]
|
||||||
|
if (!nextElement?.groupIds?.includes(groupId)) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupElementList
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteGroup(groupId: string) {
|
||||||
|
if (this.draw.isReadonly()) return
|
||||||
|
// 仅主体内容可以成组
|
||||||
|
const elementList = this.draw.getOriginalMainElementList()
|
||||||
|
const groupElementList = this.getElementListByGroupId(elementList, groupId)
|
||||||
|
if (!groupElementList.length) return
|
||||||
|
for (let e = 0; e < groupElementList.length; e++) {
|
||||||
|
const element = groupElementList[e]
|
||||||
|
const groupIds = element.groupIds!
|
||||||
|
const groupIndex = groupIds.findIndex(id => id === groupId)
|
||||||
|
groupIds.splice(groupIndex, 1)
|
||||||
|
// 不包含成组时删除字段,减少存储及内存占用
|
||||||
|
if (!groupIds.length) {
|
||||||
|
delete element.groupIds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.draw.render({
|
||||||
|
isSetCursor: false,
|
||||||
|
isCompute: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public getContextByGroupId(
|
||||||
|
elementList: IElement[],
|
||||||
|
groupId: string
|
||||||
|
): (IRange & IPositionContext) | null {
|
||||||
|
for (let e = 0; e < elementList.length; e++) {
|
||||||
|
const element = elementList[e]
|
||||||
|
if (element.type === ElementType.TABLE) {
|
||||||
|
const trList = element.trList!
|
||||||
|
for (let r = 0; r < trList.length; r++) {
|
||||||
|
const tr = trList[r]
|
||||||
|
for (let d = 0; d < tr.tdList.length; d++) {
|
||||||
|
const td = tr.tdList[d]
|
||||||
|
const range = this.getContextByGroupId(td.value, groupId)
|
||||||
|
if (range) {
|
||||||
|
return {
|
||||||
|
...range,
|
||||||
|
isTable: true,
|
||||||
|
index: e,
|
||||||
|
trIndex: r,
|
||||||
|
tdIndex: d,
|
||||||
|
tdId: td.id,
|
||||||
|
trId: tr.id,
|
||||||
|
tableId: element.tableId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const nextElement = elementList[e + 1]
|
||||||
|
if (
|
||||||
|
element.groupIds?.includes(groupId) &&
|
||||||
|
!nextElement?.groupIds?.includes(groupId)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
isTable: false,
|
||||||
|
startIndex: e,
|
||||||
|
endIndex: e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearFillInfo() {
|
||||||
|
this.fillRectMap.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
public recordFillInfo(
|
||||||
|
element: IElement,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number
|
||||||
|
) {
|
||||||
|
const groupIds = element.groupIds
|
||||||
|
if (!groupIds) return
|
||||||
|
for (const groupId of groupIds) {
|
||||||
|
const fillRect = this.fillRectMap.get(groupId)
|
||||||
|
if (!fillRect) {
|
||||||
|
this.fillRectMap.set(groupId, {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
fillRect.width += width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(ctx: CanvasRenderingContext2D) {
|
||||||
|
if (!this.fillRectMap.size) return
|
||||||
|
// 当前激活组信息
|
||||||
|
const range = this.range.getRange()
|
||||||
|
const elementList = this.draw.getElementList()
|
||||||
|
const anchorGroupIds = elementList[range.endIndex]?.groupIds
|
||||||
|
const {
|
||||||
|
group: { backgroundColor, opacity, activeOpacity, activeBackgroundColor }
|
||||||
|
} = this.options
|
||||||
|
ctx.save()
|
||||||
|
this.fillRectMap.forEach((fillRect, groupId) => {
|
||||||
|
const { x, y, width, height } = fillRect
|
||||||
|
if (anchorGroupIds?.includes(groupId)) {
|
||||||
|
ctx.globalAlpha = activeOpacity
|
||||||
|
ctx.fillStyle = activeBackgroundColor
|
||||||
|
} else {
|
||||||
|
ctx.globalAlpha = opacity
|
||||||
|
ctx.fillStyle = backgroundColor
|
||||||
|
}
|
||||||
|
ctx.fillRect(x, y, width, height)
|
||||||
|
})
|
||||||
|
ctx.restore()
|
||||||
|
this.clearFillInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,8 @@ export function input(data: string, host: CanvasEvent) {
|
|||||||
(copyElement.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT)
|
(copyElement.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT)
|
||||||
) {
|
) {
|
||||||
EDITOR_ELEMENT_COPY_ATTR.forEach(attr => {
|
EDITOR_ELEMENT_COPY_ATTR.forEach(attr => {
|
||||||
|
// 在分组外无需复制分组信息
|
||||||
|
if (attr === 'groupIds' && !nextElement?.groupIds) return
|
||||||
const value = copyElement[attr] as never
|
const value = copyElement[attr] as never
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
newElement[attr] = value
|
newElement[attr] = value
|
||||||
|
|||||||
@@ -339,6 +339,8 @@ export class RangeManager {
|
|||||||
const painter = !!this.draw.getPainterStyle()
|
const painter = !!this.draw.getPainterStyle()
|
||||||
const undo = this.historyManager.isCanUndo()
|
const undo = this.historyManager.isCanUndo()
|
||||||
const redo = this.historyManager.isCanRedo()
|
const redo = this.historyManager.isCanRedo()
|
||||||
|
// 组信息
|
||||||
|
const groupIds = curElement.groupIds || null
|
||||||
const rangeStyle: IRangeStyle = {
|
const rangeStyle: IRangeStyle = {
|
||||||
type,
|
type,
|
||||||
undo,
|
undo,
|
||||||
@@ -357,7 +359,8 @@ export class RangeManager {
|
|||||||
dashArray,
|
dashArray,
|
||||||
level,
|
level,
|
||||||
listType,
|
listType,
|
||||||
listStyle
|
listStyle,
|
||||||
|
groupIds
|
||||||
}
|
}
|
||||||
if (rangeStyleChangeListener) {
|
if (rangeStyleChangeListener) {
|
||||||
rangeStyleChangeListener(rangeStyle)
|
rangeStyleChangeListener(rangeStyle)
|
||||||
@@ -396,7 +399,8 @@ export class RangeManager {
|
|||||||
dashArray: [],
|
dashArray: [],
|
||||||
level: null,
|
level: null,
|
||||||
listType: null,
|
listType: null,
|
||||||
listStyle: null
|
listStyle: null,
|
||||||
|
groupIds: null
|
||||||
}
|
}
|
||||||
if (rangeStyleChangeListener) {
|
if (rangeStyleChangeListener) {
|
||||||
rangeStyleChangeListener(rangeStyle)
|
rangeStyleChangeListener(rangeStyle)
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { Draw } from '../draw/Draw'
|
import { Draw } from '../draw/Draw'
|
||||||
import WordCountWorker from './works/wordCount?worker&inline'
|
import WordCountWorker from './works/wordCount?worker&inline'
|
||||||
import CatalogWorker from './works/catalog?worker&inline'
|
import CatalogWorker from './works/catalog?worker&inline'
|
||||||
|
import GroupWorker from './works/group?worker&inline'
|
||||||
import { ICatalog } from '../../interface/Catalog'
|
import { ICatalog } from '../../interface/Catalog'
|
||||||
|
|
||||||
export class WorkerManager {
|
export class WorkerManager {
|
||||||
private draw: Draw
|
private draw: Draw
|
||||||
private wordCountWorker: Worker
|
private wordCountWorker: Worker
|
||||||
private catalogWorker: Worker
|
private catalogWorker: Worker
|
||||||
|
private groupWorker: Worker
|
||||||
|
|
||||||
constructor(draw: Draw) {
|
constructor(draw: Draw) {
|
||||||
this.draw = draw
|
this.draw = draw
|
||||||
this.wordCountWorker = new WordCountWorker()
|
this.wordCountWorker = new WordCountWorker()
|
||||||
this.catalogWorker = new CatalogWorker()
|
this.catalogWorker = new CatalogWorker()
|
||||||
|
this.groupWorker = new GroupWorker()
|
||||||
}
|
}
|
||||||
|
|
||||||
public getWordCount(): Promise<number> {
|
public getWordCount(): Promise<number> {
|
||||||
@@ -43,4 +46,19 @@ export class WorkerManager {
|
|||||||
this.catalogWorker.postMessage(elementList)
|
this.catalogWorker.postMessage(elementList)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getGroupIds(): Promise<string[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.groupWorker.onmessage = evt => {
|
||||||
|
resolve(evt.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.groupWorker.onerror = evt => {
|
||||||
|
reject(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
const elementList = this.draw.getOriginalMainElementList()
|
||||||
|
this.groupWorker.postMessage(elementList)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { IElement } from '../../../interface/Element'
|
||||||
|
|
||||||
|
enum ElementType {
|
||||||
|
TABLE = 'table'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupIds(elementList: IElement[]): string[] {
|
||||||
|
const groupIds: string[] = []
|
||||||
|
for (const element of elementList) {
|
||||||
|
if (element.type === ElementType.TABLE) {
|
||||||
|
const trList = element.trList!
|
||||||
|
for (let r = 0; r < trList.length; r++) {
|
||||||
|
const tr = trList[r]
|
||||||
|
for (let d = 0; d < tr.tdList.length; d++) {
|
||||||
|
const td = tr.tdList[d]
|
||||||
|
groupIds.push(...getGroupIds(td.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!element.groupIds) continue
|
||||||
|
for (const groupId of element.groupIds) {
|
||||||
|
if (!groupIds.includes(groupId)) {
|
||||||
|
groupIds.push(groupId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupIds
|
||||||
|
}
|
||||||
|
|
||||||
|
onmessage = evt => {
|
||||||
|
const elementList = <IElement[]>evt.data
|
||||||
|
const groupIds = getGroupIds(elementList)
|
||||||
|
postMessage(groupIds)
|
||||||
|
}
|
||||||
@@ -26,7 +26,8 @@ export const EDITOR_ELEMENT_COPY_ATTR: Array<keyof IElement> = [
|
|||||||
'url',
|
'url',
|
||||||
'hyperlinkId',
|
'hyperlinkId',
|
||||||
'dateId',
|
'dateId',
|
||||||
'dateFormat'
|
'dateFormat',
|
||||||
|
'groupIds'
|
||||||
]
|
]
|
||||||
|
|
||||||
export const EDITOR_ELEMENT_ZIP_ATTR: Array<keyof IElement> = [
|
export const EDITOR_ELEMENT_ZIP_ATTR: Array<keyof IElement> = [
|
||||||
@@ -56,7 +57,8 @@ export const EDITOR_ELEMENT_ZIP_ATTR: Array<keyof IElement> = [
|
|||||||
'level',
|
'level',
|
||||||
'listType',
|
'listType',
|
||||||
'listStyle',
|
'listStyle',
|
||||||
'listWrap'
|
'listWrap',
|
||||||
|
'groupIds'
|
||||||
]
|
]
|
||||||
|
|
||||||
export const TABLE_CONTEXT_ATTR: Array<keyof IElement> = [
|
export const TABLE_CONTEXT_ATTR: Array<keyof IElement> = [
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { IGroup } from '../../interface/Group'
|
||||||
|
|
||||||
|
export const defaultGroupOption: Readonly<Required<IGroup>> = {
|
||||||
|
opacity: 0.1,
|
||||||
|
backgroundColor: '#E99D00',
|
||||||
|
activeOpacity: 0.5,
|
||||||
|
activeBackgroundColor: '#E99D00',
|
||||||
|
disabled: false
|
||||||
|
}
|
||||||
@@ -5,7 +5,8 @@ export enum EditorComponent {
|
|||||||
FOOTER = 'footer',
|
FOOTER = 'footer',
|
||||||
CONTEXTMENU = 'contextmenu',
|
CONTEXTMENU = 'contextmenu',
|
||||||
POPUP = 'popup',
|
POPUP = 'popup',
|
||||||
CATALOG = 'catalog'
|
CATALOG = 'catalog',
|
||||||
|
COMMENT = 'comment'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EditorContext {
|
export enum EditorContext {
|
||||||
|
|||||||
+8
-1
@@ -59,6 +59,8 @@ import { Plugin } from './core/plugin/Plugin'
|
|||||||
import { UsePlugin } from './interface/Plugin'
|
import { UsePlugin } from './interface/Plugin'
|
||||||
import { EventBus } from './core/event/eventbus/EventBus'
|
import { EventBus } from './core/event/eventbus/EventBus'
|
||||||
import { EventBusMap } from './interface/EventBus'
|
import { EventBusMap } from './interface/EventBus'
|
||||||
|
import { IGroup } from './interface/Group'
|
||||||
|
import { defaultGroupOption } from './dataset/constant/Group'
|
||||||
|
|
||||||
export default class Editor {
|
export default class Editor {
|
||||||
public command: Command
|
public command: Command
|
||||||
@@ -109,6 +111,10 @@ export default class Editor {
|
|||||||
...defaultPlaceholderOption,
|
...defaultPlaceholderOption,
|
||||||
...options.placeholder
|
...options.placeholder
|
||||||
}
|
}
|
||||||
|
const groupOptions: Required<IGroup> = {
|
||||||
|
...defaultGroupOption,
|
||||||
|
...options.group
|
||||||
|
}
|
||||||
|
|
||||||
const editorOptions: DeepRequired<IEditorOption> = {
|
const editorOptions: DeepRequired<IEditorOption> = {
|
||||||
mode: EditorMode.EDIT,
|
mode: EditorMode.EDIT,
|
||||||
@@ -158,7 +164,8 @@ export default class Editor {
|
|||||||
checkbox: checkboxOptions,
|
checkbox: checkboxOptions,
|
||||||
cursor: cursorOptions,
|
cursor: cursorOptions,
|
||||||
title: titleOptions,
|
title: titleOptions,
|
||||||
placeholder: placeholderOptions
|
placeholder: placeholderOptions,
|
||||||
|
group: groupOptions
|
||||||
}
|
}
|
||||||
// 数据处理
|
// 数据处理
|
||||||
let headerElementList: IElement[] = []
|
let headerElementList: IElement[] = []
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface IDrawOption {
|
|||||||
isCompute?: boolean
|
isCompute?: boolean
|
||||||
isLazy?: boolean
|
isLazy?: boolean
|
||||||
isInit?: boolean
|
isInit?: boolean
|
||||||
|
isSourceHistory?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDrawImagePayload {
|
export interface IDrawImagePayload {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { ICheckboxOption } from './Checkbox'
|
|||||||
import { IControlOption } from './Control'
|
import { IControlOption } from './Control'
|
||||||
import { ICursorOption } from './Cursor'
|
import { ICursorOption } from './Cursor'
|
||||||
import { IFooter } from './Footer'
|
import { IFooter } from './Footer'
|
||||||
|
import { IGroup } from './Group'
|
||||||
import { IHeader } from './Header'
|
import { IHeader } from './Header'
|
||||||
import { IMargin } from './Margin'
|
import { IMargin } from './Margin'
|
||||||
import { IPageNumber } from './PageNumber'
|
import { IPageNumber } from './PageNumber'
|
||||||
@@ -70,6 +71,7 @@ export interface IEditorOption {
|
|||||||
cursor?: ICursorOption
|
cursor?: ICursorOption
|
||||||
title?: ITitleOption
|
title?: ITitleOption
|
||||||
placeholder?: IPlaceholder
|
placeholder?: IPlaceholder
|
||||||
|
group?: IGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEditorResult {
|
export interface IEditorResult {
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ export interface IElementStyle {
|
|||||||
letterSpacing?: number
|
letterSpacing?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IElementGroup {
|
||||||
|
groupIds?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface ITitleElement {
|
export interface ITitleElement {
|
||||||
valueList?: IElement[]
|
valueList?: IElement[]
|
||||||
level?: TitleLevel
|
level?: TitleLevel
|
||||||
@@ -103,6 +107,7 @@ export interface IBlockElement {
|
|||||||
|
|
||||||
export type IElement = IElementBasic &
|
export type IElement = IElementBasic &
|
||||||
IElementStyle &
|
IElementStyle &
|
||||||
|
IElementGroup &
|
||||||
ITable &
|
ITable &
|
||||||
IHyperlinkElement &
|
IHyperlinkElement &
|
||||||
ISuperscriptSubscript &
|
ISuperscriptSubscript &
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface IGroup {
|
||||||
|
opacity?: number
|
||||||
|
backgroundColor?: string
|
||||||
|
activeOpacity?: number
|
||||||
|
activeBackgroundColor?: string
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ export interface IRangeStyle {
|
|||||||
level: TitleLevel | null
|
level: TitleLevel | null
|
||||||
listType: ListType | null
|
listType: ListType | null
|
||||||
listStyle: ListStyle | null
|
listStyle: ListStyle | null
|
||||||
|
groupIds: string[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IRangeStyleChange = (payload: IRangeStyle) => void
|
export type IRangeStyleChange = (payload: IRangeStyle) => void
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cloneProperty, deepClone, getUUID, splitText } from '.'
|
import { cloneProperty, deepClone, getUUID, isArrayEqual, splitText } from '.'
|
||||||
import {
|
import {
|
||||||
ElementType,
|
ElementType,
|
||||||
IEditorOption,
|
IEditorOption,
|
||||||
@@ -375,7 +375,17 @@ export function isSameElementExceptValue(
|
|||||||
if (sourceKeys.length !== targetKeys.length) return false
|
if (sourceKeys.length !== targetKeys.length) return false
|
||||||
for (let s = 0; s < sourceKeys.length; s++) {
|
for (let s = 0; s < sourceKeys.length; s++) {
|
||||||
const key = sourceKeys[s] as never
|
const key = sourceKeys[s] as never
|
||||||
|
// 值不需要校验
|
||||||
if (key === 'value') continue
|
if (key === 'value') continue
|
||||||
|
// groupIds数组需特殊校验数组是否相等
|
||||||
|
if (
|
||||||
|
key === 'groupIds' &&
|
||||||
|
Array.isArray(source[key]) &&
|
||||||
|
Array.isArray(target[key]) &&
|
||||||
|
isArrayEqual(source[key], target[key])
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (source[key] !== target[key]) {
|
if (source[key] !== target[key]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,3 +249,10 @@ export function findScrollContainer(element: HTMLElement) {
|
|||||||
}
|
}
|
||||||
return document.documentElement
|
return document.documentElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isArrayEqual(arr1: unknown[], arr2: unknown[]): boolean {
|
||||||
|
if (arr1.length !== arr2.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !arr1.some(item => !arr2.includes(item))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user