feat: add zone tooltip #367
This commit is contained in:
@@ -96,5 +96,9 @@ interface ILang {
|
|||||||
pageBreak: {
|
pageBreak: {
|
||||||
displayName: string
|
displayName: string
|
||||||
}
|
}
|
||||||
|
zone: {
|
||||||
|
headerTip: string
|
||||||
|
footerTip: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ interface IEditorOption {
|
|||||||
placeholder?: IPlaceholder // Placeholder text
|
placeholder?: IPlaceholder // Placeholder text
|
||||||
group?: IGroup // Group option. {opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean}
|
group?: IGroup // Group option. {opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean}
|
||||||
pageBreak?: IPageBreak // PageBreak option。{font?:string; fontSize?:number; lineDash?:number[];}
|
pageBreak?: IPageBreak // PageBreak option。{font?:string; fontSize?:number; lineDash?:number[];}
|
||||||
|
zone?: IZoneOption // Zone option。{tipDisabled?:boolean;}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -96,5 +96,9 @@ interface ILang {
|
|||||||
pageBreak: {
|
pageBreak: {
|
||||||
displayName: string
|
displayName: string
|
||||||
}
|
}
|
||||||
|
zone: {
|
||||||
|
headerTip: string
|
||||||
|
footerTip: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ interface IEditorOption {
|
|||||||
placeholder?: IPlaceholder // 编辑器空白占位文本
|
placeholder?: IPlaceholder // 编辑器空白占位文本
|
||||||
group?: IGroup // 成组配置。{opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean}
|
group?: IGroup // 成组配置。{opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean}
|
||||||
pageBreak?: IPageBreak // 分页符配置。{font?:string; fontSize?:number; lineDash?:number[];}
|
pageBreak?: IPageBreak // 分页符配置。{font?:string; fontSize?:number; lineDash?:number[];}
|
||||||
|
zone?: IZoneOption // 编辑器区域配置。{tipDisabled?:boolean;}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -32,3 +32,30 @@
|
|||||||
.ce-zone-indicator-border__right {
|
.ce-zone-indicator-border__right {
|
||||||
border-right: 2px dashed rgb(238, 238, 238);
|
border-right: 2px dashed rgb(238, 238, 238);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ce-zone-tip {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
height: 30px;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: fixed;
|
||||||
|
opacity: .9;
|
||||||
|
background-color: #000000;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 9;
|
||||||
|
transition: all .3s;
|
||||||
|
outline: none;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translate(10px, 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ce-zone-tip.show {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ce-zone-tip span {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
@@ -76,5 +76,9 @@
|
|||||||
},
|
},
|
||||||
"pageBreak": {
|
"pageBreak": {
|
||||||
"displayName": "Page Break"
|
"displayName": "Page Break"
|
||||||
|
},
|
||||||
|
"zone": {
|
||||||
|
"headerTip": "Double click to edit header",
|
||||||
|
"footerTip": "Double click to edit footer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,5 +76,9 @@
|
|||||||
},
|
},
|
||||||
"pageBreak": {
|
"pageBreak": {
|
||||||
"displayName": "分页符"
|
"displayName": "分页符"
|
||||||
|
},
|
||||||
|
"zone": {
|
||||||
|
"headerTip": "双击编辑页眉",
|
||||||
|
"footerTip": "双击编辑页脚"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ export class Position {
|
|||||||
positionList = this.getOriginalPositionList()
|
positionList = this.getOriginalPositionList()
|
||||||
}
|
}
|
||||||
const zoneManager = this.draw.getZone()
|
const zoneManager = this.draw.getZone()
|
||||||
const curPageNo = this.draw.getPageNo()
|
const curPageNo = payload.pageNo ?? this.draw.getPageNo()
|
||||||
const isMainActive = zoneManager.isMainActive()
|
const isMainActive = zoneManager.isMainActive()
|
||||||
const positionNo = isMainActive ? curPageNo : 0
|
const positionNo = isMainActive ? curPageNo : 0
|
||||||
for (let j = 0; j < positionList.length; j++) {
|
for (let j = 0; j < positionList.length; j++) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { IEditorOption } from '../../interface/Editor'
|
|||||||
import { nextTick } from '../../utils'
|
import { nextTick } from '../../utils'
|
||||||
import { Draw } from '../draw/Draw'
|
import { Draw } from '../draw/Draw'
|
||||||
import { I18n } from '../i18n/I18n'
|
import { I18n } from '../i18n/I18n'
|
||||||
|
import { ZoneTip } from './ZoneTip'
|
||||||
|
|
||||||
export class Zone {
|
export class Zone {
|
||||||
private readonly INDICATOR_PADDING = 2
|
private readonly INDICATOR_PADDING = 2
|
||||||
@@ -24,6 +25,10 @@ export class Zone {
|
|||||||
this.container = draw.getContainer()
|
this.container = draw.getContainer()
|
||||||
this.currentZone = EditorZone.MAIN
|
this.currentZone = EditorZone.MAIN
|
||||||
this.indicatorContainer = null
|
this.indicatorContainer = null
|
||||||
|
// 区域提示
|
||||||
|
if (!this.options.zone.tipDisabled) {
|
||||||
|
new ZoneTip(draw)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public isHeaderActive(): boolean {
|
public isHeaderActive(): boolean {
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { EDITOR_PREFIX } from '../../dataset/constant/Editor'
|
||||||
|
import { EditorZone } from '../../dataset/enum/Editor'
|
||||||
|
import { throttle } from '../../utils'
|
||||||
|
import { Draw } from '../draw/Draw'
|
||||||
|
import { I18n } from '../i18n/I18n'
|
||||||
|
import { Position } from '../position/Position'
|
||||||
|
|
||||||
|
export class ZoneTip {
|
||||||
|
private position: Position
|
||||||
|
private i18n: I18n
|
||||||
|
private container: HTMLDivElement
|
||||||
|
private pageContainer: HTMLDivElement
|
||||||
|
|
||||||
|
private isDisableMouseMove: boolean
|
||||||
|
private tipContainer: HTMLDivElement
|
||||||
|
private tipContent: HTMLSpanElement
|
||||||
|
private currentMoveZone: EditorZone | undefined
|
||||||
|
|
||||||
|
constructor(draw: Draw) {
|
||||||
|
this.position = draw.getPosition()
|
||||||
|
this.i18n = draw.getI18n()
|
||||||
|
this.container = draw.getContainer()
|
||||||
|
this.pageContainer = draw.getPageContainer()
|
||||||
|
|
||||||
|
const { tipContainer, tipContent } = this._drawZoneTip()
|
||||||
|
this.tipContainer = tipContainer
|
||||||
|
this.tipContent = tipContent
|
||||||
|
this.isDisableMouseMove = true
|
||||||
|
this.currentMoveZone = EditorZone.MAIN
|
||||||
|
|
||||||
|
this._watchMouseMoveZoneChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
private _watchMouseMoveZoneChange() {
|
||||||
|
this.pageContainer.addEventListener(
|
||||||
|
'mousemove',
|
||||||
|
throttle((evt: MouseEvent) => {
|
||||||
|
if (this.isDisableMouseMove) return
|
||||||
|
const pageNo = Number((<HTMLCanvasElement>evt.target).dataset.index)
|
||||||
|
if (Number.isNaN(pageNo)) {
|
||||||
|
this._updateZoneTip(false)
|
||||||
|
} else {
|
||||||
|
const positionInfo = this.position.getPositionByXY({
|
||||||
|
x: evt.offsetX,
|
||||||
|
y: evt.offsetY,
|
||||||
|
pageNo
|
||||||
|
})
|
||||||
|
this.currentMoveZone = positionInfo.zone
|
||||||
|
this._updateZoneTip(
|
||||||
|
positionInfo.zone === EditorZone.HEADER ||
|
||||||
|
positionInfo.zone === EditorZone.FOOTER,
|
||||||
|
evt.x,
|
||||||
|
evt.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
)
|
||||||
|
// mouseenter后mousemove有效,避免因节流导致的mouseleave后继续执行逻辑
|
||||||
|
this.pageContainer.addEventListener('mouseenter', () => {
|
||||||
|
this.isDisableMouseMove = false
|
||||||
|
})
|
||||||
|
this.pageContainer.addEventListener('mouseleave', () => {
|
||||||
|
this.isDisableMouseMove = true
|
||||||
|
this._updateZoneTip(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private _drawZoneTip() {
|
||||||
|
const tipContainer = document.createElement('div')
|
||||||
|
tipContainer.classList.add(`${EDITOR_PREFIX}-zone-tip`)
|
||||||
|
const tipContent = document.createElement('span')
|
||||||
|
tipContainer.append(tipContent)
|
||||||
|
this.container.append(tipContainer)
|
||||||
|
return {
|
||||||
|
tipContainer,
|
||||||
|
tipContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateZoneTip(visible: boolean, left?: number, top?: number) {
|
||||||
|
if (visible) {
|
||||||
|
this.tipContainer.classList.add('show')
|
||||||
|
this.tipContainer.style.left = `${left}px`
|
||||||
|
this.tipContainer.style.top = `${top}px`
|
||||||
|
this.tipContent.innerText = this.i18n.t(
|
||||||
|
`zone.${
|
||||||
|
this.currentMoveZone === EditorZone.HEADER ? 'headerTip' : 'footerTip'
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.tipContainer.classList.remove('show')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { IZoneOption } from '../../interface/Zone'
|
||||||
|
|
||||||
|
export const defaultZoneOption: Readonly<Required<IZoneOption>> = {
|
||||||
|
tipDisabled: true
|
||||||
|
}
|
||||||
+8
-1
@@ -73,6 +73,8 @@ import { LETTER_CLASS } from './dataset/constant/Common'
|
|||||||
import { INTERNAL_CONTEXT_MENU_KEY } from './dataset/constant/ContextMenu'
|
import { INTERNAL_CONTEXT_MENU_KEY } from './dataset/constant/ContextMenu'
|
||||||
import { IRange } from './interface/Range'
|
import { IRange } from './interface/Range'
|
||||||
import { deepClone, splitText } from './utils'
|
import { deepClone, splitText } from './utils'
|
||||||
|
import { IZoneOption } from './interface/Zone'
|
||||||
|
import { defaultZoneOption } from './dataset/constant/Zone'
|
||||||
|
|
||||||
export default class Editor {
|
export default class Editor {
|
||||||
public command: Command
|
public command: Command
|
||||||
@@ -132,6 +134,10 @@ export default class Editor {
|
|||||||
...defaultPageBreakOption,
|
...defaultPageBreakOption,
|
||||||
...options.pageBreak
|
...options.pageBreak
|
||||||
}
|
}
|
||||||
|
const zoneOptions: Required<IZoneOption> = {
|
||||||
|
...defaultZoneOption,
|
||||||
|
...options.zone
|
||||||
|
}
|
||||||
|
|
||||||
const editorOptions: DeepRequired<IEditorOption> = {
|
const editorOptions: DeepRequired<IEditorOption> = {
|
||||||
mode: EditorMode.EDIT,
|
mode: EditorMode.EDIT,
|
||||||
@@ -187,7 +193,8 @@ export default class Editor {
|
|||||||
title: titleOptions,
|
title: titleOptions,
|
||||||
placeholder: placeholderOptions,
|
placeholder: placeholderOptions,
|
||||||
group: groupOptions,
|
group: groupOptions,
|
||||||
pageBreak: pageBreakOptions
|
pageBreak: pageBreakOptions,
|
||||||
|
zone: zoneOptions
|
||||||
}
|
}
|
||||||
// 数据处理
|
// 数据处理
|
||||||
data = deepClone(data)
|
data = deepClone(data)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { IPageNumber } from './PageNumber'
|
|||||||
import { IPlaceholder } from './Placeholder'
|
import { IPlaceholder } from './Placeholder'
|
||||||
import { ITitleOption } from './Title'
|
import { ITitleOption } from './Title'
|
||||||
import { IWatermark } from './Watermark'
|
import { IWatermark } from './Watermark'
|
||||||
|
import { IZoneOption } from './Zone'
|
||||||
|
|
||||||
export interface IEditorData {
|
export interface IEditorData {
|
||||||
header?: IElement[]
|
header?: IElement[]
|
||||||
@@ -79,6 +80,7 @@ export interface IEditorOption {
|
|||||||
placeholder?: IPlaceholder
|
placeholder?: IPlaceholder
|
||||||
group?: IGroup
|
group?: IGroup
|
||||||
pageBreak?: IPageBreak
|
pageBreak?: IPageBreak
|
||||||
|
zone?: IZoneOption
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEditorResult {
|
export interface IEditorResult {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface ICurrentPosition {
|
|||||||
export interface IGetPositionByXYPayload {
|
export interface IGetPositionByXYPayload {
|
||||||
x: number
|
x: number
|
||||||
y: number
|
y: number
|
||||||
|
pageNo?: number
|
||||||
isTable?: boolean
|
isTable?: boolean
|
||||||
td?: ITd
|
td?: ITd
|
||||||
tablePosition?: IElementPosition
|
tablePosition?: IElementPosition
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface IZoneOption {
|
||||||
|
tipDisabled?: boolean
|
||||||
|
}
|
||||||
@@ -12,6 +12,25 @@ export function debounce(func: Function, delay: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function throttle(func: Function, delay: number) {
|
||||||
|
let lastExecTime = 0
|
||||||
|
let timer: number
|
||||||
|
return function (this: any, ...args: any[]) {
|
||||||
|
const currentTime = Date.now()
|
||||||
|
if (currentTime - lastExecTime >= delay) {
|
||||||
|
window.clearTimeout(timer)
|
||||||
|
func.apply(this, args)
|
||||||
|
lastExecTime = currentTime
|
||||||
|
} else {
|
||||||
|
window.clearTimeout(timer)
|
||||||
|
timer = window.setTimeout(() => {
|
||||||
|
func.apply(this, args)
|
||||||
|
lastExecTime = currentTime
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function deepClone<T>(obj: T): T {
|
export function deepClone<T>(obj: T): T {
|
||||||
if (!obj || typeof obj !== 'object') {
|
if (!obj || typeof obj !== 'object') {
|
||||||
return obj
|
return obj
|
||||||
|
|||||||
Reference in New Issue
Block a user