feat: add zone tooltip #367
This commit is contained in:
@@ -31,4 +31,31 @@
|
||||
|
||||
.ce-zone-indicator-border__right {
|
||||
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": {
|
||||
"displayName": "Page Break"
|
||||
},
|
||||
"zone": {
|
||||
"headerTip": "Double click to edit header",
|
||||
"footerTip": "Double click to edit footer"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,5 +76,9 @@
|
||||
},
|
||||
"pageBreak": {
|
||||
"displayName": "分页符"
|
||||
},
|
||||
"zone": {
|
||||
"headerTip": "双击编辑页眉",
|
||||
"footerTip": "双击编辑页脚"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ export class Position {
|
||||
positionList = this.getOriginalPositionList()
|
||||
}
|
||||
const zoneManager = this.draw.getZone()
|
||||
const curPageNo = this.draw.getPageNo()
|
||||
const curPageNo = payload.pageNo ?? this.draw.getPageNo()
|
||||
const isMainActive = zoneManager.isMainActive()
|
||||
const positionNo = isMainActive ? curPageNo : 0
|
||||
for (let j = 0; j < positionList.length; j++) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { IEditorOption } from '../../interface/Editor'
|
||||
import { nextTick } from '../../utils'
|
||||
import { Draw } from '../draw/Draw'
|
||||
import { I18n } from '../i18n/I18n'
|
||||
import { ZoneTip } from './ZoneTip'
|
||||
|
||||
export class Zone {
|
||||
private readonly INDICATOR_PADDING = 2
|
||||
@@ -24,6 +25,10 @@ export class Zone {
|
||||
this.container = draw.getContainer()
|
||||
this.currentZone = EditorZone.MAIN
|
||||
this.indicatorContainer = null
|
||||
// 区域提示
|
||||
if (!this.options.zone.tipDisabled) {
|
||||
new ZoneTip(draw)
|
||||
}
|
||||
}
|
||||
|
||||
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 { IRange } from './interface/Range'
|
||||
import { deepClone, splitText } from './utils'
|
||||
import { IZoneOption } from './interface/Zone'
|
||||
import { defaultZoneOption } from './dataset/constant/Zone'
|
||||
|
||||
export default class Editor {
|
||||
public command: Command
|
||||
@@ -132,6 +134,10 @@ export default class Editor {
|
||||
...defaultPageBreakOption,
|
||||
...options.pageBreak
|
||||
}
|
||||
const zoneOptions: Required<IZoneOption> = {
|
||||
...defaultZoneOption,
|
||||
...options.zone
|
||||
}
|
||||
|
||||
const editorOptions: DeepRequired<IEditorOption> = {
|
||||
mode: EditorMode.EDIT,
|
||||
@@ -187,7 +193,8 @@ export default class Editor {
|
||||
title: titleOptions,
|
||||
placeholder: placeholderOptions,
|
||||
group: groupOptions,
|
||||
pageBreak: pageBreakOptions
|
||||
pageBreak: pageBreakOptions,
|
||||
zone: zoneOptions
|
||||
}
|
||||
// 数据处理
|
||||
data = deepClone(data)
|
||||
|
||||
@@ -18,6 +18,7 @@ import { IPageNumber } from './PageNumber'
|
||||
import { IPlaceholder } from './Placeholder'
|
||||
import { ITitleOption } from './Title'
|
||||
import { IWatermark } from './Watermark'
|
||||
import { IZoneOption } from './Zone'
|
||||
|
||||
export interface IEditorData {
|
||||
header?: IElement[]
|
||||
@@ -79,6 +80,7 @@ export interface IEditorOption {
|
||||
placeholder?: IPlaceholder
|
||||
group?: IGroup
|
||||
pageBreak?: IPageBreak
|
||||
zone?: IZoneOption
|
||||
}
|
||||
|
||||
export interface IEditorResult {
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface ICurrentPosition {
|
||||
export interface IGetPositionByXYPayload {
|
||||
x: number
|
||||
y: number
|
||||
pageNo?: number
|
||||
isTable?: boolean
|
||||
td?: ITd
|
||||
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 {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj
|
||||
|
||||
Reference in New Issue
Block a user