Merge pull request #38 from Hufe921/feature/checkbox

Feature/checkbox
pr675
Hufe 4 years ago committed by GitHub
commit 9e47a710d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,5 @@
{
"video": false,
"viewportWidth": 1080,
"viewportWidth": 1366,
"viewportHeight": 720
}

@ -0,0 +1,37 @@
import Editor, { ElementType } from '../../../src/editor'
describe('菜单-复选框', () => {
beforeEach(() => {
cy.visit('http://localhost:3000/canvas-editor/')
cy.get('canvas').first().as('canvas').should('have.length', 1)
})
const type: ElementType = <ElementType>'checkbox'
it('代码块', () => {
cy.getEditor().then((editor: Editor) => {
editor.listener.saved = function (payload) {
const data = payload.data[0]
expect(data.checkbox?.value).to.eq(true)
}
editor.command.executeSelectAll()
editor.command.executeBackspace()
editor.command.executeInsertElementList([{
type,
value: '',
checkbox: {
value: true
}
}])
cy.get('@canvas').type('{ctrl}s')
})
})
})

@ -169,6 +169,9 @@
</ul>
</div>
</div>
<div class="menu-item__checkbox">
<i></i>
</div>
</div>
<div class="menu-divider"></div>
<div class="menu-item">

@ -0,0 +1 @@
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 16 16" xml:space="preserve"><style>.st0{fill:#3d4757}</style><g id="_x30_1-文字_x2F_01开始_x2F_任务列表-16px"><path id="合并形状" class="st0" d="M10.1 2H2v11h11V8.7l1-1V13c0 .6-.4 1-1 1H2c-.6 0-1-.4-1-1V2c0-.6.4-1 1-1h9.1l-1 1z"/><path id="路径" class="st0" d="M7.7 8.5l5.7-5.8.9.8-6.1 5.9-.5.5-3.9-3.4.8-.7z"/></g></svg>

After

Width:  |  Height:  |  Size: 428 B

@ -37,6 +37,8 @@ import { Watermark } from './frame/Watermark'
import { EditorMode } from '../../dataset/enum/Editor'
import { Control } from './control/Control'
import { zipElementList } from '../../utils/element'
import { CheckboxParticle } from './particle/CheckboxParticle'
import { DeepRequired } from '../../interface/Common'
export class Draw {
@ -46,7 +48,7 @@ export class Draw {
private ctxList: CanvasRenderingContext2D[]
private pageNo: number
private mode: EditorMode
private options: Required<IEditorOption>
private options: DeepRequired<IEditorOption>
private position: Position
private elementList: IElement[]
private listener: Listener
@ -73,6 +75,7 @@ export class Draw {
private pageBreakParticle: PageBreakParticle
private superscriptParticle: SuperscriptParticle
private subscriptParticle: SubscriptParticle
private checkboxParticle: CheckboxParticle
private control: Control
private rowList: IRow[]
@ -83,7 +86,7 @@ export class Draw {
constructor(
container: HTMLDivElement,
options: Required<IEditorOption>,
options: DeepRequired<IEditorOption>,
elementList: IElement[],
listener: Listener
) {
@ -120,6 +123,7 @@ export class Draw {
this.pageBreakParticle = new PageBreakParticle(this)
this.superscriptParticle = new SuperscriptParticle()
this.subscriptParticle = new SubscriptParticle()
this.checkboxParticle = new CheckboxParticle(this)
this.control = new Control(this)
new ScrollObserver(this)
@ -249,7 +253,7 @@ export class Draw {
return this.ctxList[this.pageNo]
}
public getOptions(): Required<IEditorOption> {
public getOptions(): DeepRequired<IEditorOption> {
return this.options
}
@ -563,6 +567,12 @@ export class Draw {
element.width = innerWidth
metrics.width = innerWidth
metrics.height = this.options.defaultSize
} else if (element.type === ElementType.CHECKBOX) {
const { width, height, gap } = this.options.checkbox
const elementWidth = (width + gap * 2) * scale
element.width = elementWidth
metrics.width = elementWidth
metrics.height = height * scale
} else {
// 设置上下标真实字体尺寸
const size = element.size || this.options.defaultSize
@ -697,6 +707,9 @@ export class Draw {
if (this.mode !== EditorMode.CLEAN) {
this.pageBreakParticle.render(ctx, element, x, y)
}
} else if (element.type === ElementType.CHECKBOX) {
this.textParticle.complete()
this.checkboxParticle.render(ctx, element, x, y + offsetY)
} else {
this.textParticle.record(ctx, element, x, y + offsetY)
}

@ -0,0 +1,54 @@
import { DeepRequired } from '../../../interface/Common'
import { IEditorOption } from '../../../interface/Editor'
import { IRowElement } from '../../../interface/Row'
import { Draw } from '../Draw'
export class CheckboxParticle {
private options: DeepRequired<IEditorOption>
constructor(draw: Draw) {
this.options = draw.getOptions()
}
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) {
const { checkbox: { gap, lineWidth, fillStyle, fontStyle }, scale } = this.options
const { metrics, checkbox } = element
// left top 四舍五入避免1像素问题
const left = Math.round(x + gap)
const top = Math.round(y - metrics.height + lineWidth)
const width = metrics.width - gap * 2 * scale
const height = metrics.height
ctx.save()
ctx.beginPath()
ctx.translate(0.5, 0.5)
// 绘制勾选状态
if (checkbox?.value) {
// 边框
ctx.lineWidth = lineWidth
ctx.strokeStyle = fillStyle
ctx.rect(left, top, width, height)
ctx.stroke()
// 背景色
ctx.beginPath()
ctx.fillStyle = fillStyle
ctx.fillRect(left, top, width, height)
// 勾选对号
ctx.beginPath()
ctx.strokeStyle = fontStyle
ctx.lineWidth = lineWidth * 2
ctx.moveTo(left + 2 * scale, top + 7 * scale)
ctx.lineTo(left + 7 * scale, top + 11 * scale)
ctx.moveTo(left + 6.5 * scale, top + 11 * scale)
ctx.lineTo(left + 12 * scale, top + 3 * scale)
ctx.stroke()
} else {
ctx.lineWidth = lineWidth
ctx.rect(left, top, width, height)
ctx.stroke()
}
ctx.closePath()
ctx.restore()
}
}

@ -188,6 +188,7 @@ export class CanvasEvent {
const {
index,
isDirectHit,
isCheckbox,
isControl,
isImage,
isTable,
@ -214,27 +215,34 @@ export class CanvasEvent {
...positionResult,
index: isTable ? tdValueIndex! : index
}
const elementList = this.draw.getElementList()
const positionList = this.position.getPositionList()
const curIndex = isTable ? tdValueIndex! : index
const curElement = elementList[curIndex]
// 绘制
const isDirectHitImage = isDirectHit && isImage
const isDirectHitImage = !!(isDirectHit && isImage)
const isDirectHitCheckbox = !!(isDirectHit && isCheckbox)
if (~index) {
let curIndex = index
if (isTable) {
this.range.setRange(tdValueIndex!, tdValueIndex!)
curIndex = tdValueIndex!
this.range.setRange(curIndex, curIndex)
// 复选框
const isSetCheckbox = isDirectHitCheckbox && !isReadonly
if (isSetCheckbox) {
const { checkbox } = curElement
if (checkbox) {
checkbox.value = !checkbox.value
} else {
this.range.setRange(index, index)
curElement.checkbox = {
value: true
}
}
}
this.draw.render({
curIndex,
isSubmitHistory: false,
isSetCursor: !isDirectHitImage,
isSubmitHistory: isSetCheckbox,
isSetCursor: !isDirectHitImage && !isDirectHitCheckbox,
isComputeRowList: false
})
}
const elementList = this.draw.getElementList()
const positionList = this.position.getPositionList()
const curIndex = isTable ? tdValueIndex! : index
const curElement = elementList[curIndex]
// 图片尺寸拖拽组件
this.imageParticle.clearResizer()
if (isDirectHitImage && !isReadonly) {

@ -94,9 +94,11 @@ export class Position {
})
if (~tablePosition.index) {
const { index: tdValueIndex } = tablePosition
const tdValueElement = td.value[tdValueIndex]
return {
index,
isControl: td.value[tdValueIndex].type === ElementType.CONTROL,
isCheckbox: tdValueElement.type === ElementType.CHECKBOX,
isControl: tdValueElement.type === ElementType.CONTROL,
isImage: tablePosition.isImage,
isDirectHit: tablePosition.isDirectHit,
isTable: true,
@ -119,6 +121,13 @@ export class Position {
isImage: true
}
}
if (element.type === ElementType.CHECKBOX) {
return {
index: curPositionIndex,
isDirectHit: true,
isCheckbox: true
}
}
// 判断是否在文字中间前后
if (elementList[index].value !== ZERO) {
const valueWidth = rightTop[0] - leftTop[0]

@ -0,0 +1,10 @@
import { ICheckboxOption } from '../../interface/Checkbox'
export const defaultCheckboxOption: Readonly<Required<ICheckboxOption>> = {
width: 14,
height: 14,
gap: 5,
lineWidth: 1,
fillStyle: '#5175f4',
fontStyle: '#ffffff'
}

@ -46,7 +46,8 @@ export const EDITOR_ELEMENT_ZIP_ATTR: Array<keyof IElement> = [
'url',
'colgroup',
'valueList',
'control'
'control',
'checkbox'
]
export const TEXTLIKE_ELEMENT_TYPE: ElementType[] = [

@ -7,5 +7,6 @@ export enum ElementType {
SUBSCRIPT = 'subscript',
SEPARATOR = 'separator',
PAGE_BREAK = 'pageBreak',
CONTROL = 'control'
CONTROL = 'control',
CHECKBOX = 'checkbox'
}

@ -22,6 +22,9 @@ import { defaultWatermarkOption } from './dataset/constant/Watermark'
import { ControlType } from './dataset/enum/Control'
import { defaultControlOption } from './dataset/constant/Control'
import { IControlOption } from './interface/Control'
import { ICheckboxOption } from './interface/Checkbox'
import { defaultCheckboxOption } from './dataset/constant/Checkbox'
import { DeepRequired } from './interface/Common'
export default class Editor {
@ -42,7 +45,12 @@ export default class Editor {
...defaultControlOption,
...options.control
}
const editorOptions: Required<IEditorOption> = {
const checkboxOptions: Required<ICheckboxOption> = {
...defaultCheckboxOption,
...options.checkbox
}
const editorOptions: DeepRequired<IEditorOption> = {
defaultMode: EditorMode.EDIT,
defaultType: 'TEXT',
defaultFont: 'Yahei',
@ -76,7 +84,8 @@ export default class Editor {
...options,
header: headerOptions,
watermark: waterMarkOptions,
control: controlOptions
control: controlOptions,
checkbox: checkboxOptions
}
formatElementList(elementList, {
editorOptions

@ -0,0 +1,13 @@
export interface ICheckbox {
disabled?: boolean;
value: boolean | null;
}
export interface ICheckboxOption {
width?: number;
height?: number;
gap?: number;
lineWidth?: number;
fillStyle?: string;
fontStyle?: string;
}

@ -1,5 +1,6 @@
import { IElement } from '..'
import { EditorMode } from '../dataset/enum/Editor'
import { ICheckboxOption } from './Checkbox'
import { IControlOption } from './Control'
import { IHeader } from './Header'
import { IWatermark } from './Watermark'
@ -38,6 +39,7 @@ export interface IEditorOption {
header?: IHeader;
watermark?: IWatermark;
control?: IControlOption;
checkbox?: ICheckboxOption;
}
export interface IEditorResult {

@ -1,6 +1,7 @@
import { ControlComponent } from '../dataset/enum/Control'
import { ElementType } from '../dataset/enum/Element'
import { RowFlex } from '../dataset/enum/Row'
import { ICheckbox } from './Checkbox'
import { IControl } from './Control'
import { IColgroup } from './table/Colgroup'
import { ITr } from './table/Tr'
@ -59,6 +60,10 @@ export interface IControlElement {
controlComponent?: ControlComponent;
}
export interface ICheckboxElement {
checkbox?: ICheckbox;
}
export type IElement = IElementBasic
& IElementStyle
& ITable
@ -66,6 +71,7 @@ export type IElement = IElementBasic
& ISuperscriptSubscript
& ISeparator
& IControlElement
& ICheckboxElement
export interface IElementMetrics {
width: number;

@ -4,6 +4,7 @@ import { ITd } from './table/Td'
export interface ICurrentPosition {
index: number;
isCheckbox?: boolean;
isControl?: boolean;
isImage?: boolean;
isTable?: boolean;

@ -492,6 +492,15 @@ window.onload = function () {
}
}
const checkboxDom = document.querySelector<HTMLDivElement>('.menu-item__checkbox')!
checkboxDom.onclick = function () {
console.log('checkbox')
instance.command.executeInsertElementList([{
type: ElementType.CHECKBOX,
value: ''
}])
}
// 5. | 搜索&替换 | 打印 |
const searchCollapseDom = document.querySelector<HTMLDivElement>('.menu-item__search__collapse')!
const searchInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__search input')!
@ -679,7 +688,8 @@ window.onload = function () {
'separator',
'codeblock',
'page-break',
'control'
'control',
'checkbox'
]
// 菜单操作权限
disableMenusInControlContext.forEach(menu => {

@ -246,6 +246,26 @@ elementList.push({
}]
})
// 模拟checkbox
elementList.push(...<IElement[]>[{
value: '是否同意以上内容:'
}, {
type: ElementType.CHECKBOX,
checkbox: {
value: true
},
value: ''
}, {
value: '同意'
}, {
type: ElementType.CHECKBOX,
value: ''
}, {
value: '否定'
}, {
value: '\n'
}])
// 模拟结尾文本
elementList.push(...[{
value: 'E',

@ -439,6 +439,10 @@ ul {
background-image: url('./assets/images/control.svg');
}
.menu-item__checkbox i {
background-image: url('./assets/images/checkbox.svg');
}
.menu-item .menu-item__control .options {
width: 55px;
}

Loading…
Cancel
Save