Commit 8a34bb0b by pangchong

Merge branch 'master' of 122.112.146.86:qlintonger/standalone-anyremote

parents 7511e0c8 078d33fb
import {ref, Ref} from "vue";
export enum AndroidBridgeInfoType {
Open,
Close,
Mark
}
/**
* 存储javascript:window.jsBridge({type: AndroidBridgeInfoType.Open, data: {}})传递过来的数据
*/
export const currentBridgeInfo = ref({}) as Ref
\ No newline at end of file
<template>
<i :class="displayedClass" @click="triggerDrawArrow"></i>
</template>
<script setup lang="ts">
import {CoreOperation} from "../objs/coreOperation";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {drawArrowsFunc} from "../funcs/drawArrows";
import {nextTick, computed} from "vue";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType} from "../types/OperationRecordTyping";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
let triggerDrawArrow = () => {
props.co.stopDraw()
props.co.triggerDraw('arrows')
}
nextTick(() => {
props.co.register(ProcessCycle.Start, 'arrows', drawArrowsFunc, {ctx: props.ctx})
props.co.register(ProcessCycle.Stamp, 'arrows', drawArrowsFunc, {ctx: props.ctx})
props.or.registerType('arrows', RecordType.Vector, 'A')
})
const displayedClass = computed(function () {
return props.co.getInternalState().currentDrawOption === 'arrows'
? 'iconfont icon-arrows activated': 'iconfont icon-arrows'
})
</script>
<template>
<i class="iconfont icon-delete" @click="triggerDrawClear"></i>
</template>
<script setup lang="ts">
import {CoreOperation} from "../objs/coreOperation";
import {nextTick} from "vue";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {DefaultNoopFunc} from "../types/HandlerFunc";
import {drawCanvas, resetCanvas} from "../objs/renderBasic";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType} from "../types/OperationRecordTyping";
import {clearAll} from "../state/wholeTextInput";
import {restoreColorConfig, restoreLineWidthConfig} from "../state/operationConfigHisotry";
import {ConfigDrawAttachment} from "../types/AttachmentSpecifics";
import {OPERATION_CLEAR} from "../config/canvasConfig";
import {enableRecover} from "../state/canvasRecover";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
const triggerDrawClear = () => {
drawClear()
props.co.triggerDraw('clear')
props.or.removeRecord()
}
const clearAttachment: ConfigDrawAttachment = {
configType: OPERATION_CLEAR,
ctx: props.ctx
}
const drawClear: DefaultNoopFunc = () => {
clearAll()
restoreColorConfig()
restoreLineWidthConfig()
resetCanvas()
drawCanvas()
enableRecover.value = false
}
defineExpose({
'clear': triggerDrawClear
})
nextTick(() => {
props.co.register(ProcessCycle.Immediate, 'clear', drawClear, clearAttachment)
props.or.registerType('clear', RecordType.Immediate, 'C')
})
</script>
\ No newline at end of file
<template>
<n-popover trigger="click" v-model:show="showColorPicker">
<template #trigger>
<i class="iconfont icon-color" :style="{color: styledColor}"></i>
</template>
<div class="color-block-container">
<div v-for="(item, index) in colorBlock" :key="index" class="color-block"
:style="{backgroundColor: item}" @click="confirmColorPick(index)"></div>
</div>
</n-popover>
</template>
<script setup lang="ts">
import {NPopover} from "naive-ui";
import {nextTick} from "vue";
import {CoreOperation} from "../objs/coreOperation";
import {RecordRelatedFunc} from "../types/HandlerFunc";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {ConfigDrawAttachment} from "../types/AttachmentSpecifics";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType} from "../types/OperationRecordTyping";
import {CONFIG_COLOR} from "../config/canvasConfig";
import {isDrawFromAction} from "../state/drawOperationType";
import {colorBlock, initialColorIndex, showColorPicker, styledColor} from "../state/operationConfig";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
let colorConfigAttachment: ConfigDrawAttachment = {
ctx: props.ctx,
configType: CONFIG_COLOR
}
let triggerDrawColorChanged: RecordRelatedFunc = (shouldRecord: boolean = true) => {
props.ctx.fillStyle = styledColor.value
props.ctx.strokeStyle = styledColor.value
if (shouldRecord) {
props.co.triggerDraw('colorConfig')
}
}
const confirmColorPick = (v: number) => {
initialColorIndex.value = v
showColorPicker.value = false
triggerDrawColorChanged(isDrawFromAction())
}
nextTick(() => {
props.co.register(ProcessCycle.Immediate, 'colorConfig', triggerDrawColorChanged, colorConfigAttachment)
props.or.registerType('colorConfig', RecordType.Index, 'Y')
})
</script>
<style scoped>
@import url("../resources/styles/color-block.css");
@import url("../resources/styles/opitem.css");
@import url("../resources/styles/media-adapt.css");
</style>
\ No newline at end of file
<template>
<i :class="displayedClass" @click="triggerDrawEllipse"></i>
</template>
<script setup lang="ts">
import {CoreOperation} from "../objs/coreOperation";
import {computed, nextTick} from "vue";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {drawEllipse} from "../funcs/drawEclipse";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType} from "../types/OperationRecordTyping";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
const triggerDrawEllipse = () => {
props.co.stopDraw()
props.co.triggerDraw('ellipse')
}
nextTick(() => {
props.co.register(ProcessCycle.Start, 'ellipse', drawEllipse, {ctx: props.ctx})
props.co.register(ProcessCycle.Stamp, 'ellipse', drawEllipse, {ctx: props.ctx})
props.or.registerType('ellipse', RecordType.Vector, 'E')
})
const displayedClass = computed(function () {
return props.co.getInternalState().currentDrawOption === 'ellipse'
? 'iconfont icon-circle activated': 'iconfont icon-circle'
})
</script>
\ No newline at end of file
<template>
<i :class="displayedClass" @click="triggerDrawErase"></i>
</template>
<script setup lang="ts">
import {CoreOperation} from "../objs/coreOperation";
import {computed, nextTick} from "vue";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {drawErase} from "../funcs/drawErase";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType} from "../types/OperationRecordTyping";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
// 存储持续绘制橡皮擦的attachment
let eraseAttachment = {
ctx: undefined as undefined | CanvasRenderingContext2D,
o_ctx: undefined as undefined | CanvasRenderingContext2D,
points: []
}
const triggerDrawErase = () => {
props.co.stopDraw()
props.co.triggerDraw('erase')
}
nextTick(() =>{
eraseAttachment.ctx = props.ctx
eraseAttachment.o_ctx = props.o_ctx
props.co.register(ProcessCycle.Start, 'erase', drawErase, eraseAttachment)
props.co.register(ProcessCycle.Stamp, 'erase', drawErase, eraseAttachment)
props.or.registerType('erase', RecordType.Continuous, 'X')
})
const displayedClass = computed(function () {
return props.co.getInternalState().currentDrawOption === 'erase'
? 'iconfont icon-eraser activated': 'iconfont icon-eraser'
})
</script>
<style scoped>
@import url("../resources/styles/media-adapt.css");
</style>
\ No newline at end of file
<template>
<n-popover trigger="click" v-model:show="showExpressionPicker" display-directive="if">
<template #trigger>
<i :class="displayedClass"></i>
</template>
<div class="color-block-container">
<img
v-for="(item, index) in pics"
@click="confirmExpressionPick(index)"
:key="index"
class="color-block"
alt="expression addon"
:src="item"
/>
</div>
</n-popover>
<div class="for-view-only" id="draw-box-image">
<img
v-for="(item, index) in pics"
:key="index"
class="color-block"
alt="expression addon"
:src="item"
/>
</div>
</template>
<script setup lang="ts">
// @ts-nocheck
import {NPopover} from "naive-ui";
import {computed, nextTick, ref} from "vue";
import {CoreOperation} from "../objs/coreOperation";
import {drawExpression} from "../funcs/drawExpression";
import {PayloadDrawAttachment} from "../types/AttachmentSpecifics";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType} from "../types/OperationRecordTyping";
import E1 from "../resources/expressions/1.png"
import E2 from "../resources/expressions/2.png"
import E3 from "../resources/expressions/3.png"
import E4 from "../resources/expressions/4.png"
import E5 from "../resources/expressions/5.png"
import E6 from "../resources/expressions/6.png"
import E7 from "../resources/expressions/7.png"
import E8 from "../resources/expressions/8.png"
import E9 from "../resources/expressions/9.png"
import E10 from "../resources/expressions/10.png"
import E11 from "../resources/expressions/11.png"
import E12 from "../resources/expressions/12.png"
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
// 虚假数据,只是为了演示用
const pics = ref([
E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12
])
// 挑选表情的处理
const selectedIndex = ref(-1)
const showExpressionPicker = ref(false)
const confirmExpressionPick = (v: any) => {
showExpressionPicker.value = false
selectedIndex.value = v
triggerDrawExpression()
}
// 挑选表情的attachment
const expressionAttachment:PayloadDrawAttachment = {
ctx: props.ctx,
payload: null
}
const triggerDrawExpression = () => {
expressionAttachment.payload = selectedIndex.value
props.co.triggerDraw('expression')
}
nextTick(() => {
props.co.register(ProcessCycle.Start, 'expression', drawExpression, expressionAttachment)
props.co.register(ProcessCycle.Stamp, 'expression', drawExpression, expressionAttachment)
props.or.registerType('expression', RecordType.VectorPayload, 'H')
})
const displayedClass = computed(function () {
return props.co.getInternalState().currentDrawOption === 'expression'
? 'iconfont icon-expression activated': 'iconfont icon-expression'
})
</script>
<style scoped>
@import url("../resources/styles/color-block.css");
@import url("../resources/styles/media-adapt.css");
.for-view-only {
display: none !important;
}
</style>
\ No newline at end of file
<template>
<n-popselect v-model:value="picFont" trigger="click" :options="picFontOptions" size="small"
:on-update:value="confirmLineWidthPick">
<i class="iconfont">{{ picFont }}</i>
</n-popselect>
</template>
<script setup lang="ts">
import {nextTick} from "vue";
import {NPopselect} from "naive-ui";
import {CoreOperation} from "../objs/coreOperation";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {ConfigDrawAttachment} from "../types/AttachmentSpecifics";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType} from "../types/OperationRecordTyping";
import {RecordRelatedFunc} from "../types/HandlerFunc";
import {CONFIG_LINE_WIDTH} from "../config/canvasConfig";
import {isDrawFromAction} from "../state/drawOperationType";
import {picFont, picFontOptions} from "../state/operationConfig";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
let lineWidthAttachment: ConfigDrawAttachment = {
ctx: props.ctx,
configType: CONFIG_LINE_WIDTH
}
const changeLineWidth: RecordRelatedFunc = (shouldRecord: boolean = true) => {
props.ctx.lineWidth = picFont.value
if (shouldRecord) {
props.co.triggerDraw('lineWidth')
}
}
const confirmLineWidthPick = (nv: any) => {
picFont.value = nv
changeLineWidth(isDrawFromAction())
}
nextTick(() => {
props.co.register(ProcessCycle.Immediate, 'lineWidth', changeLineWidth, lineWidthAttachment)
props.or.registerType('lineWidth', RecordType.Index, 'L')
})
</script>
<style scoped>
@import url("../resources/styles/opitem.css");
@import url("../resources/styles/media-adapt.css");
.iconfont {
margin-right: 0 !important;
}
</style>
\ No newline at end of file
<template></template>
<script setup>
import {useMessage} from 'naive-ui'
import {inject, watch} from "vue";
const message = useMessage()
const loadingInfo = inject('loadingInfo')
let currentNotifier = null
watch(loadingInfo, function () {
if (loadingInfo?.value?.display) {
currentNotifier = message.loading(loadingInfo?.value?.info, {
duration: 0
})
} else {
if (currentNotifier !== null) {
currentNotifier.destroy()
}
}
}, {deep: true, immediate: true})
</script>
\ No newline at end of file
<template>
<i class="iconfont icon-left_rotation" :class="{'history-disabled': !canRevoke}" @click="triggerRollback"></i>
<i class="iconfont icon-right_rotation" :class="{'history-disabled': !enableRecover}" @click="triggerRecover"></i>
</template>
<script setup lang="ts">
import {CoreOperation} from "../objs/coreOperation";
import {computed, nextTick, watch} from "vue";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {DefaultNoopFunc} from "../types/HandlerFunc";
import {unifyArray} from "../utils/object";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType, VectorRecord} from "../types/OperationRecordTyping";
import {drawRecord} from "../funcs/drawRecord";
import {setDrawFromAction, setDrawFromRecord} from "../state/drawOperationType";
import {enableRecover, rollbackStep} from "../state/canvasRecover";
import {clearAll} from "../state/wholeTextInput";
import {CoreInternalState} from "../types/CoreOperationTyping";
import {CONFIG_COLOR, CONFIG_LINE_WIDTH, OPERATION_CLEAR} from "../config/canvasConfig";
import {initialColorIndex, picFont} from "../state/operationConfig";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
start: {
type: Boolean,
default: true
},
or: {
type: OperationRecord,
required: true
}
})
const canRevoke = computed(() => {
const rawRecord = props.or.getInternalState().rawRecord
return rawRecord.length > 0 && rollbackStep.value < rawRecord.length
})
const storeRecord: DefaultNoopFunc = () => {
const internalState: CoreInternalState = props.co.getInternalState()
// 立即触发类
if (internalState.isHandleImmediate) {
// 清除
if (Reflect.get(internalState.currentDrawAttachment, 'configType') === OPERATION_CLEAR) {
props.or.storeRecord(internalState.currentImmediateOption, null)
}
// 设置颜色
else if (Reflect.get(internalState.currentDrawAttachment, 'configType') === CONFIG_COLOR) {
props.or.storeRecord(internalState.currentImmediateOption, initialColorIndex.value)
}
// 设置宽度
else if (Reflect.get(internalState.currentDrawAttachment, 'configType') === CONFIG_LINE_WIDTH) {
props.or.storeRecord(internalState.currentImmediateOption, picFont.value)
}
// 是否涉及到操作记录回溯
else if (Reflect.has(internalState.currentDrawAttachment, 'recordOpType')) {
props.or.storeRecord(internalState.currentImmediateOption, rollbackStep.value)
}
return
}
// 如果是连续性绘制,减少重复点
if (Reflect.has(internalState.currentDrawAttachment, 'points')) {
props.or.storeRecord(internalState.currentDrawOption, unifyArray(internalState.currentDrawAttachment.points))
}
// 如果存在清空,则直接清零,减少数据传输长度
else if (Reflect.has(internalState.currentDrawAttachment, 'clear')) {
props.or.storeRecord(internalState.currentImmediateOption, null)
}
// 其余线性矢量绘图,直接传入坐标即可
else {
const vectorData = {
sx: internalState.sx,
sy: internalState.sy,
ex: internalState.ex,
ey: internalState.ey,
payload: null
}
// 传输贴画类,是需要同时输入x和y坐标以及图像选择器
if (Reflect.has(internalState.currentDrawAttachment, 'payload')) {
vectorData.payload = internalState.currentDrawAttachment.payload
props.or.storeRecord(internalState.currentDrawOption, vectorData as VectorRecord)
} else {
props.or.storeRecord(internalState.currentDrawOption, vectorData as VectorRecord)
}
}
}
// 如果所有截图程序退出,则清空记录
watch(() => props.start, () => {
if (!props.start) {
props.or.removeRecord()
setDrawFromAction()
enableRecover.value = false
clearAll()
}
})
// 触发撤销操作,根据操作历史记录直接调用,注意需要在预定义的placeholder画板和canvas画板上同时进行操作
// 在这里进行绘图操作,是为了不触发storeRecord方法,并且可以主动侦测到其他绘制方法的调用
const triggerRollback = () => {
if (!canRevoke.value) return
enableRecover.value = true
setDrawFromRecord()
if (rollbackStep.value < props.or.getInternalState().rawRecord.length) {
rollbackStep.value++
const {rawRecord, operationRecordTypeSet} = props.or.getInternalState()
drawRecord(
rawRecord.slice(0, rawRecord.length - rollbackStep.value),
props.co,
operationRecordTypeSet,
props.ctx
)
}
rollBackFunc()
}
// 触发重绘操作,同上
const triggerRecover = () => {
if (!enableRecover.value) return
setDrawFromRecord()
rollbackStep.value--
const {rawRecord, operationRecordTypeSet} = props.or.getInternalState()
drawRecord(
rawRecord.slice(0, rawRecord.length - rollbackStep.value),
props.co,
operationRecordTypeSet,
props.ctx
)
if (rollbackStep.value == 0) {
enableRecover.value = false
}
recoverFunc()
}
const rollBackFunc = () => {
props.co.triggerDraw('rollback')
}
const recoverFunc = () => {
props.co.triggerDraw('recover')
}
nextTick(() => {
props.co.register(ProcessCycle.Immediate, 'record', storeRecord, {}, true)
props.co.register(ProcessCycle.Stop, 'record', storeRecord, {}, true)
// 涉及到撤销或者回溯的记录发送,视为空,制作记录的截取
props.or.registerType('rollback', RecordType.Revoke, 'B')
props.or.registerType('recover', RecordType.Recover, 'F')
props.co.register(ProcessCycle.Immediate, 'rollback', rollBackFunc, {recordOpType: RecordType.Revoke})
props.co.register(ProcessCycle.Immediate, 'recover', recoverFunc, {recordOpType: RecordType.Recover})
})
</script>
<style scoped>
@import url("../resources/styles/opitem.css");
@import url("../resources/styles/media-adapt.css");
.history-disabled {
cursor: not-allowed !important;
color: darkgray !important;
}
</style>
\ No newline at end of file
<template>
<i :class="displayedClass" @click="triggerDrawRect"></i>
</template>
<script setup lang="ts">
import {CoreOperation} from "../objs/coreOperation";
import {computed, nextTick} from "vue";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {drawRect} from "../funcs/drawRect";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType} from "../types/OperationRecordTyping";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
const triggerDrawRect = () => {
props.co.stopDraw()
props.co.triggerDraw('rect')
}
nextTick(() => {
props.co.register(ProcessCycle.Start, 'rect', drawRect, {ctx: props.ctx})
props.co.register(ProcessCycle.Stamp, 'rect', drawRect, {ctx: props.ctx})
props.or.registerType('rect', RecordType.Vector, 'R')
})
const displayedClass = computed(function () {
return props.co.getInternalState().currentDrawOption === 'rect'
? 'iconfont icon-rectangle activated': 'iconfont icon-rectangle'
})
</script>
\ No newline at end of file
<template>
<i :class="displayedClass" @click="triggerDrawStroke"></i>
</template>
<script setup lang="ts">
import {CoreOperation} from "../objs/coreOperation";
import {computed, nextTick} from "vue";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {drawStroke} from "../funcs/drawStroke";
import {OperationRecord} from "../objs/operationRecord";
import {RecordType} from "../types/OperationRecordTyping";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
let triggerDrawStroke = () => {
props.co.stopDraw()
props.co.triggerDraw('stroke')
}
// 存储持续绘制线条的attachment
let strokeAttachment = {
ctx: undefined as undefined | CanvasRenderingContext2D,
points: []
}
let clearStroke = () => {
strokeAttachment.points.length = 0
}
nextTick(()=>{
strokeAttachment.ctx = props.ctx
props.co.register(ProcessCycle.Start, 'stroke', drawStroke, strokeAttachment)
props.co.register(ProcessCycle.Stamp, 'stroke', drawStroke, strokeAttachment)
props.co.register(ProcessCycle.Stop, 'stroke', clearStroke, strokeAttachment)
props.or.registerType('stroke', RecordType.Continuous, 'S')
})
const displayedClass = computed(function () {
return props.co.getInternalState().currentDrawOption === 'stroke'
? 'iconfont icon-brush activated': 'iconfont icon-brush'
})
</script>
<template>
<i class="iconfont icon-text" @click="triggerDrawTextWidget"></i>
</template>
<script setup lang="ts">
import {CoreOperation} from "../objs/coreOperation";
import {OperationRecord} from "../objs/operationRecord";
import {nextTick} from "vue";
import {ComplexDrawHandleFunc} from "../types/HandlerFunc";
import {ProcessCycle} from "../types/HandleProcessCycle";
import {createUUID} from "../utils/math";
import {
addNewWidget,
pruneWidget,
setCurrentActionWidgetId,
wholeTextInputWidgets as inputAnchorDataSet,
} from "../state/wholeTextInput";
import {RecordType} from "../types/OperationRecordTyping";
import {textWidgetAttachment} from "../state/textWidgetAttachment";
const props = defineProps({
co: {
type: CoreOperation,
required: true
},
ctx: {
type: CanvasRenderingContext2D,
required: true
},
o_ctx: {
type: CanvasRenderingContext2D,
required: true
},
or: {
type: OperationRecord,
required: true
}
})
// 判断是否点击坐标在任何一个文本输入框之内
const isInAnyWidget = (sx: number, sy: number): boolean => {
for (const item of inputAnchorDataSet.value) {
if (sx >= item.sx && sx <= item.sx + item.width && sy >= item.sy && sy <= item.sy + item.height)
return true
}
return false
}
const triggerDrawTextWidget = () => {
props.co.stopDraw()
props.co.triggerDraw('text')
}
// 由于创建[TIWS]组件是独立在几何画板之外,只需要获取开始点坐标的,因此在此独立撰写添加逻辑
const drawTextWidget: ComplexDrawHandleFunc = (
sx: number | undefined,
sy: number | undefined,
ex: number | undefined,
ey: number | undefined,
attachment: any) => {
if (!isInAnyWidget(sx as number, sy as number)) {
pruneWidget()
const id = createUUID()
addNewWidget({
id,
sx: sx as number,
sy: sy as number,
width: 200,
height: 21,
text: attachment.payload ? attachment.payload : '',
display: true
})
setCurrentActionWidgetId(id)
}
}
const pruneTextWidget = () => {
if (props.co.getInternalState().currentDrawOption !== 'text') {
pruneWidget()
}
}
nextTick(() => {
props.co.register(ProcessCycle.Start, 'text', drawTextWidget, textWidgetAttachment)
props.co.register(ProcessCycle.BeforeStart, 'text', pruneTextWidget, {})
props.or.registerType('text', RecordType.VectorPayload, 'T')
})
</script>
\ No newline at end of file
export const color = 'red'
export const lineWidth = 5
export const CONFIG_COLOR = 'color'
export const OPERATION_CLEAR = 'clear'
export const CONFIG_LINE_WIDTH = 'lineWidth'
export const COLOR_SELECTION_SET = ['red', 'green', 'blue', 'yellow', 'pink', 'gray', 'white', 'aqua', 'brown']
export const COLOR_INITIAL_SELECTION_INDEX = 0
export const LINE_WIDTH_SELECTION_SET = [1, 2, 3, 4, 5, 6, 7]
export const LINE_WIDTH_CONFIG_SET = LINE_WIDTH_SELECTION_SET.map(i=>({value: i, label: `${i} 线宽`}))
export const LINE_WIDTH_INITIAL_SELECTION_INDEX = 4
\ No newline at end of file
export enum DrawRecordDirection {
BACKWARDS,
FORWARDS
}
\ No newline at end of file
import {ObjectDirective} from "vue";
export const vFocus:ObjectDirective = {
mounted(el:HTMLElement) {
setTimeout(()=>{
el.focus()
},0)
}
}
\ No newline at end of file
// @ts-nocheck
import {drawCanvas} from "../objs/renderBasic";
import {ComplexDrawHandleFunc} from "../types/HandlerFunc";
import {BaseDrawAttachment} from "../types/AttachmentSpecifics";
import {isDrawFromAction} from "../state/drawOperationType";
export const drawArrowsFunc: ComplexDrawHandleFunc = function (
sx,
sy,
ex,
ey,
attachment: BaseDrawAttachment) {
if (isDrawFromAction())
drawCanvas()
const ctx = attachment.ctx
arrowDrawAct(ctx, sx, sy, ex, ey)
}
export function arrowDrawAct(ctx, sx, sy, ex, ey) {
ctx.beginPath()
ctx.moveTo(sx, sy)
ctx.lineTo(ex, ey)
const angle = Math.atan((ey - sy) / (ex - sx))
if (ex - sx > 0) {
ctx.lineTo(30 * Math.cos(angle + 7 * Math.PI / 6) + ex, 30 * Math.sin(angle + 7 * Math.PI / 6) + ey)
ctx.moveTo(ex, ey)
ctx.lineTo(30 * Math.cos(angle + 5 * Math.PI / 6) + ex, 30 * Math.sin(angle + 5 * Math.PI / 6) + ey)
} else if (ex === sx) {
ctx.lineTo(30 * Math.cos(4 * Math.PI / 3) + ex, 30 * Math.sin(4 * Math.PI / 3) + ey)
ctx.moveTo(ex, ey)
ctx.lineTo(30 * Math.cos(5 * Math.PI / 3) + ex, 30 * Math.sin(5 * Math.PI / 3) + ey)
} else {
ctx.lineTo(30 * Math.cos(angle + Math.PI / 6) + ex, 30 * Math.sin(angle + Math.PI / 6) + ey)
ctx.moveTo(ex, ey)
ctx.lineTo(30 * Math.cos(angle - Math.PI / 6) + ex, 30 * Math.sin(angle - Math.PI / 6) + ey)
}
ctx.stroke()
}
\ No newline at end of file
// @ts-nocheck
import {drawCanvas} from "../objs/renderBasic";
import {ComplexDrawHandleFunc} from "../types/HandlerFunc";
import {BaseDrawAttachment} from "../types/AttachmentSpecifics";
import {isDrawFromAction} from "../state/drawOperationType";
export const drawEllipse: ComplexDrawHandleFunc = function (
sx,
sy,
ex,
ey,
attachment: BaseDrawAttachment
) {
if (typeof ex === undefined || typeof ey === undefined) return
if (isDrawFromAction())
drawCanvas()
const {ctx} = attachment
ellipseDrawAct(ctx, sx, sy, ex, ey)
}
export function ellipseDrawAct(ctx, sx, sy, ex, ey) {
ctx.beginPath()
ctx.ellipse((ex + sx) / 2, (ey + sy) / 2, Math.abs((ex - sx) / 2), Math.abs((ey - sy) / 2), 0, 0, 2 * Math.PI)
ctx.stroke()
}
\ No newline at end of file
import {drawCanvas, plantCanvas} from "../objs/renderBasic";
import {ComplexDrawHandleFunc} from "../types/HandlerFunc";
import {ComplexDrawAttachment, ContinuousDrawAttachment} from "../types/AttachmentSpecifics";
import {isDrawFromAction} from "../state/drawOperationType";
export const drawErase: ComplexDrawHandleFunc = function (
sx,
sy,
ex,
ey,
attachment: ComplexDrawAttachment & ContinuousDrawAttachment) {
if (typeof ex === 'undefined' || typeof ey === 'undefined') return
if (isDrawFromAction())
drawCanvas()
const {ctx, o_ctx, points} = attachment
points.push({ex, ey})
eraseDrawAct(ctx, o_ctx, ex, ey)
if (isDrawFromAction())
plantCanvas()
}
// @ts-ignore
export function eraseDrawAct(ctx, o_ctx, ex, ey) {
ctx.drawImage(o_ctx.canvas, ex - 6, ey - 6, 12, 12, ex - 6, ey - 6, 12, 12)
}
\ No newline at end of file
// @ts-nocheck
import {drawCanvas} from "../objs/renderBasic";
import {ComplexDrawHandleFunc} from "../types/HandlerFunc";
import {PayloadDrawAttachment} from "../types/AttachmentSpecifics";
import {isDrawFromAction} from "../state/drawOperationType";
export const drawExpression: ComplexDrawHandleFunc = function (
sx,
sy,
ex,
ey,
attachment: PayloadDrawAttachment) {
if (isDrawFromAction())
drawCanvas()
const ctx: CanvasRenderingContext2D = attachment.ctx
const payload = Number(attachment.payload) + 1
const selector = `#draw-box-image.for-view-only img:nth-of-type(${payload})`
const imageToBeDrawn = document.querySelector(selector) as HTMLImageElement
const width = parseInt(window.getComputedStyle(imageToBeDrawn).width)
const height = parseInt(window.getComputedStyle(imageToBeDrawn).height)
const cx = typeof ex === 'undefined' || Number.isNaN(ex) ? sx : ex
const cy = typeof ey === 'undefined' || Number.isNaN(ey) ? sy : ey
ctx.drawImage(
imageToBeDrawn,
0,
0,
imageToBeDrawn.naturalWidth,
imageToBeDrawn.naturalHeight,
cx - width / 2,
cy - height / 2,
width,
height
)
}
import {OperationRecordTyping, RawRecordItem, RecordType} from "../types/OperationRecordTyping";
import {CoreOperation} from "../objs/coreOperation";
import {drawCanvas, resetCanvas, setupCanvas} from "../objs/renderBasic";
import {ProcessCycleItem} from "../types/HandleProcessCycle";
import {setupColorConfig, setupLineWidthConfig} from "../state/operationConfigHisotry";
import {setDrawFromRecord} from "../state/drawOperationType";
export function drawOtherRecord (
records: Array<RawRecordItem>,
op: CoreOperation,
typing: OperationRecordTyping,
ctx: CanvasRenderingContext2D
) {
setDrawFromRecord()
drawRecord(
records,
op,
typing,
ctx
)
drawRecord(
// @ts-ignore
op.record?.getInternalState().rawRecord,
op,
op.record?.getInternalState().operationRecordTypeSet,
ctx,
true
)
}
export function drawRecord (
records: Array<RawRecordItem>,
op: CoreOperation,
typing: OperationRecordTyping,
ctx: CanvasRenderingContext2D,
keepCurrentScratch = false) {
if (!keepCurrentScratch) {
resetCanvas()
drawCanvas()
}
const operationInternalState = op.getInternalState()
setupCanvas(ctx)
records.forEach(({name, record}) => {
const operationTyping = Reflect.get(typing, name) as RecordType
if (operationTyping === RecordType.Immediate) {
const foundedImmediate = operationInternalState.immediatePs.find(e => e.name === name) as ProcessCycleItem
return foundedImmediate.func.call(null)
}
if (operationTyping === RecordType.Continuous) {
const foundedStamp = Reflect.get(operationInternalState.stampProcess, name) as ProcessCycleItem
foundedStamp.attachment.points.length = 0
const continuousPoints = (record as Array<{ ex: number, ey: number }>)
continuousPoints.forEach(point => {
foundedStamp.func.call(null, undefined, undefined, point.ex, point.ey, foundedStamp.attachment)
})
foundedStamp.attachment.points.length = 0
return
}
// 目前所有索引类记录只涉及到canvas的图像设置,并且操作均为Immediate
if (operationTyping === RecordType.Index) {
// 如果是回溯,则需要调整到前一个同类型操作
const foundedImmediate = operationInternalState.immediatePs.find(e => e.name === name) as ProcessCycleItem
if (name === 'colorConfig') {
setupColorConfig(record)
foundedImmediate.func.call(null, false)
} else if (name === 'lineWidth') {
setupLineWidthConfig(record)
foundedImmediate.func.call(null, false)
}
return
}
if (operationTyping === RecordType.VectorPayload) {
const foundedStamp = Reflect.get(operationInternalState.stampProcess, name) as ProcessCycleItem
foundedStamp.attachment.payload = record.payload
return foundedStamp.func.call(null, record.sx, record.sy, record.ex, record.ey, foundedStamp.attachment)
}
if (operationTyping === RecordType.Vector) {
const foundedStamp = Reflect.get(operationInternalState.stampProcess, name) as ProcessCycleItem
foundedStamp.func.call(null, record.sx, record.sy, record.ex, record.ey, foundedStamp.attachment)
}
})
}
\ No newline at end of file
import {drawCanvas} from "../objs/renderBasic";
import {ComplexDrawHandleFunc} from "../types/HandlerFunc";
import {BaseDrawAttachment} from "../types/AttachmentSpecifics";
import {drawOperationType, isDrawFromAction} from "../state/drawOperationType";
export const drawRect: ComplexDrawHandleFunc = function (
sx,
sy,
ex,
ey,
attachment: BaseDrawAttachment) {
if (typeof ex === 'undefined' || typeof ey === 'undefined') return
if (isDrawFromAction())
drawCanvas()
if (typeof sx === "number" && typeof sy === "number") {
const {ctx} = attachment
rectDrawAct(ctx, sx, sy, ex, ey)
}
}
// @ts-ignore
export function rectDrawAct(ctx, sx, sy, ex, ey) {
ctx.beginPath()
ctx.rect(Math.min(sx, ex), Math.min(sy, ey), Math.abs(sx - ex), Math.abs(sy - ey))
ctx.stroke()
}
\ No newline at end of file
import {drawCanvas} from "../objs/renderBasic";
import {ComplexDrawHandleFunc} from "../types/HandlerFunc";
import {ContinuousDrawAttachment} from "../types/AttachmentSpecifics";
import {isDrawFromAction} from "../state/drawOperationType";
export const drawStroke: ComplexDrawHandleFunc = function (
sx,
sy,
ex,
ey,
attachment: ContinuousDrawAttachment) {
if (typeof ex === 'undefined' || typeof ey === 'undefined') return
if (isDrawFromAction())
drawCanvas()
const {ctx, points} = attachment
points.push({ex, ey})
ctx.beginPath()
points.forEach((item, index) => {
if (index === 0) {
return ctx.moveTo(item.ex, item.ey)
}
ctx.lineTo(item.ex, item.ey)
})
ctx.stroke()
}
\ No newline at end of file
<template>
<n-notification-provider>
<draw-loading-notifier/>
</n-notification-provider>
<section :class="['editor-wrapper', !start && !bridgeOpen ? 'not-show' : '']" ref="wrapper">
<img :src="BlankImage" class="cut-image" alt="blank sketch" id="cut-blank"/>
<img class="cut-image" crossorigin="anonymous" alt="provided sketch" ref="urlImageHolder" id="provided-url-image"
src=""/>
<section :class="['editor-container', full ? 'editor-container-full' : '']">
<div ref="canvasWrapper" id="canvasWrapper" :style="{
'height': full ? '100%' : 'calc(100% - 40px)'
}">
<canvas ref="canvas" id="canvas"></canvas>
<movable-auto-scale-input
v-for="item in wholeTextInputWidgets"
:key="item.id"
:parent-element="canvasWrapper"
:id="item.id"
:input-text="item.text"
:sx="item.sx"
:sy="item.sy"
@input-cancel="removeWidget"
/>
</div>
<canvas ref="placeholder" class="cut-image"></canvas>
<canvas ref="sketch" class="cut-image"></canvas>
<div v-show="clipboardLoading" class="lds-dual-ring"></div>
<img id="clipboard-image" ref="clipboardImage" :src="clipboardImageURL" alt="clipboard image"/>
<section class="operation-branch" ref="operation" v-if="isMounted" v-show="!full">
<div>
<draw-arrow :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or"/>
<draw-stroke :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or"/>
<draw-ellipse :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or"/>
<draw-rect :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or"/>
<!-- <draw-text :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or" :parent-element="canvasWrapper"/>-->
<draw-expression :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or"/>
</div>
<div>
<draw-color-selection :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or"/>
<draw-pic-font :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or"/>
</div>
<div>
<draw-record :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :start="start" :or="p_or"/>
<draw-eraser :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or"/>
<draw-clear ref="clearPart" :co="p_co" :ctx="p_ctx" :o_ctx="p_oCtx" :or="p_or"/>
<n-button class="op-button sm-hide" type="info" size="small" @click="triggerDrawSave">保存</n-button>
<n-button v-if="showQuit" type="warning" class="sm-hide" size="small" @click="triggerDrawCancel">退出</n-button>
</div>
</section>
</section>
</section>
</template>
<script setup lang="ts">
import "./resources/fonts/iconfont.css"
import {ref, Ref, watch} from "vue";
import BlankImage from "./resources/blank.png"
import {NButton, NNotificationProvider} from "naive-ui";
import {drawCanvas, setupCanvas, triggerRenderBasic} from "./objs/renderBasic";
import {CoreOperation} from "./objs/coreOperation";
import DrawArrow from "./components/drawArrow.vue"
import DrawStroke from "./components/drawStroke.vue"
import DrawEllipse from "./components/drawEllipse.vue"
import DrawRect from "./components/drawRect.vue"
import DrawLoadingNotifier from "./components/drawLoadingNotifier.vue"
import {provide} from "vue";
// import DrawText from "./components/drawText.vue"
import DrawExpression from "./components/drawExpression.vue"
import DrawColorSelection from "./components/drawColorSelection.vue"
import DrawPicFont from "./components/drawLineWidth.vue"
import DrawClear from "./components/drawClear.vue"
import DrawEraser from "./components/drawEraser.vue"
import DrawRecord from "./components/drawRecord.vue"
import {OperationRecord} from "./objs/operationRecord";
import {clearAll, removeWidget, wholeTextInputWidgets} from "./state/wholeTextInput";
import MovableAutoScaleInput from "./widgets/MovableAutoScaleInput.vue"
import {drawOtherRecord} from "./funcs/drawRecord";
import {AndroidBridgeInfoType, currentBridgeInfo} from "./bridges/android.bridge";
const isMounted = ref(false)
const loadingInfo = ref({
display: false,
info: ''
})
provide('loadingInfo', loadingInfo)
const props = defineProps({
target: {
type: String,
default: '',
},
callBridge: {
type: Function,
default: () => {}
},
start: {
type: Boolean,
default: false,
required: true
},
fromClipboardImage: {
type: Boolean,
default: false
},
recordListener: {
type: Function,
default() {
}
},
urlImage: {
type: String,
default: ''
},
passInRecords: {
type: String,
default: ''
},
full: {
type: Boolean,
default: false
},
beforeCancel: {
type: Function,
default: async () => true
},
showQuit: {
type: Boolean,
default: true
}
})
const emits = defineEmits(['done', 'cancel', 'first-paint-done', 'canvas-saved'])
// 如果是由外部bridge打开,同样需要显示
const bridgeOpen = ref(false)
// 为外层传入的video元素,作为截图canvas的主要来源
let target: CanvasImageSource
// 整体外层,主要用于定位
const wrapper = ref(null) as Ref<Element | null>
// 画图操作显示的canvas面板
const canvas = ref(null) as Ref<HTMLCanvasElement | null>
// 用于存储canvas的外壳容器,主要用于承载多个文本框
const canvasWrapper = ref(null) as Ref<HTMLDivElement | null>
// 用于存储历史记录的canvas面板
const placeholder = ref(null) as Ref<HTMLCanvasElement | null>
// 用于存储最初截图的canvas面板
const sketch = ref(null) as Ref<HTMLCanvasElement | null>
// 底部operation
const operation = ref() as Ref<HTMLElement>
// 标记清除的组件
const clearPart = ref()
// 用来承载来自于url的图片
const urlImageHolder = ref()
let width: number, height: number
let canvasWidth: number
let canvasHeight: number
let coreOperation: CoreOperation
let operationRecord: OperationRecord
let ctx: CanvasRenderingContext2D
let sketch_ctx: CanvasRenderingContext2D
let placeholder_ctx: CanvasRenderingContext2D
let p_ctx = ref(null)
let p_co = ref(null)
let p_oCtx = ref(null)
let p_or = ref(null)
let clipboardImage = ref() as Ref<HTMLImageElement>
let clipboardImageURL = ref('')
let clipboardLoading = ref(false) as Ref<boolean>
let triggerDrawSave = () => {
ctx.canvas.toBlob(function (blob) {
emits('canvas-saved', blob)
} as BlobCallback)
}
let triggerDrawCancel = async () => {
const canClose = await props.beforeCancel()
if (canClose) {
clearAll()
clearPart.value.clear()
emits('cancel')
}
}
let beginInitial = () => {
placeholder_ctx.drawImage(target, 0, 0, width, height, 0, 0, canvasWidth, canvasHeight)
sketch_ctx.drawImage(target, 0, 0, width, height, 0, 0, canvasWidth, canvasHeight)
operationRecord.setRelativeGEO(canvasWidth, canvasHeight)
triggerRenderBasic.renderDrawArgs = [
ctx, placeholder.value, 0, 0, canvasWidth, canvasHeight, 0, 0, canvasWidth, canvasHeight
]
triggerRenderBasic.renderPlantArgs = [
placeholder_ctx, canvas.value, 0, 0, canvasWidth, canvasHeight, 0, 0, canvasWidth, canvasHeight
]
triggerRenderBasic.renderResetArgs = [
placeholder_ctx, sketch.value, 0, 0, canvasWidth, canvasHeight, 0, 0, canvasWidth, canvasHeight
]
drawCanvas()
setupCanvas(ctx)
if (typeof props.callBridge === 'function') {
props.callBridge('初步加载完成')
}
setTimeout(function () {
ctx.canvas.toBlob(function (blob) {
emits('first-paint-done', blob)
} as BlobCallback, 'image/png')
})
}
let geoHandle = function () {
// @ts-ignore
canvasWidth = canvasWrapper.value?.clientWidth
// @ts-ignore
canvasHeight = canvasWrapper.value?.clientHeight
canvas.value?.setAttribute('width', canvasWidth.toString())
canvas.value?.setAttribute('height', canvasHeight.toString())
placeholder.value?.setAttribute('width', canvasWidth.toString())
placeholder.value?.setAttribute('height', canvasHeight.toString())
sketch.value?.setAttribute('width', canvasWidth.toString())
sketch.value?.setAttribute('height', canvasHeight.toString())
}
let initDraw = async () => {
if (props.start) {
if (props.fromClipboardImage) {
try {
clipboardLoading.value = true
const clipboardItems: Array<ClipboardItem> = await navigator.clipboard.read()
const clippedImage = await clipboardItems[0].getType('image/png')
clipboardImageURL.value = window.URL.createObjectURL(clippedImage)
loadingInfo.value.display = true
loadingInfo.value.info = '正在加载剪贴板图片,请稍后'
clipboardImage.value?.addEventListener('load', () => {
target = clipboardImage.value
width = clipboardImage.value.naturalWidth
height = clipboardImage.value.naturalHeight
clipboardLoading.value = false
window.URL.revokeObjectURL(clipboardImageURL.value)
loadingInfo.value.display = false
beginInitial()
})
} catch (e) {
console.error("没有发现图片剪贴来源!")
loadingInfo.value.display = false
console.log(e)
clipboardLoading.value = false
triggerDrawCancel()
}
return
}
if (bridgeOpen.value) {
target = document.querySelector('#provided-url-image') as HTMLImageElement
target.src = currentBridgeInfo.value.data
loadingInfo.value.display = true
loadingInfo.value.info = '正在加载外部图像,请稍后'
target.onload = function () {
width = urlImageHolder.value.naturalWidth
height = urlImageHolder.value.naturalHeight
beginInitial()
loadingInfo.value.display = false
}
target.onerror = function () {
loadingInfo.value.display = false
}
return
}
if (props.urlImage) {
target = document.querySelector('#provided-url-image') as HTMLImageElement
target.src = props.urlImage
loadingInfo.value.display = true
loadingInfo.value.info = '正在加载外部图像,请稍后'
target.onload = function () {
// @ts-ignore
width = target.naturalWidth
// @ts-ignore
height = target.naturalHeight
loadingInfo.value.display = false
beginInitial()
}
target.onerror = function () {
loadingInfo.value.display = false
}
return
}
target = document.querySelector(props.target) as HTMLVideoElement | HTMLImageElement
if (!target || !props.target) {
target = document.querySelector('#cut-blank') as HTMLImageElement
if (target.naturalWidth && target.naturalHeight) {
width = target.naturalWidth
// @ts-ignore
height = target.naturalHeight
beginInitial()
return
}
loadingInfo.value.display = true
loadingInfo.value.info = '正在加载图像,请稍后'
target.addEventListener('load', function () {
// @ts-ignore
width = target.naturalWidth
// @ts-ignore
height = target.naturalHeight
loadingInfo.value.display = false
beginInitial()
})
target.addEventListener('error', function () {
loadingInfo.value.display = false
})
} else {
if (target instanceof HTMLImageElement) {
// @ts-ignore
width = target.naturalWidth
// @ts-ignore
height = target.naturalHeight
} else if (target instanceof HTMLVideoElement) {
width = target.videoWidth
height = target.videoHeight
}
beginInitial()
}
}
}
// 如果是从外部bridge传值,则需要优先储存数据
const bridgeRecordMapper = ref({})
let longQueueHandle = function () {
geoHandle()
ctx = canvas.value?.getContext('2d') as CanvasRenderingContext2D
sketch_ctx = sketch.value?.getContext('2d') as CanvasRenderingContext2D
placeholder_ctx = placeholder.value?.getContext('2d') as CanvasRenderingContext2D
coreOperation = new CoreOperation(canvasWrapper.value as HTMLDivElement)
operationRecord = new OperationRecord(props.recordListener)
coreOperation.setRecord(operationRecord)
// @ts-ignore
p_ctx.value = ctx
// @ts-ignore
p_co.value = coreOperation
// @ts-ignore
p_oCtx.value = sketch_ctx
// @ts-ignore
p_or.value = operationRecord
isMounted.value = true
watch(() => props.start, initDraw, {immediate: true})
watch(() => props.passInRecords, function () {
if (props.passInRecords?.length) {
const wholeRawRecords = operationRecord.parseRecord(props.passInRecords)
drawOtherRecord(wholeRawRecords, coreOperation, operationRecord.getInternalState().operationRecordTypeSet, ctx)
}
})
watch(bridgeOpen, initDraw, {immediate: true})
watch(currentBridgeInfo, function () {
if (currentBridgeInfo.value.type === AndroidBridgeInfoType.Open) {
bridgeOpen.value = true
} else if (currentBridgeInfo.value.type === AndroidBridgeInfoType.Close) {
bridgeOpen.value = false
triggerDrawCancel()
} else if (currentBridgeInfo.value.type === AndroidBridgeInfoType.Mark) {
if (currentBridgeInfo.value.data && currentBridgeInfo.value.id) {
Reflect.set(bridgeRecordMapper.value, currentBridgeInfo.value.id, currentBridgeInfo.value.data)
// @ts-ignore
const normalizedRecords = Object.values(bridgeRecordMapper.value).map(q => q.trim()).join(' ')
const wholeRawRecords = operationRecord.parseRecord(normalizedRecords)
drawOtherRecord(wholeRawRecords, coreOperation, operationRecord.getInternalState().operationRecordTypeSet, ctx)
}
}
}, {
deep: true,
immediate: true
})
}
let pullUntilMounted = setInterval(() => {
props.callBridge('进入元素查找')
if (!canvasWrapper.value) return;
props.callBridge('找到canvas元素')
if (canvasWrapper.value.clientWidth === 0) return;
props.callBridge('元素几何确定')
if (canvasWrapper.value?.getBoundingClientRect()?.width === 0) return;
props.callBridge('元素几何数据成功获取')
clearInterval(pullUntilMounted)
longQueueHandle()
}, 0)
</script>
<style>
i.iconfont.activated {
color: #00AFF0;
}
.operation-branch i.iconfont::before {
color: #ffffff !important;
}
</style>
<style scoped>
@import url("resources/styles/opitem.css");
@import url("resources/styles/entry.css");
@import url("resources/styles/media-adapt.css");
</style>
\ No newline at end of file
import {plantCanvas} from "./renderBasic";
import {GenericDrawFunc} from "../types/HandlerFunc";
import {clamp} from "../utils/math";
import {ProcessCycle, ProcessCycleItem, ProcessCycleMapping} from "../types/HandleProcessCycle";
import {isDrawFromAction, setDrawFromAction} from "../state/drawOperationType";
import {OperationRecord} from "./operationRecord";
import {enableRecover, rollbackStep} from "../state/canvasRecover";
import {CoreInternalState} from "../types/CoreOperationTyping";
export class CoreOperation {
private readonly defaultNoop: string = 'noop'
private currentDrawOption: string = 'noop'
private currentImmediateOption: string | undefined = 'noop'
private currentDrawAttachment: any = null
private isMouseDown: boolean = false
private element: HTMLElement
private readonly cvs_width: number
private readonly cvs_height: number
private sx: number | undefined
private sy: number | undefined
private ex: number | undefined
private ey: number | undefined
public record: OperationRecord | undefined
private readonly persistedStartPs: Array<ProcessCycleItem> = []
private readonly persistedStampPs: Array<ProcessCycleItem> = []
private readonly persistedStopPs: Array<ProcessCycleItem> = []
private readonly immediatePs: Array<ProcessCycleItem> = []
private readonly beforeStartPs: Array<ProcessCycleItem> = []
private readonly startProcess: ProcessCycleMapping
private readonly stampProcess: ProcessCycleMapping
private readonly stopProcess: ProcessCycleMapping
private isHandleImmediate: boolean
constructor(canvas: HTMLElement) {
this.element = canvas
this.startProcess = {}
this.stampProcess = {}
this.stopProcess = {}
this.isHandleImmediate = false
this.record = undefined
const innerCanvas = this.element.querySelector('canvas')
// @ts-ignore
this.cvs_width = innerCanvas.width
// @ts-ignore
this.cvs_height = innerCanvas.height
this.addEventListener()
}
setRecord(record: OperationRecord) {
this.record = record
}
triggerDraw(name: string) {
const immediateFunc: ProcessCycleItem | undefined = this.immediatePs.find(item => item.name === name)
this.isHandleImmediate = typeof immediateFunc !== 'undefined'
// 立即触发类方法不会主动调用本方法,而会调用其他所有常驻方法,
if (typeof immediateFunc !== 'undefined') {
this.currentDrawAttachment = immediateFunc.attachment
this.currentImmediateOption = immediateFunc.name
this.immediatePs.filter(item => item.name !== immediateFunc.name && item.persist).forEach(f => {
f.func.call(null)
})
} else {
this.currentDrawOption = name
}
this.beforeStartPs.forEach(item => {
item.func.call(null)
})
}
register(cycle: ProcessCycle, name: string, func: GenericDrawFunc, attachment: Object, persist?: boolean) {
switch (cycle) {
case ProcessCycle.Immediate:
this.immediatePs.push({func, attachment, name, persist})
return;
case ProcessCycle.BeforeStart:
this.beforeStartPs.push({func})
return;
case ProcessCycle.Start:
if (persist)
this.persistedStartPs.push({func, attachment})
else
Reflect.set(this.startProcess, name, {func, attachment})
return
case ProcessCycle.Stamp:
if (persist)
this.persistedStampPs.push({func, attachment})
else
Reflect.set(this.stampProcess, name, {func, attachment})
return;
case ProcessCycle.Stop:
if (persist)
this.persistedStopPs.push({func, attachment})
else
Reflect.set(this.stopProcess, name, {func, attachment})
return;
default:
break
}
}
stopDraw() {
this.currentDrawOption = this.defaultNoop
}
// 返回所有私有内部成员变量,不仅可以抽取数据,而且可以直接发起绘图
public getInternalState(): CoreInternalState {
return {
sx: this.sx,
sy: this.sy,
ex: this.ex,
ey: this.ey,
isMouseDown: this.isMouseDown,
currentDrawOption: this.currentDrawOption,
currentDrawAttachment: this.currentDrawAttachment,
currentImmediateOption: this.currentImmediateOption,
startProcess: this.startProcess,
stampProcess: this.stampProcess,
stopProcess: this.stopProcess,
persistedStartPs: this.persistedStartPs,
persistedStampPs: this.persistedStampPs,
persistedStopPs: this.persistedStopPs,
immediatePs: this.immediatePs,
isHandleImmediate: this.isHandleImmediate
}
}
private endDraw() {
if (!this.isMouseDown) return
this.persistedStopPs.forEach(item => {
if (item.func.length === 5)
item.func.call(null, this.sx, this.sy, this.ex, this.ey, item.attachment)
else
item.func.call(null)
})
const currentOp: ProcessCycleItem = Reflect.get(this.stopProcess, this.currentDrawOption)
if (typeof currentOp !== 'undefined') {
currentOp.func.call(null, this.sx, this.sy, this.ex, this.ey, currentOp.attachment)
}
plantCanvas()
this.sx = undefined
this.sy = undefined
this.ex = undefined
this.ey = undefined
this.isMouseDown = false
this.currentDrawAttachment = null
}
private handleStart(sx: number, sy: number, rx?: number, ry?: number) {
if (this.currentDrawOption === this.defaultNoop) return
if (!isDrawFromAction()) {
setDrawFromAction()
plantCanvas()
const toBeCut = this.record?.getInternalState().rawRecord
// @ts-ignore
this.record?.setNewRawRecord(toBeCut?.slice(0, toBeCut?.length - rollbackStep.value))
rollbackStep.value = 0
enableRecover.value = false
}
this.triggerDraw(this.currentDrawOption)
this.isMouseDown = true
this.sx = sx
this.sy = sy
const currentOp: ProcessCycleItem = Reflect.get(this.startProcess, this.currentDrawOption)
if (typeof currentOp !== 'undefined') {
this.currentDrawAttachment = currentOp.attachment
if (Reflect.has(this.currentDrawAttachment, 'realPoint')) {
currentOp.func.call(null, rx, ry, this.ex, this.ey, currentOp.attachment)
} else {
currentOp.func.call(null, this.sx, this.sy, this.ex, this.ey, currentOp.attachment)
}
}
this.persistedStartPs.forEach(item => {
item.func.call(null, this.sx, this.sy, this.ex, this.ey, item.attachment)
})
}
private handleMove(ex: number, ey: number) {
if (this.currentDrawOption === this.defaultNoop || !this.isMouseDown) return
this.ex = ex
this.ey = ey
const currentOp: ProcessCycleItem = Reflect.get(this.stampProcess, this.currentDrawOption)
if (typeof currentOp !== 'undefined')
currentOp.func.call(null, this.sx, this.sy, this.ex, this.ey, currentOp.attachment)
this.persistedStampPs.forEach(item => {
item.func.call(null, this.sx, this.sy, this.ex, this.ey, item.attachment)
})
}
private addEventListener() {
const genericMobileHandle = (e: TouchEvent): { sx: number, sy: number, rx: number, ry: number } => {
const {left, top} = this.element.getBoundingClientRect()
const sx = clamp(0, e.touches[0].clientX - left, this.element.clientWidth)
const sy = clamp(0, e.touches[0].clientY - top, this.element.clientHeight)
const realX = sx / this.element.clientWidth * this.cvs_width
const realY = sy / this.element.clientHeight * this.cvs_height
return {
sx: realX,
sy: realY,
rx: sx,
ry: sy
}
}
const genericDesktopHandle = (e: MouseEvent): { sx: number, sy: number, rx: number, ry: number } => {
const sx = clamp(0, e.offsetX, this.element.clientWidth)
const sy = clamp(0, e.offsetY, this.element.clientHeight)
const realX = sx / this.element.clientWidth * this.cvs_width
const realY = sy / this.element.clientHeight * this.cvs_height
return {
sx: realX,
sy: realY,
rx: sx,
ry: sy
}
}
document.body.addEventListener('touchmove', e => {
e.preventDefault()
})
this.element.addEventListener('mousedown', (e: MouseEvent) => {
const {sx: realX, sy: realY, rx, ry} = genericDesktopHandle(e)
this.handleStart(realX, realY, rx, ry)
})
this.element.addEventListener('mousemove', (e: MouseEvent) => {
const {sx: realX, sy: realY} = genericDesktopHandle(e)
this.handleMove(realX, realY)
})
this.element.addEventListener('mouseup', this.endDraw.bind(this))
this.element.addEventListener('mouseleave', this.endDraw.bind(this))
this.element.addEventListener('touchstart', e => {
const {sx: realX, sy: realY, rx, ry} = genericMobileHandle(e)
this.handleStart(realX, realY, rx, ry)
})
this.element.addEventListener('touchmove', e => {
e.stopPropagation()
const {sx: realX, sy: realY} = genericMobileHandle(e)
this.handleMove(realX, realY)
})
this.element.addEventListener('touchend', this.endDraw.bind(this))
this.element.addEventListener('touchcancel', this.endDraw.bind(this))
}
}
\ No newline at end of file
import {
ContinuousRecord,
GenericRecordType,
IndexRecord,
OperationRecordAlias,
OperationRecordInternalState,
OperationRecordTyping,
RawRecordItem,
RecordType,
VectorRecord
} from "../types/OperationRecordTyping";
import {deepClone} from "../utils/object";
export class OperationRecord {
private readonly operationRecordTypeSet: OperationRecordTyping
private readonly operationRecordAlias: OperationRecordAlias
private rawRecord: Array<RawRecordItem>
private operationRecord: string
private relativeWidth: number = 0
private relativeHeight: number = 0
private recordListener: Function
constructor(recordListener: Function) {
this.operationRecordTypeSet = {}
this.operationRecordAlias = {}
this.rawRecord = []
this.operationRecord = ''
this.recordListener = recordListener
}
registerType(name: string, type: RecordType, alias: string) {
Reflect.set(this.operationRecordTypeSet, name, type)
Reflect.set(this.operationRecordAlias, name, alias)
}
setRelativeGEO(width: number, height: number) {
this.relativeWidth = width
this.relativeHeight = height
}
public setNewRawRecord(records: Array<RawRecordItem>) {
this.rawRecord = deepClone(records)
this.operationRecord = ''
this.rawRecord.forEach(recordItem => {
this.storeRecord(recordItem.name, recordItem.record, false)
})
}
storeRecord(name: string, record: GenericRecordType, recording = true) {
const currentRecordType: RecordType = Reflect.get(this.operationRecordTypeSet, name)
const currentRecordAlias: string = Reflect.get(this.operationRecordAlias, name)
if (recording && ![RecordType.Revoke, RecordType.Recover].includes(currentRecordType)) {
this.rawRecord.push({
name,
record
})
}
this.operationRecord += this.makeRecordString(record, currentRecordAlias, currentRecordType)
if (currentRecordType === RecordType.Recover || currentRecordType === RecordType.Revoke) {
// @ts-ignore
const slicedRecords = this.rawRecord.slice(0, this.rawRecord.length - record)
.reduce((q, a)=>{
const recordType: RecordType = Reflect.get(this.operationRecordTypeSet, a.name)
const alias: string = Reflect.get(this.operationRecordAlias, a.name)
const record = this.makeRecordString(a.record, alias, recordType)
return q + record
}, '')
this.recordListener.call(null, slicedRecords)
}
else {
this.recordListener.call(null, this.operationRecord)
}
}
private makeRecordString(record: GenericRecordType, currentRecordAlias: string, currentRecordType: RecordType) {
switch (currentRecordType) {
case RecordType.Vector:
const vectorRecord = (record as VectorRecord)
return `${currentRecordAlias} ${(vectorRecord.sx / this.relativeWidth).toFixed(2)} ${(vectorRecord.sy / this.relativeHeight).toFixed(2)} ${(vectorRecord.ex / this.relativeWidth).toFixed(2)} ${(vectorRecord.ey / this.relativeHeight).toFixed(2)} `
case RecordType.Continuous:
const continuousRecord = (record as ContinuousRecord)
return `${currentRecordAlias} ${continuousRecord.map(item => [(item.ex / this.relativeWidth).toFixed(2), (item.ey / this.relativeHeight).toFixed(2)]).flat().join(' ')} `
case RecordType.Index:
const indexRecord = (record as IndexRecord)
return `${currentRecordAlias} ${indexRecord} `
case RecordType.Immediate:
return `${currentRecordAlias} `
case RecordType.VectorPayload:
const pointPayloadRecord = (record as VectorRecord)
return `${currentRecordAlias} ${(pointPayloadRecord.sx / this.relativeWidth).toFixed(2)} ${(pointPayloadRecord.sy / this.relativeHeight).toFixed(2)} ${(pointPayloadRecord.ex / this.relativeWidth).toFixed(2)} ${(pointPayloadRecord.ey / this.relativeHeight).toFixed(2)} ${pointPayloadRecord.payload} `
case RecordType.Recover:
case RecordType.Revoke:
case RecordType.UNSET:
default:
return ''
}
}
parseRecord(inputRecord: string): Array<RawRecordItem> {
let wholeRecord: Array<RawRecordItem> = []
let inputRecordSplit: Array<string> = inputRecord.trim().split(' ')
let paintCommandAlias: string
let paintCommand: string
try {
inputRecordSplit.forEach(span => {
if (!Object.values(this.operationRecordAlias).includes(span)) {
const lastInsertedRecord = wholeRecord[wholeRecord.length - 1]
const lastRecordType = this.operationRecordTypeSet[lastInsertedRecord.name]
if (lastRecordType === RecordType.Vector || lastRecordType === RecordType.VectorPayload) {
const objectKeyLength = Object.keys(lastInsertedRecord.record).length
if (objectKeyLength === 0) {
lastInsertedRecord.record.sx = Number(span) * this.relativeWidth
return
}
if (objectKeyLength === 1) {
lastInsertedRecord.record.sy = Number(span) * this.relativeHeight
return
}
if (objectKeyLength === 2) {
lastInsertedRecord.record.ex = Number(span) * this.relativeWidth
return
}
if (objectKeyLength === 3) {
lastInsertedRecord.record.ey = Number(span) * this.relativeHeight
}
if (lastRecordType === RecordType.VectorPayload) {
lastInsertedRecord.record.payload = span
} else {
lastInsertedRecord.record.payload = null
}
return;
}
if (lastRecordType === RecordType.Continuous) {
if (!Array.isArray(lastInsertedRecord.record)) {
lastInsertedRecord.record = []
}
const lastRecordExEyItem = lastInsertedRecord.record[lastInsertedRecord.record.length - 1]
if (!lastRecordExEyItem || lastRecordExEyItem.ey) {
const newlyAddedItem = {
ex: Number(span) * this.relativeWidth
}
lastInsertedRecord.record.push(newlyAddedItem)
return
}
if (!lastRecordExEyItem.ey) {
lastRecordExEyItem.ey = Number(span) * this.relativeHeight
}
return
}
if (lastRecordType === RecordType.Index) {
lastInsertedRecord.record = Number(span)
return
}
lastInsertedRecord.record = null
return
}
paintCommandAlias = span[0]
paintCommand = Object.keys(this.operationRecordAlias).find(key => this.operationRecordAlias[key] === paintCommandAlias) as string
let currentFoundRecord: RawRecordItem = {
name: '',
record: {}
}
currentFoundRecord.name = paintCommand
wholeRecord.push(currentFoundRecord)
})
} catch (e) {
console.log('error???', e)
return []
}
return wholeRecord
}
removeRecord() {
this.rawRecord.length = 0
this.operationRecord = ''
}
public getInternalState(): OperationRecordInternalState {
return {
operationRecordTypeSet: this.operationRecordTypeSet,
operationRecord: this.operationRecord,
operationRecordAlias: this.operationRecordAlias,
rawRecord: this.rawRecord
}
}
}
\ No newline at end of file
import {color, lineWidth} from "../config/canvasConfig";
import {restoreColorConfig, restoreLineWidthConfig} from "../state/operationConfigHisotry";
export const triggerRenderBasic = {
renderDrawArgs: [] as Array<any>,
renderResetArgs: [] as Array<any>,
renderPlantArgs: [] as Array<any>,
draw: 0,
reset: 0,
plant: 0
}
const renderBasic = function (
ctx: CanvasRenderingContext2D,
target: CanvasImageSource,
sx: number,
sy: number,
sw: number,
sh: number,
dx: number,
dy: number,
dw: number,
dh: number
) {
ctx.clearRect(0, 0, dw, dh)
ctx.drawImage(target, sx, sy, sw, sh, dx, dy, dw, dh)
}
let draw = 0
let plant = 0
let reset = 0
Object.defineProperty(triggerRenderBasic, 'reset', {
get() {
return reset
},
set(v) {
reset = v
// @ts-ignore
renderBasic.apply(null, triggerRenderBasic.renderResetArgs)
}
})
Object.defineProperty(triggerRenderBasic, 'plant', {
get() {
return plant
},
set(v) {
plant = v
// @ts-ignore
renderBasic.apply(null, triggerRenderBasic.renderPlantArgs)
}
})
Object.defineProperty(triggerRenderBasic, 'draw', {
get() {
return draw
},
set(v) {
draw = v
// @ts-ignore
renderBasic.apply(null, triggerRenderBasic.renderDrawArgs)
}
})
export function drawCanvas() {
triggerRenderBasic.draw++
}
export function plantCanvas() {
triggerRenderBasic.plant++
}
export function resetCanvas() {
triggerRenderBasic.reset++
}
export function setupCanvasColor(ctx: CanvasRenderingContext2D) {
ctx.strokeStyle = color
ctx.fillStyle = color
restoreColorConfig()
}
export function setupCanvasLineWidth(ctx: CanvasRenderingContext2D) {
ctx.lineWidth = lineWidth
restoreLineWidthConfig()
}
export function setupCanvas(ctx: CanvasRenderingContext2D) {
setupCanvasColor(ctx)
setupCanvasLineWidth(ctx)
}
\ No newline at end of file
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
@font-face {
font-family: "iconfont";
src: url('iconfont.eot?t=1619149728418');
/* IE9 */
src: url('iconfont.eot?t=1619149728418#iefix') format('embedded-opentype'),
/* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAEEEAAsAAAAAfCQAAECyAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCUFAqBxTiBmnEBNgIkA4R8C4JAAAQgBYRtB45WG9VkdUG4HUAgqfqRElG5qoyKGjE4ebL//4ykYwwH5iaqFr4OqkWgCwN0ZKeJ1bbDoAnwJp5R2pEmkO4+kEfQ+1WTNnjp63xmWRa8RemK1ZAFOYSzdAjGMQ1J09iJ+cm02Zvw0MoyeRyaloE7QkvOc9ITKEeQR1YzCsb+5PxrHN1uTNBhebv8kKQomoiBDUubHDDBI/SlR4U50/LGQl0oDwgIgJ7pcYKTq9EIwLhavBopPgiwMb3HkpSkJiRBPOlVDpCkqHJjvQD4P51V9SXLnv0lbJoDgCABznZZpWpYMGuQ3EqktJSSbc2woeGOB4jXlt677KCyyaa3U0KKICFB4zECjMarFwYpMRq7+6q+pUiyJJdcnz0sv+jnpmzX97rywSQkISYBHgDZ+TeoZ+63FIaBcTqRIWURp/gDP7f/c+/dRi2aqDtGSTnYQEIU2MagBzJKlFEbUhJpsBEKqAiYgIr5DBADTNAnRoP61C8YKP/p00Z4+kKfMp/nci+OcEybOPWuNQiXFnEggeR/NAdQWU21wVrCwXI6YSRZ9ocQqvaL+ofphePKg92m2nx/99QZmP4TyxJoHZLvfvJj5T2vmCS5wBWuEeKVyLJEKvFHQl2QmX/7R3Pa2tQOEQ5KA4LsTWobpzY/1uWHCCDAbsK/veAB2NPvfy+/tbUwTQOAgqTHQsocm0pn9ZXAbMie2b2QqS1feS7Exyf9Qvy9ulsC1Gpk0wLv0JKT8AbLTEDg8yKYFLDHl2KWcCBdkBi7yngT2JfzK4T8To/P+/Z+C5HLlFnEM9LeYMdpWvUYc3lQP6Y+GhkREpKg4ljZr70MZ313ItZOqyt1AoGg+2ca0MlAKxj7nVVBgPKvGtCsUBGBANepIjSyuWSYO+GcIuPBjPhOv28+ZoUt0MIS/9WOc1gKgtlYU/xO51sj3u3NOBtBK5iCNtByLl2bQ1Kcp9BSypYcFtXniqCMbUxRcvYIGkwoO//pvJAKpUqt0er0QpFYIpXJFUo9fQNDI2MTUzNzC0sraxuVWqPV2drZOzg6Obu4url7eHrpvH18/fyFhEVExcQlJKWkZWTl5BUUlZRVVNXUNTS1tHV09fQNDI2MTUzNzC0sraxtbO3sHRydnF1c3dw9PL28fXz9+PXnf2Nza3tnd2//4PDo+OT07Pzi8ur65vbu/uHx6fnlVVP5+y5A+23Y/hcTQQMxowViQSvEijYQG9pC7GgHcaA9xIkOEBc6QtzoBPGgM8SLLhAfukL86AYJoDskiB4khJ4kjF4kgt4kij4khr4kjn4kgf4kiQEkhYEkjUEkg8EkiyEkh6Ekj2GkgOGkiBGkhJGkjFGkgtGkijGkhrGkjnGkgfGkiQmkhYmkjUmkg8mkiymkh6mkj2lkgOlkCDPIMGaSEcwio5hNxjCHjGMumcA8Mon5ZAoLyDQWkhksIrNYTOYS1HnAErKApWQRy8gSlpNlrCArWElWsYqsYTVZxxqygbVkE+vIFtaTbWwgO9hIdrGJ7GEz2ccWcoCt5BDbyBG2k2PsICfYSU6xi5xhNznHHnKBveQS+8gV9pNrHCA3OEhucYjc4TC5xxHygKPkEcfIE46TZ5wgLzhJXnGKvOE0eccZ8oGz5BPnyBfOk29cID+4SH5xifzhMvnHFQqAqxQQ1ygQrlNg3KAguElBcYuC4TYFxx0KgbsUEvcoFO5TaDygMHhIYfGIwuExhccTioCnFBHPKBKeU2S8UBS8VFS8UjS8VnS8UQy8VUy8Uyy8V2x8UBx8VFx8Ujx8Vnx8UQJ8VUJ8UyJ8V2L8oCT4UUnxk5LhZyXHL0qBX5USvykVfldq/KE0+FNp8ZfS4W+lxz/KgH+VcfLfk+49zUBL19BXE5b82DpqoNkOaZ1G4ss9LECYKMwwISXlSEIs5kkUmKJzwgHCSJc4QfxEUFuE5CizaDFoSKKoBjPleTAQIoUUQEKLOCMSKQqicyIh8WKVNfRszKWKiEjsRbqSWooUKtaQhKVg6MELVDhAQtR1RBUqCJ1rHiTIVdJRknoBpAdyQVVzfEvpVSPHrwoQNvf3sH3gkEvWy+hoaMheqmNmXDYUguDB2lnysWXbh7ika/dPLKyVbOmVPdGI2zk1TMZMmjXHVEgThp9j8h1DG8SXGMniDAfHGGp3qEykxNfRWtJUR4SfQ2t7R1t5XfgyHg2l2oOBy9wBowhXR5ueAcUPQTFZNucIgx3HUsSlTWc3zoRN31pyc+bOTbnu9NIj9N6GDIfPm8Y7yhMJy2hE5lxPrx/r28vL26JTKqnnOiZNBlz5HGyoFb59ohkHWDnQyEWDmRy+RZMoTpll3abM8yMjhVDueYcD02Z16tf7ubznPVS8KwUMfLvDFO5UR24qTxfM/4YDF+h1yCylFhsZTgBxv0RfkK6P1fJl7wjW+2IIKeuoijs9X1H+sGHzguHCMXVks/qacHemxzMSeKH8/mqb50rI8H9cr74ennjrZiLpaDIQf9rEZpsxRfOj7XHNaGBvApgSC3Ou1/xDxZenb5y8gqrEM00gQgtAyOw4JV2iU7hKCTbW+PUOrRKR8UjJUPZYZbPhuAiahGBmmFj+CaGT14HDmcuWc+vpnuDro6moZG6sMe5ZLVD9Jre2t1aptsJITxW6utjXpOwCAlVIZ+Fkbfm4c6nVMxmUUMZRXnIz4yS1QhD8ujEIgXEPnSnefF176aN1v7wn/CLiur76rE3xODZWLvjNf1/TJb/OwVo010kdMbhAVLoksRPAhUJUmCrfCCbECXBuvmcZtgQlh+okAQ63b8T7Wce9pNzDzJByH/f/vy8JPfnO3tq+Z28izq047nF651Zs2NrJXDkDrxOnPZzsmXdmj1kVcy+CcZQji8KG2ozwCaMKWi5gntYsk/uJFlpgOingn6tdcj7WDjA5O6onIi43X7AV6Kt1YVYW/xEsXPx2IEfcIO9fsDDFv87Es8HA1W/Pv9mznj63mXGcc7bK9tbVD5tr6ketddXRP25vaJ+4mw93DNCJZMifmSMqVjKbLS/WiG8aWqD65FKczqWqmUwhuz2L8YS52DDtFNeOO/KHF8KMKanm5sL4SWuUAPcGQcuH003v6cMr39koiGopxtjWKINOYb/KYzaM1X9/9NZvcOPXQ9d/h5txU1BYCezeForqPGEp5v44p5pcNdLreV5OC40aOvob9nu7zTeNj1gFHHY1be7uG41F6M21mh5P02kQ/ZTOO0mCGiHIPqMLqASgd2VjeUTHrnOCG5RxZQm3m6ItypltWBHJolq3mcylisNTA7FY5arejqagLVlztwmWR0nXYRBoo1MNMRX3jV8zQhAwGSP9oGXuOVWmktbfWOrRGZXIquQ08fs4oqyKacWOaXLg9izAybtAW2IH4ZT7AyXYAVFJOAFDvDhdNy/x3aDq4XFnj4jmjyBk92sbaQ0Nldrwbj6Z7GCQqEbA9I4xLp4MiTw2irJFuriCLmhQq8K+eVZiTCjlr6c7ooCfPsL//cckf8+uROHHE/7L210T7/JrXGPjn399U8w1MOrL572LkqEcglNCwZzS7JjVfBpLXchqW4Thb7ww5oSMggaZzW+cd5ZOIQR2cup8siZbL8q7ojrN4y+ywUfMbdrOIl7V8c6Fg3nrsqd1SR4eH85yYBJe8R3ZN21e87Vp8bR9LjRuf9G+GuxORKD1R5xCmAOiWqxW9i3DhhUt5FX3Q6O25hoT1WFH0+vf51xpojpnJY1xTT4mREftG8mu5ch1VrUjLpKwyLXRXNOnCRQzB9f4qOVuGvscbhzj7GfGkXuXslFV9VlHS9DQEQ6uCrkCfMiGMbYaCidfr1zg8N+4oHghVUqZ/9KzwYMOBhAtfaoTAQ5cRiiw3RfNr4KHDeckhJ2GP8NX8mPYuznpxNraKQi2jfaV0GRCic4dilTLwEdTbRVaB6tWCQBQLHh0a1qm+6cSnvwmj/snNFi6Glgk/NfNH59V/0dmT/ceGvy3n28zfWRTpo2Amn9fbf9sxxxQMcBe2jBqqNMrytHRbUZ9RCt2zyqSOXSo3VnhzjJKzJbocj3yqDbaY/vMNJXrVicrQCWbSX9Zdri7DOXwX2uY3TjKDrW5hzpva9/aeF3l1AqGVr41hQNjWeYSf7uRR4BQrE8mv2cv7GFASYF9xPfH5UovDfmH9e9NPxp9nJTtAYrRE4+Oe62ifNmbyvtBx2ipTb2999Q3nmGOrLDJcKUxJP8q8QI+f9DkBjDnlyI9S7kQDm5+kSn9NWShUvkx+mbvoxt+76VPNsPQsiE2fFDIdSAHgY5KWSad8ZV7fkiQr+tVE1hwlQ+qORrVkfa5G6g6Pg9p2T3VTTW+nfTPulRE7IY/DcZqq9m2eesnr7XhkRmQYJYcqDqN7Q1W9bRymGq55nFU6o0UVXkn0RZ1QUDuWawR2pLSp7s/SGs57gc0TKspRlX0RmHPu9FHS/xjFBkti7W4ljOTFPgqAPNWp2x7VEGhHBSgMSsHpt2PmirMOOqb1WaycKX5sate5BihTq8JTyr9QYypzLWXiyAZARmFP6nQeYsaBTKVteeKNnMVQeH6jXtEAH+0AsGQ1KiCgbTUbp/R02q/5IghJMy6zqugLaJotlia1qLtA1uC+1keCo84V+MtTnE5nEIIMgyKVLN7kSrTcG1UtqQttjRLpR00TP/iVJH3mcGQZGdomn1YC/zenRyJdQhB0WMmx2zqhFfy0gTKUTETcVIDI+JSpjDtSqjOl4OebEpALGAmNiJSR0n1BAWU8fK3hErYmAI0GUMz35VzB7aTuCw21jsrPH490U68MlK1hbOWufr0jcWX8w9NXlcSDdOcrfFnZVkqHyU8hLE/iFkBPJSlTDcw0qvKs5VjpmLZJ94au9lfz/k3RUvmF0vcQ7BBlQzYl9RLMRo2BImJyV5bAO/CcvsgigRlOlaw3XpiT9eRB0zaFj79zO+t8eeKEnJorJrwFTn7dTSxz39hf8yPK5CrXsD+YvZ2zfcb61DUYZoRTgncJX8cDqZS31XGumj+zK9ISl9EuYNHrNBdDyen35vnnTXkxGTpActPe1MPVRF03JUzD8vX/3hcmTbP+P1XjF+uhZVF+/4kb6k5d2WNh/mX2mXnwtlSlm7TGcZN8yYJ1IwV32jmzmZHk0i56xwUHhADcgXvo+yMP/3IlWcD26wqV1qirFW9Qfm0J4Jp1Bw4QtmfVFrChcuCQm7/jnsdCguo+H45xnbGMs5/a9P4vsEuCUdTKPfdGOWbIx/hRgw/dmTeKRbU15tqZDqHrtoaSKvbi9lG/5vpGjOkPzdjUQKcYiDlqDWRMksmRwlzRrRsx5i3M7kPRaCWynPh6Nb0eLORb3KXvaLRb3wyGEjE1G6gFyyfgHBQXUttbKssF5El8HLRaCiIu9zBDwxkoUS9+K5EDsayUoV1WKZPxq1fVyH+QoTpFp9Ld0m1hlyvNlID1YZDpnKVe2k9sUEbHSD+cAzBSfKFBmIcFTWT60jP2wqwlw2UmPzQGGkqYe5WcceuNtUddbLn+9D5wqxPKSb+Cczi4F55xTHOrxRxAmIbCYu6O59DyG7Pbd2aSdnHG/Koy62tRQhuf7L5vHv89PNdOOGDvXK/GTRBLRF++UUluizG7Yo7GLXAvZPk838WNtuv7Av92yCcbDXvCt3Wc1giuHH4HToynxnwvbEjV/1TvuLDeT44uQ6cT5j70bZjW0RC+fqyWDp2BbsaETOH2GKULjEb4q2bIU/5Cy+m0wK/zMX/7ijCnmoG72OOZ7zDxxJnPYXtpHVV4dlgVBLsuTkkMOuo3BkoLH+uJtdeh91udCEpS2NKRDE+1Th74RJRPID7z5VROfQ87SuPi+BL25iL7Kg7LmNO04XOj0vNbtT5t2Zmo69yiTEf57i6k7gpxHextAjy/ENwGJju3PyyrZYEb2vyZEr2T4zzPpEnSxpRCRJcOGQsJh5KrxMDAuy812Xo0/22Htf3NW26GHE2dZJ5jt9BThk/Wep6XRN4JN/2e56pdy7nLkFAuwTYGnoo4asjEMZln9smTblDGX6vvueuCWJ1WAOaREIbbxy/EGEzokuKoPolq8da4cCuwEpQpv3jGlGXnC5dB0MhofCChRIsWcoTHU70C3bcihKiOoIIJLbjMxDQ1S0pfso8bRGT5a/kbwvtytKGMyi7HaVZI8zyy39G3W/1GPBZHjMDZ9YSGVgKsqwZpkHxU8smWNIa2zEQ39ojwsGhKtuDMYRgRWGEtxSzxOYsqAQua7HXZiK1DQuVusOxuBK2mjKO2h2WYR1h0FDFB/eqICOWyq/skWkeYt8m6PC2M55y8l2Pfv7N+GTedmuOz+TKdiV8JcVGjEqOGtbY5w2DA/OQz6jWTOO2kKkKYlozfgRcNbILNOlsBJYqHEGi8mCsWogI9dFRMylGT7QnI72Jx2FzJSUIAGKAzkKf+oTRVTRljas2Vmi4+ewsEgo0BOsM1vLUHcPZSxtGgJHF8xDqOiVR+WRG2/WIvCEoZ/EgQF1M+jkrfP0f+YvTg1vop5RngoCguKiwDxayxyTpfDI2lHlnS7ObUYikDPMUu4cMoJBr+EQOLLjGtLCWU80qhx1TOf+BlVOveylPGywNa4jRCy+LS8ffnCNHpZ9Zq2DacAKAyxAWu8Oh28fict9lKQS8t2THXDJ2IDFA9XSzULBrEczUy8iDEBo85WDhxWxwgGYCJam4DlIdGemVsriKNgVp4Pj8LpxXRhw+tS8iHO+/6BiSVdK7oKPJlNaGpA/ByohQ7hRnnQcr8rxeRtO3J7ZKZHsK6WzA0Nlvd1kd0CROULcREFpWPkon3nbT6TarZ0jJc6aX3pfCteD1YSsPUS/bbJYW9Z8ukW4yBi8qltJwIMPKz59FKaAEDiXIZq7c/reYfbtUxgNQ9lpodIuTyhyF0Q9h8OLyCb80uHg1DH5zlUnY811TmLcGhO6uZepVM5eOr64IUlQ5hQMbBtMZo2FZb4XOD0g1a7jXp/E0Ubbvviy2CG6AkFwT7QBECEVu0sJwNVErJ9dx8JBwlWLnDgu/ydJyPbVovzSJ3YKnPt5gxKfdDVjkCLGZx/fq/rb93eebPG0V4ZJT91akFNMSPsR0OncMPp/6dWw5zpzk4oIukeMCP1ivycS7CqXCB2Ol6JwioqEUrBQbJENKp6DKiF7nHav6qPfm1XoeVoLb7nPfbl04IH8QH6vkiTFA7ylm3kgzDCRlNJttqCqLKtqdmBcPagMhoYwKdDiIpRURsjK/5U9q1JWjYoR0S2jYi1GiCsKRf8fI/cyBcQguTPbZ1wdUWCRGX6zZv+E8OXYmeQ1OZZbMNOgW9BCC8VAg6idDQXoc4XW4oOWmbIEJlPSEiWhfYQnoFz7yU+iCgImbVWsUYJ9e0s97JbfD7zbGK25HrATBqf9GxrXphYv3TMKNqZ8uMrxbXFMVVpk2bjVnU5s1bV+Lw4TO7cLx8UJKFUXkkDUs78yGgPOwzi+0WPRoG0wVngwInr5sAHI4UAm5RlQepkbhIeolc/ApPmNN6yFKEN5sHkKS0VMgw4J69ZNGqdrNNNLC7NHkjFtLQc1F/fRa5IvDS3v9aDvRueZAmJO6mrv5pfdeud2q1hflWmzgxlLHvR/HxTEQ777HWCU6ny+4WKEpT6OTKQTXOGM1ZgZgKIxzP3EeEBQxXU3O9BviFPIu4GoqMQuB9tXAMGk87nKj2kPV4gTIXF9tRsY0CjMjfS3UlM2nvW09PiAbjkHyA/wLUJmJ+QoxYTSx8i4fE5rlRqOGPmqUWqquic9v9tkPzwDFrJz5MJHUR82T+T9EnYDKRLvxJCnNvO+1LrylouZvIgWyn2atMAuVIMOUDoWgj4TwQPaMLBRBapVamzeTubo7L6ahTPZ1S5d+yNQdGrVcrlRzkkKhUashtYBrbCNszgMn0TAHGaFSDSdCjZBIeDWbdAmDdrSXxmPLMIUixPGz0eQyBokGfhkWo5qu18+/cU5WfTTbWe7rgp3weWtr4crQCsV0ovbUu2aEhV9KtNPe2g/yVoLF1ERVLWFk5mq4mVnNmKW2bbxbGphTFgtfJ3s5+6LFVHt1i6t5ETEttN4qOh/aDv/4DZ0ogB3Gx8J6FL/53npnY9HrP6rat3bj5steViWXY0Grjx7cvH7rmVVtjK9fRge2JzP38aISKlGZNmSV6KBKtISSBgbRZFQcSnDPm/4ZQhieOrifoUL3tU+99sKGV3doTf2IGaC6m9plh7c8FU5/IDJXVJ0+QiW8SclUT9NlPIwuj/jiE46zyBY3QcuH5Oxqi9U8fiSyBMST0JcaLRw4cMd8K1ZNDiXFMsPLmPAeB5SGeY1Y+hCFJaGycBFlq7I6u2XGIwcIRW6M+VIkiqBheBKj95iWE8SP7I+hESPApXsX3jrJaDqSCVPEdQGFsI6H/23LqKQrEwddSBQzBCJ0YeGLBmj0R2HlfxMRy0bldSccSb2hkZ6CFIxvTS0iCAwmR9wLmjLJUw07laOnIuXwHj7OpcxtJ+toyKVcM9L3CJqv3IniV97qqUJz7YQQT3RFRLGk2YpLJom3SwoSC9emVKKfrJcDdqLT5KpwQLV9z/PCbKU/3a0FlhNq7S7uAxbNitUB7W8c4JiZyqgpsSmBFGReURk1JgbXZF5VGUragPk2bRH2trts7VOGQpt78d7mA3J6Ca9fE3qFvn4dxliR4cvNjwk4TqD/MY4LcB5xrGjsmwrdeuvWViR0QYjhVmCwV8sZ5wjVQsK408vOlTnOEWBqAXM2znMuDQvl5wzGA4TDwyoFIYn4pWBgIlr+9OmO4nhFcZPJS1F+OcUHsYkJLKWDC4LA5OLcneDwscO9MllmJpE4n53fTcv+TQyjGsOamgVXw8mHyb6nalJs1a/p7BQWfChqP7c41HRxBBkjF9Y61RaaFBY7FReG04dy9z3dlztk+4NJZ/0Afhdx2cMPpidKzpScaH360KCq39VkWNgpFIIQvrvg+eLl1aWve73VnXP3CE+8CYsRk/qCPO0uOkQlJoXbhReXOM158u96sagO6tIXN2xFtEMSuFH2zYpwanr6JCHyEdHSkvDIHkmqqEhCIpehe/agy2D+hBwQFNm6989mr5C57kcsk/DETUsWgwEmrROtkr4mQ+eu9/qnK8hWFMG6HQcwIO57OraPsBd79pi4FwFswLEbe/MmFskvQLI/Nj9fQmbP0FiShUwls4RqjQot7+oqR1V7sNFR2z0q0nRAwDRJZaG3bNnWU6px9HUD8+lOwOVsdtZI0daesXU2662+lQfW+Nf5UpYuKkpio6XxHO5Px3FH1eQi2JIZcoTfV1/fxz8SkrkFFpNdC+LjC1whALQ/U1T3dva6i4dGNTSiGnmieoKfrR6Pu9DfvN/b6Qr6Fmb/7p1PYubdeOk47+zVBcJoM3NdXIArprbQ+xTfGDdfefJCGn1x2VrKpg1dW61Xi13gICDUDWVxIRsex/hDaERFyqYNzcnOOWq3hX94eXa8MktJXBzusBF9CDrNGBHHiWPjvoLIilq/yWR9/X19BUMIgT4uxA7uL3NtRbYRi8AeuYgtWuIr2j/IAJ5FrdA190vul9E/vvwhki9WjpMcSc+N9+QpFpP/VbuNJ40djT8B+YP2Ca+a9PUIYzzh2szn4OA2w0CxhQlLKuFiymf/dB+IMf5kHLP2TJd9PH4iSCwO7Mfj7RoK/aZskhMSF9pM+S29cFxomtqpck60HJAEB0sGLROd41kRaYf8OOecBgMS5YNO57iLMnoLnGsHP25GE7ZOM0xLBt1tGps8bEDo0dgo5IMN+Msju5lNDPE4n9ndUzGjyZuuFg2L4nwOXDO7doC457TFuusUGeV687FT8OFjt1bb7fTIxaWnh+f+st07w4OBKdLT9ZvW6Kelu77TWpno03+Mjbm40E3wnG5vGrO7t1D/uOEysl3TaLVnkcTrvqcXx9PzvpcnxwuStIg8Ny8G0TpoB0Su1V3IQ1ta0JTyFoQYuwHg3VXc0vPS/KVFT/Oh9SZTeuv1dM+6isJCQcGru1PgxicK8/Iktid0Pza0zMyaOdk/6Tx5UubqSCot1XOd9aSTc4ulR33Ytk73I9px7fNT+QH5gVb1hNbDe60a0fWGZ95rXg9tWBfhJ9vMfZdKKg/tnwSdqUpfrQC6joFr4W+UrV9fWMLUvvXugtZMECr38owZE7qpmFw2d/JZs5JUj6EbctZ9dg25ZMWHcZcH/a8T8OPrpCYLIXXXpzVMTiLb4yudfIbGf6re/PXL2M3z1mXPmLQzpLF/Dbd7cBDhksT1AsIZyjPkVhiDjYYzkZuU8dMYTltansyRvHK+EE1/mzEA1sHFzT1mL431HGx2MlmbS6zHWcRRyEE3OfV5O3Zcpt5NuPXg71YRpwICpogqC1JVVZbFEoN6AqneIOe4UUDgfPcIlcqwA3gznSn82C6fAz39x6TbIoTILUBUNzpU8hOQIsvsaTnb/MveIZWwskq0RH+OjNxnQNULzijY6Xq7SBzsD0RlJa1SPCF/RjosUo64jCjD5eMu43LPXuKwEv8K++XPXJ5z93O6uEr1Oe448QFx3HUkmn7HHec+COIKp/ox76sR3MXaD5//+bOqVFS8eo66bzUv2yXdbVF4Vlnx3wkH90WEZyjnJir8s9K+fQXfmhqYHdPOy4mI3boI4gSz5bQ843xjmps8dnYKsj8sFs+pdJErPd3Iy3lY9qfAgtlu+zdiks8Ft5g4QeqBsFheTvvsGI+8INu1dg4BtuIeC3QQsy22WwtzH2g75YMu57idau5tl9tcdSf3nMsgmGpbp4V0ISLkjWgRlO7BQBnDayTkdIi+WKxstXVYfDHdixlPr+rAA24TEgi3AzqphP90s7F5CvP6c8W+kgZ/RuLb4Cte8+KIMNQIBkTUhcx6wNSQEAAlME72ZH3Zpp8dL6Vpqup4vaj+o/E6y3X3u1vXE2/3GTczm5vfNLhl9Ppzh11uy1Xy2y7DXI0pVnv4cC1agwq47oDVHO4FH0A21r8lsRNhmtQlb9e/bGrwxEZD4nQxxg2PhiHp6UhKYQCGQ5mnUGvbMrMAchA9kBJoFoJCwHzQ+atVBbqScm+NqCYHbbBtQIGQs35dDky771nJ2t2v69wdZLjHffpY0sqmNy6g/FwhcjvpsaM6gDhuzKqkOP048pJKROGAHe8yeuOkTEnR1GKREa0KJIb368E3Iw4javW43aDds9X17EGrEqlwEsxOAz5uOG3cASPEW/bjLR0P7tk8H7HffA0zjQ3l9qsTjd1Sve3vjS1L56t2pK5BkvLiRryDxAn5KaTm1N05jhfso33Pzs1IlW91C3Rt1LoGzm6PSc30H/STE4zme6amuMhhbqvNBuUnIjXXYqPeimbLHSuo9z03Z119dDc3eyzPPaDX9HbrOcOn+nXzWHfVswi88VJoS8+zutDg2xUVH5XX5XtOF3iltE5/t24E3VClrUz29OmTpyEyPj+xefd8+QeWky3m7GiFHWl5MwXD4uNExkfaY7YmZ8XG9Z6NeZP1FwKoX4pDB66/x9/fCBu6GovtwtJBpzH+QlA2biqP95iHoALg73ifhifZ8ZLS3iNZDQ0qNAstL3frrriH2vLL571HVv9yud50ZeXKBOd9Iavfz7MtZ4HWvniPXjA83dQx8yLqFXn9uZJJx7u1P5+8Tio2+mJeWdJA3rbICpfASw3vlstA0IDLrQ4cIhx0OwMZJUhbbc+EtPPXTABSSQ4zCzZOvo7cR8wzqOFgFZC0qe1fz3+VRld+h83xs0IWLrRYD4usQuz1zoXwHq4gnixrmUOIhOklCNJgV4EQNbtOVLMGrqccpGXt6HIHf99MtV9ptp9PJtnBKMGRSp8/lG9mlTO5388kfC8ZGfR/mBPEaji6aavZVnzkei91O633hksO0nFB5AwuZ13wo3OC6ksHeT3qsh76gEzQAz0DF7OzXIrBns3ncm3IaEy+Gd/VWe6KU+1oBwSg9ipQRcAyPa8YrwOjmH+036bLATdDG++gGAPny56jB6/w/PmGRKMVRokNq66UuihwPXuYLSD0cNZzzmH0ZvDHX2vODMy74fbGaIY7Y8glu5Rnhe1YtipoyLZ83lujSe7kLu4xUXhGQEE9+VQ1TMF4x0rc/QfDoepq5lamlrzHz9hvDxmMcfw2j2eGV9hW4HGKy/MGccw8EXSbXs1sZwLi47PQ33kJO1cek8de4jw32QCJys6WNzI33ry5kelOPpFiknKC4r6J+SxlbnQXdDh2CNzl3CbMvhnD3DfefM4OOR4S3CstR460mJSwS3Ou583OdxoLFCvnZ3ImYuPGy93Bv+87f0pMP8WVenI3PD1dMCG0Ek1sEOroDAWVf4oupdnSpPRTAvaNo4uF/IJnBaGhTzgL4jhPpmyt+IvCoA3oImsRfbEi+Pfbn5qS0mfo0inwee9rLCmaoCC4EqSEaFLQRjdfghQTYM3XLWXRTxBgNtcru90IEkIEGeRGiMMisMGb3ARSQjhhkBtBgYVjMr5zhNJFYa4JFygwNyxOIxA3thYikAiadBukD7lxbo/WSjDBKU7D6C3M7B6C3GRseOb0XunlzAxSaVuJIMY8fJDHM2c22jYwNYwDTPDZ1XdHdBkKRVketzvl5wjbCI+8hEu30V05fMchQs4Xr7IKMlkg3EUVWdpa2VqKKCNUkYWdpaeFSEsccQiX2wr3/osA7ebA3T0e/unYkc/sbKJSssDWIUhMxeS0NwcNCP8/vWMb6DRw+kDsJjqSsWTycln94URuYn13dh4lGQOCsv+E4SRn0vDEanC/0XPOc6P+/xufOJ9gngIUBIylva+d2WucGY1IYxTamgT9Cx9giDwj06SBF1ry5InZ17/e9744E1lcGmTkkehhFPRNtnq1jFnb+7C3+kpAJzdRK8kJsK8vzOZ2Buxh5leZVt220thrzBnSxiNg9aVyvWS5x/JlAfZ3guU7/rLnuJ31oR6URqc7ifHDMNz3tjj6ILHyhGbhnOuXCRhGoP8yhsXYic7bJ6L9Qly+3jnPrglwTDtGwL8TL6DZGccT53z6qFnAOxIH8Zmx77sUhoaKXfVyOMJXwADYK+jyLhAKFmct9xTyx5Ev6DNciD9DvyDjO4J4YgnKkohxd7EYM3HhHT8xZlFY6B3tQ0MosniflPHgBV6fvFNUZBkasu/kDoqcELP6sMZ/ouP8KuyAQpzvsNg9SIIyM/vDxRKmvjnvInjLtb7Wh6int4Ru2Uj4Dd5OUw9Z++Id1AJqR8mzhFsh+rLs+OwExyk+Qi6TskkhMrlcukhiy6TgDVqQy2RcMHOV3DcDpSyECMSQENirMRfvgl1CQzCPMxvbIDFP3UmxocBvY2PDY48fUhkWDKo11YaCUxm9O60p7lR8tztwf+zx+OOxh/cY5gwYT3ixaXo7Hkg9///NH3+tQ8ESQeo+/frCB5mxnDccPOA0MfvL7D+LakOL6v68HCamzPjOWE3Mog/IU4lMr1nv7Ktu9hT8YN1mr2SPD0wjOYJB1j1Ot1M3ZxdmqQrontXNgZX7wb2Ru11wAPAO0nZHJkGQYCuR8hPAUJT1++nV5GmJtdHuemN7mQEiTUmRpoQswOfCwF7m8INPU/x+mjVnCQVTNTZlnUdATf9uLZ1iKOAHTQxorOn8oXh+96dP3fwMF9LWRwP7VuPLK8sZzBzmw2dTpRTq5RtHZOy2v/mVtN9U78TbH/3G/XSzS4IjqKeImbOXKGAIYgxDHKFN1pL/Nu9tS96bX0626VZxVsleQ9ZptiHz9h07jT52uswVSbiX3zY86r7d+WpqvZl/dJ/rfvT8ihcHSBaH/m1h11bSDvfccrr26FNOzaNcD/kdl2euIy6fuFaN81ijagg8f2zo4TkEd3Qc8Qo2vDc1C2mvMk6fbZK+tYpUYr4D/jvo3+rT5uuYX1iY7+h7vLb6H/gP2a6OONPaejaClLFdA8ON7mWzsU4nJScnTc/efDi7Nw6DBjv67t2zo9xTm7ifnSZd47/JM4ymaA5ls9ivKvgSiRqcOc2z+tWNHU2xVilWPWL8Vnwq1sLqF8WndEqISQFnLTsyHCl8I4zoEAqzVCaJ5uvoZ45vNqH1TOPjfyTja/ZZAD293FqX4NAGQWFBqKDhjb4vvdkNd/7U0wOobhCEBte60Ip1MHnKpXadI0PHcQmxP4Wr32Wudadkstw6V9kxwrt3wmPHd34y4MCB02fja+rdveNpm0ujUTZ2aiUIxwwUDGPsNg4EFP3th/fhAZsg96S+T+tmnXXgRTw7pOrSc3NH6uNrm8iMW3ThhsQkYhppe6bsCQMx3HRtjGrueOlFk8z+3l0288H8UrsH6IMSu/msB+wXncdfCUW7yoYrhltFfwjhSiq6Dh3f5lEnWtgaxt/SWniFlYsOPHhzcanhpiBfk2w/E7FyySrfNljlGZRhJa1xtQ/61lZoHy8JDJx3QAy4MBTVoLeaXSsEsS0GSG3L0kv0jv5rx7Pe1cw20FvslZ+YWuVVv0zw0/qOkkxyf7e+gB/94Uy7P+FyLapBbq3PsmNBi/4FF9Gw6p0W1+cLQp4PYkT0eLt+KT9qFPxXu1q3VpTcFsrf3FZ42TYXBl6NjjuZ1QtDz1aSlr3Kr22VZ2CmlaTW1SHwW1uaQ7wkYOFNrk0OfQ4LZUvnNo6Qq/XZ2wLu+hx234IeDEMRsnbtPaGQUNSxsRCKCJs6oJD9ajYVQaByVliEfRq4H99Cb2C9hntiogbQ9vfBb+N2JK9s/IxXBnQgEG0nFVxA9B2NQ31MQky1pJbWOVMeUQ+9HJa2th4SScNdFEbdYeykkigH21d3S9dgEZHx+G7pOfaww/DZHe3HVSr4ovsezI6ARubtdp9YefVMnaO+8WLzzkXIihXIIkcH/dSLkK21iE29wh+6T0VNeWyMZHYwESynpeSsmrXrebg/VhNwS7TtWe23M9vV7Sqhh0qnGiH15rCjh7+yEc7X4Si2iB317hUHYb8ajn7x8/aS2z+3zKhnsjIz1ZngN1RcZplIuUlJGOqWdd8suxnRoQS+TuKQrTCRIWOIf6Mx4f50smbS7zOYTXQm4/6g1jZ3bdo8rv8p+3koZ1g2fDtYKpMOB0tkktthMjMnPlrsIfzyLK5kufHazv8t19Cu0TTL/4peY7wmo4iTtlQ9Xv43lzOkOLeuU69Db5JEqOnzjHhO/gJshDgJnYO2y9DR8FS+qZjzjCM2ZaQmh7m02oYtDfOaCl1q1W0VhhsvnWikhlE6RUGnaSJcRCsgDIYi21McsThgQ0CanLYnU8Lnt+1toEusxVO6C5rIWkIvoEmsRVTbfJW8QYu2PXn2mxROpf4aBJ+J7hoU8DYc+PF8wG+i6o16Vaw2qPt9l2G84e623YZe3Qh9fhAH6f+9VgraBdYLtFJoDbKWGJyXgkGcVZyBFAyDqIq11fUUghWBEn0VUTHwqb25okYhtrDaZ5VNVXorSRWkfXrKNy9IFFYeqZJUL68gLfFeQs2+V38vm5rHs0i+iLctEK6sFonmWDPLGWcZFQwXpoq5P6vlzIhjluE083BIODV3LR1nck1M3DBQLAL5qag09ZoHNu5OFOXNsCDnIDzWufmUH+yRZqNr16JqNBucvYQMAqBIvVOGLE5Fgg2UIYNgQFJz1GVlWNHjadiGixdasRbsyoWL0eLxneLjnyb7CP2E95MA2moomJLwJFP8XS5i5918uhSX0p16onXYLCShdOlTET2LhhZubS9G0xgbsEuXTMCh14Pa+8nJ5TQ3WgfMhpaWkZGmywss99CczOL9O4aH97z9r5v9AiHBflwWwULWIDuZdptJSblqem3h0O8wZ2LWLBzQeRgNPy2MM/1GniF/MxXGncZp2LxY/FRbCbqtE02pRADDxSy3+L4cyclBuCwuJ+/qaDRnCXgGM6FX1xLTY3EWi2ddcotIfS6ba/KryVzZ2XjUUtzmhhlhg56gJikl6crBW3ceDXbI+eB/WXpoQkxbSI97HKKJ6Qure6a/U7MqkQikElfjVG9klO6cj1aEK9AIaEI4D2lkqpmNymrC2HeHaBJqClUyYclMoYmppZjSFpIIhUfoPUrKj6mptuxjPSE9N8tvRomhRFpQE+yxRHoIXXx/ikl/cEwCybdphAkm7cF0RTD3Sd1MyEx3zjEohJXDR6gA+gPZA7EeePOXTwUHKsmoTByIyq/ziHrukgABHo2yD4IeuppL18Qg8s7uvvWSsYXN6ICXU5uQfNYdzsZ4Sd+sr+JdRFsrYNQjIeCJbgWfunkPZJBpq23NsJXB3WMHDGsMDh4bzdYbwFsddgkGluiNiu6GB/ASY79oNAGNR/E64xrS6QMpDE4h/5594k0V0fDdCdAXiAYaYaSzyL9TTyUPlL+o75Fxmo0Zb2J8gmdmQ7289UzdvSzdKA38W5eCe8ngtmXmj4LCeEpixiL31KGgdJcgPLxqs4PuYd+sJ3T0NC2Lp6ARTn/5cho7hX39gp22juPF0bJ7CpEQZWoIAjJEqQxpSOoWXDil7q3jS6jh0UH2ht7r+ZRwW8k6HhxjGF+QGO/m60hXa7rQZqejHfvVq8xBWVQ02yyuL2tkSoEr6COMyZIaNH2+lyXEeYOpQEAx47tbay5KDBK5qfMkU94kuFQP5rc6AbBYm52qJVjPq1c9KWELgsA2Iql2DmGzJh3H9dI/ghbt6UG1/q4FCkWB6zIHDe74sssi8EaHqjVaTXyV6XQzOmNg9R5k1gGWwUsjleKR814GO3e4BRbccmpEEx/NyW4tXrVMZWccnP9PsKBCrLzq7VmTc3DxxGjFsGiFP6XsfCvg4qpRopUVMaXRlRglGABM/6qIMy2tZ8JJGVfXwf+3zKkR1YoM0zMz0w3XTWrmbJmAZsJFBHl8GS37eiFRhk6IpXIgQPbdBQuskv2l162zedndywsKsP1aq2zznPArKX5lLgf7+ZVBhexDbG2s3Yqjp7lSpTzDYOXaLGLGqrpMLHONVi8Dy6hsUS6uJd1rFlV4CktuNK800IJnndZa1a22mkfeEybfnBEU5fve9nuET6RPS+9FjoDNR/lmBq1S+vaR51mpu8ut+mNPWDRaTHu8tXkf6Rvh8x//v7TTH2w/RHqzsSf4O5tpS9U99gBTzVfR2fRyfgWza1xZWWm802T2ohDm4R8te9MCK8l0qmlKkPKw1edRzknOqA7bm3Yprdfy8xuu+pvPVoeV81NQCk6unJ+wyyjxJHFsEl9KTSfHEB39bKEsKMkiKSCtl3LA1W8nrbar5LeHNwKvIe0jS2ljnOUjq7hGaQRZv9UjS4e/Dv+yX6Uiky9dohi00AK71v8Ktpx/ifW4t3G5iuStWE2PM1R0eZzPOOpVTlcR+NERf0fIIwZ6bCCLl8WwUdiAd0cBNofhhxujwlbNV9tWzJJQIrfKmPVVDylQyalrn/YPFJgI0ja9oXAPYqTH3Hia7Jk1K8bgIzPaTryoIzKaHNES1cKXLNW/430ckmEDE0xL5gQIxEETv1H5tL33+Bb8e3snbG53wj0bS5t7IAgSw9x72k75He4f3DtymuAMrrER49GdWaPGIyvOC0GXC5WJOrdXmG7qeYBQmf5E35gUDyTvDaquDtp7wkGDe/6Jy8/KhqT0nemCNHY6G+53BYBa4l5hvGzhwmUmFe5SNQSgSeXliY12YjOReVCYuVgoNg8LMh/O4jCzIEmQ2XVeJw9Pt7oegpF0U1IHrUPPtN3U0NTAq2UUdugt0+so3KffSetZVlZOVPIrFwRG0vTbr+1nAEjTmRbB+++f+OUdfd27nht01fN/8uV3diIJ5lTfdPrLXPYX/4ofu+p+PHKuDe/wDL/51dJ0uGYw5dCSPG/rOc1+a7/saYetsMAFPhLe1n/Um3R45vDp8KmxwydPHh47dbilsYefTw1r2eNx8a5/uBTOLrhUU4lQU74A7iSFmgxtadKDvMPzA7rDezS0P7+wBzlXOQOzBqLP/SCEt11dgw+wSPrh4dpnNeFh+iTWQPDEIpdRRhY8gywGennxvYKB5M60zHG7jzvbbxx9KnhWU92oTmSsLm9MT+U7wZ/t2BIgeRY4ZBku6HDsZIeDAhzZ3Y7dbEeD9kN/GrE7HTsERh9/6t0jfizgm2brVS0DI2KdZx0S2d2g0TKHY2Mb4xNo5apGxqr6YcEh25F9+4aZGhkfE4ayrEbmTttuvqAbbwAI1gpVGSTEN54X+04/SMHb7dvxmmm0b2TWbN2jg5XCakojQmQrHcnuTeAviCnRKvQK9RVaaK5fqKfwufSIIMIKMQlWQpB4KmFUGdrOb7+viyhBSNjuYTtBTIg7SYnc71ilrjnPv8470n7v7jKjROw3XhahPm97NagrUn3Pea4peATe3BlVkKdvvLk08q6zdX32S4ewOZuaPDfM4WV2vbXzL26lm41Bp6EP1tQcnrrS4bYM8gZrtL3dAFl6AB9IrRcUwHp4PWoD4hm9kOsxvnlqoLBIBw9Mbd5MHyzUigbpMS5aIqC0nj3OhT00M/tQSEuDUAipO4QiIQgMpVCyIfqFkujguxHmD4l9tZsWnBSeTLiWsHGt14qYQyFR0YNXQ0HFV8G/fn6mkal3zJ4FmIT/A+X8ctj1Srw5x0oeb1PoNKMInhI5OWhSMrOii/3edM28+1sTuDR5nbJrbro+iof6agsu/F1iQJ4bJljy1+wlmvc/j8R2Ky6NWzG489eOruIirydPsI3l4Ox8EOand07mGiWPS/4xcVJ8fC+P/nM6Sx716OHTMborfVW9brbu9+nBNvij/9lYj4I1gxEdzWT4/X6ltPAG+IuTv+88stLvWBWNTx80zLmLtDvF6it9QlOsk7kswZOSt6E0a2ejkzd3fnd/adFsFhV9bFd0tKFRdNTu41FRsM3d27mB78xvSK8EUHhHo7TmYbdS3SG46pAu3h/npNXCwxcg+BJLHi0ZC0Vul5Sejzj/6o+Pac4HD56L9vydfeNGbo6XECSXdtYPUfjU/1OElKF6iseaCp/i4jqqDfWpkMakfHWPWfe0q4nGs+DRmnauodpY2FDXdDVRGZYMalPX6YAFkrdddVQP6oWuC+VCqQcJL4A9yFZ+KPbZNMMHWUrK1UVm+SepghMC9iD+j7fQcu4a06G9V4HCuFd9DyHDhdYym0puksjSvyIQcMz5BGFqinDCgMDwCLaIpPdapoG4wDle7+h7TAFVbb7EKDWMo0cZWiX7E460kqF96BpG6uciS1yhKLWMI0eZmlQPyTNHtMxUuGGSV6BY2NQVuMe6qsp6T2BX00IFlKPbt7NMC4XyoDqEMJzpZ9HJQYRIZX7Q+XmjsiIa/LKWuq0jhTHX3TzWjGgiSH513M2j5W/f8Yjs/95o3mGB7tciHPQSGhYdkGlWBXRjCwGcDIQHEBLF5nze0Lm+T/h4MzyCx6wODmEp4L7LTMQZdjlHEFEfrTRfeW0N5/Osz6W+w4tZ47M+dXR8dvycXZicBRYaZt9d+fCOrh07BlZUx41e7752bdp1+tpVgN81JlN73Ygrcu1a9/XRuIIV3V07urrOKV/0gYdy3uH4k6mpF2WlZTbV0+7FJYc5UQtQXNbGTIGp0HS1AaM1rqu/Wl/YOLdwX+G8wkuFS/0v4d1/b9HqIuzl6n2F+yDtW8XIl9GjwxTdN48/NdqerqLHX6CaUUfbb6iHPuKnBayuMK0g2/VTNWkLg2mjQxAKZtfgug+d8k193/0imJWK8xBAmaF9HRBBsIGAjqDbD5ga1wIJg76RjIDV6LQtbUZApvdHTsnNImWNTehKWJnPyV5Zk7epgbRUDFuXFengfjxk5rqAlR6qqdJoIyrL4AIMSVg0XK7N66cdAQ1PCuhMtirxaUDyP0UUgCgq5JVQINNFP/BJEUwG/TNAvWjOibQABLTX9bioLaOuJEXRD/5dQL/TJyKYfZ3cXwvoG2AezHIv/E4Emx8+maeJzO7Os+Sh4G8AggDqG945fCHf87UryyVg9eS0auXzErC4fm6egK3s+/VrO9MmjbBSgPwz1f7sNTEslCd1PDzZppX1bRBuFrCi6o8EzEX3jyR00rsoXUZuRYPCNyVhnva1AlbW/3qD5+fsOyOCSW1ExgiqK76Ag/MZim1yJdslgmW4OqaPVbWpMkSVF21WXtUfXROdlLQ1ma7TUDWomcki6JDVPDk/9lCb3aOm/0DRDJTdKGCqPorvHIMjvuuz0NJhUVuTGqoTsDpbSo6KzJJltVkwj8cQeVVUVmNTamlFQV+VQdofbSJ1iaVK18Ya6/1ZKWOavRPfHABgA7gs+XgFY+5f+pQAAPxI5kKG7n2VevomyD+RxUFb/DdfWjoU3LfYbdRPLa9fC0H3jR1FztfpKj9B5Qf5It7rnxR6JnU6+wL/vDPK8sXpsi/K2t5JF3tC+q9IpyOgek8Oqt3FEet35s9ZsEFdRfWT8NPubt1/uBLDlYgsJkZsPcwtu3U/2jdm55htkdT9w/BOjN8SvmAhkY2FHBHfxOcvVFRnuRYfp06pvWNs5rO4iAa+OQAcwQRQNHHzfLwqd20TNtDnxLOBAT2wHAjogy1vyGVgByMQDZxAH+YPHOBM9rEvwFIsgBAMAHha5Q0gQHd0AAUqXMCYLDSCCRAaHyAC1yeJxMVBBgwhPrBuhhHYh4Q0RhTBMZ0vr2eKwrOwj+u5f+KyZVCILlb7F9VI+Dw9Pt/v+wMZ1UYDuy0v7vN5Vinn3706mLOcq8pnjP64srd+enqaLzp4jFLejBFFcEzny2v04BSF5+fvo/Cj/xOXLYMqjnnz4P+iGr395+nxWYP/g7DWMV2x2215ccTmMzFVSqz8jhnmICvnKl/qM0Z/XDOY9dMTWmjWJY/FK2W+JIPvuH7r6heTkJKRU1BS8Rdl9Ic215Y66glln9h8X0hlmJbtuJ4fhFGcpFlelFXdtF0/jNO8rNt+nNf9vJ/+IRhBMZx4zXYbiGZYjhdESVZUTTdMy3Zczw/CKE7SLC/Kqm7arh/GaV7WbT/O637ez/f3H47Gk+lsvliu1pvtbn84ns6X6+3+eL7en+/vv2jeXRXFRiryoBhFE/EyWlREtlW8tRUUd3INmRgbFSmncIvmUDNELMi+Ew4Rcm42Q+3uNFOYOkPQuFpb2oGXjHtIKbgY6ORgd/K4hptQxDZmMewUfVPuL23ICXRgsde6oEJ7o4TSXnSzdWRCToHdmkNZuq1mgdRgIj8lnGHLHhz/KLuVzEVf259ScinVnJf9vOW8tzmxVYzeGy0cZPPm0txFYZZ01iXM6NhF0pixA1W52xilVODXQs+icfzie+JZtICTcFeL5DSuhErGu/LTkfQbmsEl41goqtRVuNJjkqWtsh93LCFnP3dQJl4G5BSqEPuQdFuHJHceC+g1mIN6v4HqbXgn16ZmeO2rykoX8sap2ohfqqIZCZ8QtCZaei/dCrxsdV8GdTpmrR4MM0bH1KFCF4rds2qTcfZWaVn9mw6LNf8NtMZc6u5/kRKIv1OEHJwKBmAWb474qZHlMH4OIV6+tpqpawpCkYR9Rk027/wmuvHg61YuDvHaVJd03dZgev+5Lu9rmne2Wb0fHgepOfVROC1z+1Df1VEJOWK4gOGQaJ/GAsR3YmPvR3eTvBX8pkx5QeZ5NGcPNqdo7z6mvgvypoMVyvttM0Q8/bfh4r0Lh3m7IB/6/ZCpRVXRAedN7+U6te2etFPTjQRtjqF+LR/bNZZU+q1ikRsGGJkq5So8g+kbcXhkho94i+whk/mhyAWRh35Wxi3YXtHNeKk/7r+nViCFzTn0LVoR1ClSBXbbixOYyOx91VVcvl4J3CGuBdm/vymPGcxopliRqO27IqfT3GRR6YKZj+ioaE5veEVIP1ganqom43ySrVvODwPjT4W3ckFtFRLJUFFNmDGfMrZqF5WtHsqWnR7iOt+go7b11CRwbBzs2juWmrnLqCAMkTy/Y3GKqFMAo1RUW6q16VtMnWIRx7EAwzIHqn2eVnALdwWUi+FYgdhPWWSYOVDtWJsBCbmnXnbTrGwaYkbQhxlNmMlqT6OtUPHBRfJ48syb9lWM8i8hXvpFHsAYbdhMtSGD+UOFBdvtdbz0VfFGstnJV3yYSRVkJ8vg/Q0A') format('woff2'),
url('iconfont.woff?t=1619149728418') format('woff'),
url('iconfont.ttf?t=1619149728418') format('truetype'),
/* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('iconfont.svg?t=1619149728418#iconfont') format('svg');
/* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-more:before {
content: "\e793";
}
.icon-open:before {
content: "\e794";
}
.icon-recording:before {
content: "\e795";
}
.icon-screenshot:before {
content: "\e796";
}
.icon-share:before {
content: "\e797";
}
.icon-ok_line:before {
content: "\e798";
}
.icon-room:before {
content: "\e799";
}
.icon-roof_placement:before {
content: "\e79a";
}
.icon-on_call:before {
content: "\e79b";
}
.icon-user:before {
content: "\e79c";
}
.icon-wifi_1:before {
content: "\e79d";
}
.icon-search:before {
content: "\e79e";
}
.icon-rectangle:before {
content: "\e79f";
}
.icon-add_to_line:before {
content: "\e7a0";
}
.icon-switch_voice:before {
content: "\e7a1";
}
.icon-close:before {
content: "\e7a2";
}
.icon-return:before {
content: "\e7a3";
}
.icon-calendar:before {
content: "\e7a4";
}
.icon-no_camera:before {
content: "\e7a5";
}
.icon-video:before {
content: "\e7a6";
}
.icon-brush:before {
content: "\e7a7";
}
.icon-video_call:before {
content: "\e7a8";
}
.icon-user_mg:before {
content: "\e7a9";
}
.icon-upload:before {
content: "\e7aa";
}
.icon-edit:before {
content: "\e7ab";
}
.icon-default_screen:before {
content: "\e7ac";
}
.icon-history:before {
content: "\e7ad";
}
.icon-Addto:before {
content: "\e7ae";
}
.icon-wifi_2:before {
content: "\e7af";
}
.icon-full_screen:before {
content: "\e7b0";
}
.icon-inspect:before {
content: "\e7b1";
}
.icon-sign_out:before {
content: "\e7b2";
}
.icon-wifi:before {
content: "\e7b3";
}
.icon-connect:before {
content: "\e7b4";
}
.icon-delete:before {
content: "\e7b5";
}
.icon-circle:before {
content: "\e7b6";
}
.icon-arrows:before {
content: "\e7b7";
}
.icon-company_mg:before {
content: "\e7b8";
}
.icon-text:before {
content: "\e7b9";
}
.icon-information:before {
content: "\e7ba";
}
.icon-close1:before {
content: "\e7bb";
}
.icon-close_line:before {
content: "\e7bc";
}
.icon-camera:before {
content: "\e7bd";
}
.icon-impassable:before {
content: "\e7be";
}
.icon-microphone:before {
content: "\e7bf";
}
.icon-color:before {
content: "\e7c0";
}
.icon-wifi_3:before {
content: "\e7c1";
}
.icon-no_wifi:before {
content: "\e7c2";
}
.icon-warning:before {
content: "\e7c3";
}
.icon-end_point:before {
content: "\e7c4";
}
.icon-drop_down:before {
content: "\e7c5";
}
.icon-mark_start:before {
content: "\e7c6";
}
.icon-mark_end:before {
content: "\e7c7";
}
.icon-ok:before {
content: "\e7c8";
}
.icon-play:before {
content: "\e7c9";
}
.icon-prohibit:before {
content: "\e7ca";
}
.icon-tips:before {
content: "\e7cb";
}
.icon-expression:before {
content: "\e7cc";
}
.icon-starting_point:before {
content: "\e7cd";
}
.icon-mute_off:before {
content: "\e7ce";
}
.icon-hangup:before {
content: "\e7cf";
}
.icon-microphone1:before {
content: "\e7d0";
}
.icon-play_selected:before {
content: "\e7d1";
}
.icon-eraser:before {
content: "\e7d2";
}
.icon-switch:before {
content: "\e7d3";
}
.icon-left:before {
content: "\e7d4";
}
.icon-right:before {
content: "\e7d5";
}
.icon-switch_video_calls:before {
content: "\e7d6";
}
.icon-stop:before {
content: "\e7d7";
}
.icon-zoom_in:before {
content: "\e7d8";
}
.icon-real_time_annotation:before {
content: "\e7d9";
}
.icon-screen_sharing:before {
content: "\e7da";
}
.icon-split_screen_mode:before {
content: "\e7db";
}
.icon-zoom_out:before {
content: "\e7dc";
}
.icon-retrun:before {
content: "\e7dd";
}
.icon-thumbtack:before {
content: "\e7de";
}
.icon-down:before {
content: "\e7df";
}
.icon-Addto1:before {
content: "\e7e0";
}
.icon-jpg:before {
content: "\e7e1";
}
.icon-pdf:before {
content: "\e7e2";
}
.icon-suspend:before {
content: "\e7e3";
}
.icon-recording1:before {
content: "\e7e4";
}
.icon-contacts:before {
content: "\e7e5";
}
.icon-experience_base:before {
content: "\e7e6";
}
.icon-contacts1:before {
content: "\e7e7";
}
.icon-mainwindow:before {
content: "\e7e8";
}
.icon-volume:before {
content: "\e7e9";
}
.icon-screen_sharing_off:before {
content: "\e7ea";
}
.icon-statistics:before {
content: "\e7eb";
}
.icon-3d:before {
content: "\e7ec";
}
.icon-warning1:before {
content: "\e7ed";
}
.icon-smiling_face:before {
content: "\e7ee";
}
.icon-question_fuben:before {
content: "\e7ef";
}
.icon-information1:before {
content: "\e7f0";
}
.icon-error:before {
content: "\e7f1";
}
.icon-prohibit1:before {
content: "\e7f2";
}
.icon-ok1:before {
content: "\e7f3";
}
.icon-stars:before {
content: "\e7f4";
}
.icon-right_rotation:before {
content: "\e7f5";
}
.icon-rotation:before {
content: "\e7f6";
}
.icon-left_rotation:before {
content: "\e7f7";
}
.icon-remove_all_silence1:before {
content: "\e7f8";
}
.icon-remove_all_silence:before {
content: "\e7f9";
}
.icon-all_silent:before {
content: "\e7fa";
}
.icon-event_list:before {
content: "\e7fb";
}
.icon-mobile-start:before {
content: "\e7fc";
}
.icon-mobile-jpg:before {
content: "\e7fd";
}
.icon-mobile-mp4:before {
content: "\e7fe";
}
.icon-mobile-rpair:before {
content: "\e7ff";
}
.icon-mobile-pdf:before {
content: "\e800";
}
.icon-mobile-participants:before {
content: "\e801";
}
.icon-mobile-edit:before {
content: "\e802";
}
.icon-mobile-photo:before {
content: "\e803";
}
.icon-mobile-attachment:before {
content: "\e804";
}
.icon-mobile-classification:before {
content: "\e805";
}
.icon-mobile-end:before {
content: "\e806";
}
.icon-mobile-dscribe:before {
content: "\e807";
}
.icon-mobile-experience:before {
content: "\e808";
}
.icon-mobile-head:before {
content: "\e809";
}
.icon-mobile-classification1:before {
content: "\e80a";
}
.icon-mobile-serch:before {
content: "\e80b";
}
.icon-pull-down:before {
content: "\e80c";
}
.icon-number:before {
content: "\e80d";
}
.icon-radio:before {
content: "\e80e";
}
.icon-personnel:before {
content: "\e80f";
}
.icon-classification:before {
content: "\e810";
}
.icon-group:before {
content: "\e811";
}
.icon-multi-choice:before {
content: "\e812";
}
.icon-text1:before {
content: "\e813";
}
.icon-date:before {
content: "\e814";
}
.icon-task:before {
content: "\e815";
}
.icon-template:before {
content: "\e816";
}
.icon-time:before {
content: "\e817";
}
.icon-timedate:before {
content: "\e818";
}
.icon-notice:before {
content: "\e819";
}
.icon-data:before {
content: "\e81a";
}
.icon-operations:before {
content: "\e81b";
}
.icon-task1:before {
content: "\e81c";
}
.icon-remote:before {
content: "\e81d";
}
.icon-management:before {
content: "\e81e";
}
.icon-language:before {
content: "\e81f";
}
.icon-choose:before {
content: "\e820";
}
.icon-paintbrush:before {
content: "\e821";
}
.icon-arrowhead:before {
content: "\e822";
}
.icon-laser:before {
content: "\e823";
}
.icon-text2:before {
content: "\e824";
}
.icon-eraser1:before {
content: "\e825";
}
.icon-clear-all:before {
content: "\e826";
}
.icon-upload1:before {
content: "\e827";
}
.icon-shape-tool:before {
content: "\e828";
}
.icon-rectangle1:before {
content: "\e829";
}
.icon-positioning:before {
content: "\e82a";
}
.icon-go-ahead:before {
content: "\e82b";
}
.icon-next:before {
content: "\e82c";
}
.icon-last-page:before {
content: "\e82d";
}
.icon-next2:before {
content: "\e82e";
}
.icon-previous:before {
content: "\e82f";
}
.icon-the-first-page:before {
content: "\e830";
}
.icon-preview:before {
content: "\e831";
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"id": "",
"name": "",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "14379530",
"name": "more",
"font_class": "more",
"unicode": "e793",
"unicode_decimal": 59283
},
{
"icon_id": "14379531",
"name": "open",
"font_class": "open",
"unicode": "e794",
"unicode_decimal": 59284
},
{
"icon_id": "14379533",
"name": "recording",
"font_class": "recording",
"unicode": "e795",
"unicode_decimal": 59285
},
{
"icon_id": "14379535",
"name": "screenshot",
"font_class": "screenshot",
"unicode": "e796",
"unicode_decimal": 59286
},
{
"icon_id": "14379536",
"name": "share",
"font_class": "share",
"unicode": "e797",
"unicode_decimal": 59287
},
{
"icon_id": "14379537",
"name": "ok_line",
"font_class": "ok_line",
"unicode": "e798",
"unicode_decimal": 59288
},
{
"icon_id": "14379538",
"name": "room",
"font_class": "room",
"unicode": "e799",
"unicode_decimal": 59289
},
{
"icon_id": "14379539",
"name": "roof_placement",
"font_class": "roof_placement",
"unicode": "e79a",
"unicode_decimal": 59290
},
{
"icon_id": "14379540",
"name": "on_call",
"font_class": "on_call",
"unicode": "e79b",
"unicode_decimal": 59291
},
{
"icon_id": "14379544",
"name": "user",
"font_class": "user",
"unicode": "e79c",
"unicode_decimal": 59292
},
{
"icon_id": "14379545",
"name": "wifi_1",
"font_class": "wifi_1",
"unicode": "e79d",
"unicode_decimal": 59293
},
{
"icon_id": "14379547",
"name": "search",
"font_class": "search",
"unicode": "e79e",
"unicode_decimal": 59294
},
{
"icon_id": "14379548",
"name": "rectangle",
"font_class": "rectangle",
"unicode": "e79f",
"unicode_decimal": 59295
},
{
"icon_id": "14379549",
"name": "add_to_line",
"font_class": "add_to_line",
"unicode": "e7a0",
"unicode_decimal": 59296
},
{
"icon_id": "14379550",
"name": "switch_voice",
"font_class": "switch_voice",
"unicode": "e7a1",
"unicode_decimal": 59297
},
{
"icon_id": "14379551",
"name": "close",
"font_class": "close",
"unicode": "e7a2",
"unicode_decimal": 59298
},
{
"icon_id": "14379552",
"name": "return",
"font_class": "return",
"unicode": "e7a3",
"unicode_decimal": 59299
},
{
"icon_id": "14379553",
"name": "calendar",
"font_class": "calendar",
"unicode": "e7a4",
"unicode_decimal": 59300
},
{
"icon_id": "14379554",
"name": "no_camera",
"font_class": "no_camera",
"unicode": "e7a5",
"unicode_decimal": 59301
},
{
"icon_id": "14379555",
"name": "video",
"font_class": "video",
"unicode": "e7a6",
"unicode_decimal": 59302
},
{
"icon_id": "14379557",
"name": "brush",
"font_class": "brush",
"unicode": "e7a7",
"unicode_decimal": 59303
},
{
"icon_id": "14379558",
"name": "video_call",
"font_class": "video_call",
"unicode": "e7a8",
"unicode_decimal": 59304
},
{
"icon_id": "14379559",
"name": "user_mg",
"font_class": "user_mg",
"unicode": "e7a9",
"unicode_decimal": 59305
},
{
"icon_id": "14379560",
"name": "upload",
"font_class": "upload",
"unicode": "e7aa",
"unicode_decimal": 59306
},
{
"icon_id": "14379561",
"name": "edit",
"font_class": "edit",
"unicode": "e7ab",
"unicode_decimal": 59307
},
{
"icon_id": "14379562",
"name": "default_screen",
"font_class": "default_screen",
"unicode": "e7ac",
"unicode_decimal": 59308
},
{
"icon_id": "14379563",
"name": "history",
"font_class": "history",
"unicode": "e7ad",
"unicode_decimal": 59309
},
{
"icon_id": "14379564",
"name": "Add to",
"font_class": "Addto",
"unicode": "e7ae",
"unicode_decimal": 59310
},
{
"icon_id": "14379565",
"name": "wifi_2",
"font_class": "wifi_2",
"unicode": "e7af",
"unicode_decimal": 59311
},
{
"icon_id": "14379566",
"name": "full_screen",
"font_class": "full_screen",
"unicode": "e7b0",
"unicode_decimal": 59312
},
{
"icon_id": "14379567",
"name": "inspect",
"font_class": "inspect",
"unicode": "e7b1",
"unicode_decimal": 59313
},
{
"icon_id": "14379568",
"name": "sign_out",
"font_class": "sign_out",
"unicode": "e7b2",
"unicode_decimal": 59314
},
{
"icon_id": "14379569",
"name": "wifi",
"font_class": "wifi",
"unicode": "e7b3",
"unicode_decimal": 59315
},
{
"icon_id": "14379571",
"name": "connect",
"font_class": "connect",
"unicode": "e7b4",
"unicode_decimal": 59316
},
{
"icon_id": "14379573",
"name": "delete",
"font_class": "delete",
"unicode": "e7b5",
"unicode_decimal": 59317
},
{
"icon_id": "14379574",
"name": "circle",
"font_class": "circle",
"unicode": "e7b6",
"unicode_decimal": 59318
},
{
"icon_id": "14379575",
"name": "arrows",
"font_class": "arrows",
"unicode": "e7b7",
"unicode_decimal": 59319
},
{
"icon_id": "14379576",
"name": "company_mg",
"font_class": "company_mg",
"unicode": "e7b8",
"unicode_decimal": 59320
},
{
"icon_id": "14379577",
"name": "text",
"font_class": "text",
"unicode": "e7b9",
"unicode_decimal": 59321
},
{
"icon_id": "14379579",
"name": "information",
"font_class": "information",
"unicode": "e7ba",
"unicode_decimal": 59322
},
{
"icon_id": "14379580",
"name": "close",
"font_class": "close1",
"unicode": "e7bb",
"unicode_decimal": 59323
},
{
"icon_id": "14379582",
"name": "close_line",
"font_class": "close_line",
"unicode": "e7bc",
"unicode_decimal": 59324
},
{
"icon_id": "14379583",
"name": "camera",
"font_class": "camera",
"unicode": "e7bd",
"unicode_decimal": 59325
},
{
"icon_id": "14379584",
"name": "impassable",
"font_class": "impassable",
"unicode": "e7be",
"unicode_decimal": 59326
},
{
"icon_id": "14379590",
"name": "microphone",
"font_class": "microphone",
"unicode": "e7bf",
"unicode_decimal": 59327
},
{
"icon_id": "14379591",
"name": "color",
"font_class": "color",
"unicode": "e7c0",
"unicode_decimal": 59328
},
{
"icon_id": "14399019",
"name": "wifi_3",
"font_class": "wifi_3",
"unicode": "e7c1",
"unicode_decimal": 59329
},
{
"icon_id": "14399025",
"name": "no_wifi",
"font_class": "no_wifi",
"unicode": "e7c2",
"unicode_decimal": 59330
},
{
"icon_id": "14492346",
"name": "warning",
"font_class": "warning",
"unicode": "e7c3",
"unicode_decimal": 59331
},
{
"icon_id": "14492347",
"name": "end_point",
"font_class": "end_point",
"unicode": "e7c4",
"unicode_decimal": 59332
},
{
"icon_id": "14492348",
"name": "drop_down",
"font_class": "drop_down",
"unicode": "e7c5",
"unicode_decimal": 59333
},
{
"icon_id": "14492351",
"name": "mark_start",
"font_class": "mark_start",
"unicode": "e7c6",
"unicode_decimal": 59334
},
{
"icon_id": "14492352",
"name": "mark_end",
"font_class": "mark_end",
"unicode": "e7c7",
"unicode_decimal": 59335
},
{
"icon_id": "14492353",
"name": "ok",
"font_class": "ok",
"unicode": "e7c8",
"unicode_decimal": 59336
},
{
"icon_id": "14492354",
"name": "play",
"font_class": "play",
"unicode": "e7c9",
"unicode_decimal": 59337
},
{
"icon_id": "14492355",
"name": "prohibit",
"font_class": "prohibit",
"unicode": "e7ca",
"unicode_decimal": 59338
},
{
"icon_id": "14492356",
"name": "tips",
"font_class": "tips",
"unicode": "e7cb",
"unicode_decimal": 59339
},
{
"icon_id": "14492357",
"name": "expression",
"font_class": "expression",
"unicode": "e7cc",
"unicode_decimal": 59340
},
{
"icon_id": "14492358",
"name": "starting_point",
"font_class": "starting_point",
"unicode": "e7cd",
"unicode_decimal": 59341
},
{
"icon_id": "14492359",
"name": "mute_off",
"font_class": "mute_off",
"unicode": "e7ce",
"unicode_decimal": 59342
},
{
"icon_id": "14492360",
"name": "hangup",
"font_class": "hangup",
"unicode": "e7cf",
"unicode_decimal": 59343
},
{
"icon_id": "14492361",
"name": "microphone",
"font_class": "microphone1",
"unicode": "e7d0",
"unicode_decimal": 59344
},
{
"icon_id": "14492362",
"name": "play_selected",
"font_class": "play_selected",
"unicode": "e7d1",
"unicode_decimal": 59345
},
{
"icon_id": "14492941",
"name": "eraser",
"font_class": "eraser",
"unicode": "e7d2",
"unicode_decimal": 59346
},
{
"icon_id": "14499548",
"name": "switch",
"font_class": "switch",
"unicode": "e7d3",
"unicode_decimal": 59347
},
{
"icon_id": "14500885",
"name": "left",
"font_class": "left",
"unicode": "e7d4",
"unicode_decimal": 59348
},
{
"icon_id": "14500886",
"name": "right",
"font_class": "right",
"unicode": "e7d5",
"unicode_decimal": 59349
},
{
"icon_id": "14655480",
"name": "switch_video_calls",
"font_class": "switch_video_calls",
"unicode": "e7d6",
"unicode_decimal": 59350
},
{
"icon_id": "14655484",
"name": "stop",
"font_class": "stop",
"unicode": "e7d7",
"unicode_decimal": 59351
},
{
"icon_id": "14710869",
"name": "zoom_in",
"font_class": "zoom_in",
"unicode": "e7d8",
"unicode_decimal": 59352
},
{
"icon_id": "14710870",
"name": "real_time_annotation",
"font_class": "real_time_annotation",
"unicode": "e7d9",
"unicode_decimal": 59353
},
{
"icon_id": "14710871",
"name": "screen_sharing",
"font_class": "screen_sharing",
"unicode": "e7da",
"unicode_decimal": 59354
},
{
"icon_id": "14710872",
"name": "split_screen_mode",
"font_class": "split_screen_mode",
"unicode": "e7db",
"unicode_decimal": 59355
},
{
"icon_id": "14710873",
"name": "zoom_out",
"font_class": "zoom_out",
"unicode": "e7dc",
"unicode_decimal": 59356
},
{
"icon_id": "14851468",
"name": "retrun",
"font_class": "retrun",
"unicode": "e7dd",
"unicode_decimal": 59357
},
{
"icon_id": "14870182",
"name": "thumbtack",
"font_class": "thumbtack",
"unicode": "e7de",
"unicode_decimal": 59358
},
{
"icon_id": "15065533",
"name": "down",
"font_class": "down",
"unicode": "e7df",
"unicode_decimal": 59359
},
{
"icon_id": "15072508",
"name": "add to",
"font_class": "Addto1",
"unicode": "e7e0",
"unicode_decimal": 59360
},
{
"icon_id": "15175251",
"name": "jpg",
"font_class": "jpg",
"unicode": "e7e1",
"unicode_decimal": 59361
},
{
"icon_id": "15175252",
"name": "pdf",
"font_class": "pdf",
"unicode": "e7e2",
"unicode_decimal": 59362
},
{
"icon_id": "15183378",
"name": "suspend",
"font_class": "suspend",
"unicode": "e7e3",
"unicode_decimal": 59363
},
{
"icon_id": "15183379",
"name": "recording",
"font_class": "recording1",
"unicode": "e7e4",
"unicode_decimal": 59364
},
{
"icon_id": "15195315",
"name": "contacts",
"font_class": "contacts",
"unicode": "e7e5",
"unicode_decimal": 59365
},
{
"icon_id": "15195316",
"name": "experience_base",
"font_class": "experience_base",
"unicode": "e7e6",
"unicode_decimal": 59366
},
{
"icon_id": "15195612",
"name": "contacts",
"font_class": "contacts1",
"unicode": "e7e7",
"unicode_decimal": 59367
},
{
"icon_id": "15245624",
"name": "main_window",
"font_class": "mainwindow",
"unicode": "e7e8",
"unicode_decimal": 59368
},
{
"icon_id": "15513312",
"name": "volume",
"font_class": "volume",
"unicode": "e7e9",
"unicode_decimal": 59369
},
{
"icon_id": "15635181",
"name": "screen_sharing_off",
"font_class": "screen_sharing_off",
"unicode": "e7ea",
"unicode_decimal": 59370
},
{
"icon_id": "16309287",
"name": "statistics",
"font_class": "statistics",
"unicode": "e7eb",
"unicode_decimal": 59371
},
{
"icon_id": "16323239",
"name": "3d",
"font_class": "3d",
"unicode": "e7ec",
"unicode_decimal": 59372
},
{
"icon_id": "16350806",
"name": "warning",
"font_class": "warning1",
"unicode": "e7ed",
"unicode_decimal": 59373
},
{
"icon_id": "16350808",
"name": "smiling_face",
"font_class": "smiling_face",
"unicode": "e7ee",
"unicode_decimal": 59374
},
{
"icon_id": "16350810",
"name": "question",
"font_class": "question_fuben",
"unicode": "e7ef",
"unicode_decimal": 59375
},
{
"icon_id": "16350811",
"name": "information",
"font_class": "information1",
"unicode": "e7f0",
"unicode_decimal": 59376
},
{
"icon_id": "16351115",
"name": "error",
"font_class": "error",
"unicode": "e7f1",
"unicode_decimal": 59377
},
{
"icon_id": "16351119",
"name": "prohibit",
"font_class": "prohibit1",
"unicode": "e7f2",
"unicode_decimal": 59378
},
{
"icon_id": "16351126",
"name": "ok",
"font_class": "ok1",
"unicode": "e7f3",
"unicode_decimal": 59379
},
{
"icon_id": "16351271",
"name": "stars",
"font_class": "stars",
"unicode": "e7f4",
"unicode_decimal": 59380
},
{
"icon_id": "16351529",
"name": "right_rotation",
"font_class": "right_rotation",
"unicode": "e7f5",
"unicode_decimal": 59381
},
{
"icon_id": "16351530",
"name": "rotation",
"font_class": "rotation",
"unicode": "e7f6",
"unicode_decimal": 59382
},
{
"icon_id": "16351531",
"name": "left_rotation",
"font_class": "left_rotation",
"unicode": "e7f7",
"unicode_decimal": 59383
},
{
"icon_id": "16523005",
"name": "host",
"font_class": "remove_all_silence1",
"unicode": "e7f8",
"unicode_decimal": 59384
},
{
"icon_id": "16534417",
"name": "remove_all_silence",
"font_class": "remove_all_silence",
"unicode": "e7f9",
"unicode_decimal": 59385
},
{
"icon_id": "16534425",
"name": "all_silent",
"font_class": "all_silent",
"unicode": "e7fa",
"unicode_decimal": 59386
},
{
"icon_id": "17194906",
"name": "event_list",
"font_class": "event_list",
"unicode": "e7fb",
"unicode_decimal": 59387
},
{
"icon_id": "17873275",
"name": "mobile-start",
"font_class": "mobile-start",
"unicode": "e7fc",
"unicode_decimal": 59388
},
{
"icon_id": "17873282",
"name": "mobile-jpg",
"font_class": "mobile-jpg",
"unicode": "e7fd",
"unicode_decimal": 59389
},
{
"icon_id": "17873283",
"name": "mobile-mp4",
"font_class": "mobile-mp4",
"unicode": "e7fe",
"unicode_decimal": 59390
},
{
"icon_id": "17873285",
"name": "mobile-rpair",
"font_class": "mobile-rpair",
"unicode": "e7ff",
"unicode_decimal": 59391
},
{
"icon_id": "17873286",
"name": "mobile-pdf",
"font_class": "mobile-pdf",
"unicode": "e800",
"unicode_decimal": 59392
},
{
"icon_id": "17873287",
"name": "mobile-participants",
"font_class": "mobile-participants",
"unicode": "e801",
"unicode_decimal": 59393
},
{
"icon_id": "17873288",
"name": "mobile-edit",
"font_class": "mobile-edit",
"unicode": "e802",
"unicode_decimal": 59394
},
{
"icon_id": "17873289",
"name": "mobile-photo",
"font_class": "mobile-photo",
"unicode": "e803",
"unicode_decimal": 59395
},
{
"icon_id": "17873290",
"name": "mobile-attachment",
"font_class": "mobile-attachment",
"unicode": "e804",
"unicode_decimal": 59396
},
{
"icon_id": "17873291",
"name": "mobile-classification.",
"font_class": "mobile-classification",
"unicode": "e805",
"unicode_decimal": 59397
},
{
"icon_id": "17873292",
"name": "mobile-end",
"font_class": "mobile-end",
"unicode": "e806",
"unicode_decimal": 59398
},
{
"icon_id": "17873293",
"name": "mobile-dscribe",
"font_class": "mobile-dscribe",
"unicode": "e807",
"unicode_decimal": 59399
},
{
"icon_id": "17873294",
"name": "mobile-experience",
"font_class": "mobile-experience",
"unicode": "e808",
"unicode_decimal": 59400
},
{
"icon_id": "17873677",
"name": "mobile-head",
"font_class": "mobile-head",
"unicode": "e809",
"unicode_decimal": 59401
},
{
"icon_id": "17878347",
"name": "mobile-classification.",
"font_class": "mobile-classification1",
"unicode": "e80a",
"unicode_decimal": 59402
},
{
"icon_id": "17889715",
"name": "mobile-serch",
"font_class": "mobile-serch",
"unicode": "e80b",
"unicode_decimal": 59403
},
{
"icon_id": "19329619",
"name": "pull-down",
"font_class": "pull-down",
"unicode": "e80c",
"unicode_decimal": 59404
},
{
"icon_id": "19329620",
"name": "number",
"font_class": "number",
"unicode": "e80d",
"unicode_decimal": 59405
},
{
"icon_id": "19329621",
"name": "radio",
"font_class": "radio",
"unicode": "e80e",
"unicode_decimal": 59406
},
{
"icon_id": "19329622",
"name": "personnel",
"font_class": "personnel",
"unicode": "e80f",
"unicode_decimal": 59407
},
{
"icon_id": "19329623",
"name": "classification",
"font_class": "classification",
"unicode": "e810",
"unicode_decimal": 59408
},
{
"icon_id": "19329624",
"name": "group",
"font_class": "group",
"unicode": "e811",
"unicode_decimal": 59409
},
{
"icon_id": "19329625",
"name": "multi-choice",
"font_class": "multi-choice",
"unicode": "e812",
"unicode_decimal": 59410
},
{
"icon_id": "19329626",
"name": "text",
"font_class": "text1",
"unicode": "e813",
"unicode_decimal": 59411
},
{
"icon_id": "19329627",
"name": "date",
"font_class": "date",
"unicode": "e814",
"unicode_decimal": 59412
},
{
"icon_id": "19329628",
"name": "task",
"font_class": "task",
"unicode": "e815",
"unicode_decimal": 59413
},
{
"icon_id": "19329629",
"name": "template",
"font_class": "template",
"unicode": "e816",
"unicode_decimal": 59414
},
{
"icon_id": "19329630",
"name": "time",
"font_class": "time",
"unicode": "e817",
"unicode_decimal": 59415
},
{
"icon_id": "19329631",
"name": "time+date",
"font_class": "timedate",
"unicode": "e818",
"unicode_decimal": 59416
},
{
"icon_id": "19340674",
"name": "notice",
"font_class": "notice",
"unicode": "e819",
"unicode_decimal": 59417
},
{
"icon_id": "19525546",
"name": "data",
"font_class": "data",
"unicode": "e81a",
"unicode_decimal": 59418
},
{
"icon_id": "19525547",
"name": "operations",
"font_class": "operations",
"unicode": "e81b",
"unicode_decimal": 59419
},
{
"icon_id": "19525548",
"name": "task",
"font_class": "task1",
"unicode": "e81c",
"unicode_decimal": 59420
},
{
"icon_id": "19525549",
"name": "remote",
"font_class": "remote",
"unicode": "e81d",
"unicode_decimal": 59421
},
{
"icon_id": "19525550",
"name": "management",
"font_class": "management",
"unicode": "e81e",
"unicode_decimal": 59422
},
{
"icon_id": "20764441",
"name": "language",
"font_class": "language",
"unicode": "e81f",
"unicode_decimal": 59423
},
{
"icon_id": "21191371",
"name": "choose",
"font_class": "choose",
"unicode": "e820",
"unicode_decimal": 59424
},
{
"icon_id": "21191375",
"name": "paintbrush",
"font_class": "paintbrush",
"unicode": "e821",
"unicode_decimal": 59425
},
{
"icon_id": "21191376",
"name": "arrowhead",
"font_class": "arrowhead",
"unicode": "e822",
"unicode_decimal": 59426
},
{
"icon_id": "21191381",
"name": "laser",
"font_class": "laser",
"unicode": "e823",
"unicode_decimal": 59427
},
{
"icon_id": "21191383",
"name": "text",
"font_class": "text2",
"unicode": "e824",
"unicode_decimal": 59428
},
{
"icon_id": "21191390",
"name": "eraser",
"font_class": "eraser1",
"unicode": "e825",
"unicode_decimal": 59429
},
{
"icon_id": "21191393",
"name": "clear-all",
"font_class": "clear-all",
"unicode": "e826",
"unicode_decimal": 59430
},
{
"icon_id": "21191396",
"name": "upload",
"font_class": "upload1",
"unicode": "e827",
"unicode_decimal": 59431
},
{
"icon_id": "21191403",
"name": "shape-tool",
"font_class": "shape-tool",
"unicode": "e828",
"unicode_decimal": 59432
},
{
"icon_id": "21192040",
"name": "rectangle",
"font_class": "rectangle1",
"unicode": "e829",
"unicode_decimal": 59433
},
{
"icon_id": "21192041",
"name": "positioning",
"font_class": "positioning",
"unicode": "e82a",
"unicode_decimal": 59434
},
{
"icon_id": "21192045",
"name": "go-ahead",
"font_class": "go-ahead",
"unicode": "e82b",
"unicode_decimal": 59435
},
{
"icon_id": "21192047",
"name": "next",
"font_class": "next",
"unicode": "e82c",
"unicode_decimal": 59436
},
{
"icon_id": "21192653",
"name": "last-page",
"font_class": "last-page",
"unicode": "e82d",
"unicode_decimal": 59437
},
{
"icon_id": "21192657",
"name": "next2",
"font_class": "next2",
"unicode": "e82e",
"unicode_decimal": 59438
},
{
"icon_id": "21192658",
"name": "previous",
"font_class": "previous",
"unicode": "e82f",
"unicode_decimal": 59439
},
{
"icon_id": "21192663",
"name": "the-first-page",
"font_class": "the-first-page",
"unicode": "e830",
"unicode_decimal": 59440
},
{
"icon_id": "21192671",
"name": "preview",
"font_class": "preview",
"unicode": "e831",
"unicode_decimal": 59441
}
]
}
This source diff could not be displayed because it is too large. You can view the blob instead.
.color-block-container {
display: flex;
align-items: center;
}
.color-block {
width: 20px;
height: 20px;
margin: 0 6px;
border-radius: 3px;
cursor: pointer;
}
\ No newline at end of file
.editor-wrapper {
display: flex;
flex-direction: column;
}
#clipboard-image {
display: none;
}
.lds-dual-ring {
display: inline-block;
width: 80px;
height: 80px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 64px;
height: 64px;
margin: 8px;
border-radius: 50%;
border-width: 6px;
border-style: solid;
border-color: red transparent red transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.editor-wrapper {
position: absolute;
z-index: 1000;
justify-content: center;
align-items: center;
background-color: rgba(144, 144, 144, .4);
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.editor-container {
border: 1px solid red;
align-items: stretch;
width: 90%;
height: 90%;
}
.editor-container-full {
width: 100% !important;
height: 100% !important;
}
.not-show {
visibility: hidden !important;
z-index: -100 !important;
opacity: 0 !important;
}
#canvas {
display: block;
width: 100%;
height: 100%;
}
.operation-branch {
background-color: #000;
height: 40px;
padding: 0 11px;
color: #fff;
box-sizing: border-box;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
}
#canvasWrapper {
position: relative;
width: 100%;
}
.cut-image {
display: none;
}
\ No newline at end of file
.input-dimension {
position: absolute;
z-index: 1000;
border: 1px solid red;
outline: none;
background: transparent;
color: red;
resize: none;
line-height: 1.5;
overflow: hidden;
height: auto;
}
.default-cursor {
cursor: text;
}
.move-cursor {
cursor: grabbing;
user-select: none;
}
\ No newline at end of file
@media screen and (max-width: 450px){
i.iconfont {
font-size: 12px;
margin-right: 8px;
}
.sm-hide {
font-size: 12px;
height: 18px;
padding: 0 4px;
}
}
\ No newline at end of file
i.iconfont, .op-button {
margin-right: 14px;
cursor: pointer;
}
@media screen and (max-width: 450px) {
.op-button {
margin-right: 8px;
}
}
\ No newline at end of file
import {ref} from "vue";
export const rollbackStep = ref(0)
export const enableRecover = ref(false)
\ No newline at end of file
import {ref} from "vue";
enum DrawOperationType {
FROM_ACTION,
FROM_RECORD
}
export const drawOperationType = ref(DrawOperationType.FROM_ACTION)
export const setDrawFromAction = () => drawOperationType.value = DrawOperationType.FROM_ACTION
export const setDrawFromRecord = () => drawOperationType.value = DrawOperationType.FROM_RECORD
export const isDrawFromAction = () => drawOperationType.value === DrawOperationType.FROM_ACTION
\ No newline at end of file
import {computed, ref} from "vue";
import {
COLOR_INITIAL_SELECTION_INDEX,
COLOR_SELECTION_SET,
LINE_WIDTH_CONFIG_SET,
LINE_WIDTH_INITIAL_SELECTION_INDEX
} from "../config/canvasConfig";
// 处理颜色选择
export const showColorPicker = ref(false)
export const colorBlock = ref(COLOR_SELECTION_SET)
export const initialColorIndex = ref(COLOR_INITIAL_SELECTION_INDEX)
export const styledColor = computed(() => colorBlock.value[initialColorIndex.value])
// 处理宽度选择
export const picFont = ref(LINE_WIDTH_INITIAL_SELECTION_INDEX)
export const picFontOptions = ref(LINE_WIDTH_CONFIG_SET)
\ No newline at end of file
import {COLOR_INITIAL_SELECTION_INDEX, LINE_WIDTH_INITIAL_SELECTION_INDEX} from "../config/canvasConfig";
import {initialColorIndex, picFont} from "./operationConfig";
export const setupColorConfig = function (newIndex: number) {
initialColorIndex.value = newIndex
}
export const setupLineWidthConfig = function (newIndex: number) {
picFont.value = newIndex
}
export const restoreLineWidthConfig = function () {
picFont.value = LINE_WIDTH_INITIAL_SELECTION_INDEX
}
export const restoreColorConfig = function () {
initialColorIndex.value = COLOR_INITIAL_SELECTION_INDEX
}
\ No newline at end of file
export const textWidgetAttachment = {realPoint: true, payload: ''}
\ No newline at end of file
import {Ref, ref} from "vue"
import {ExtendTextInputWidgetInputProps, OptionalTypeInputWidgetProps} from "../types/TextInputWidgetSpecific";
export const currentActionWidgetId = ref("") as Ref<string>
export const setCurrentActionWidgetId = (v: string) => {
currentActionWidgetId.value = v
}
export const wholeTextInputWidgets = ref([]) as Ref<Array<ExtendTextInputWidgetInputProps>>
export const addNewWidget = (v: ExtendTextInputWidgetInputProps) => {
wholeTextInputWidgets.value.push(v)
}
export const removeWidget = (v: string) => {
wholeTextInputWidgets.value = wholeTextInputWidgets.value.filter(i => i.id !== v)
}
export const updateCurrentOperatingWidget = (v: OptionalTypeInputWidgetProps) => {
const operatingWidget = wholeTextInputWidgets.value.find(i=>i.id === v.id)
if (typeof operatingWidget !== undefined) {
Object.assign(operatingWidget, v)
}
}
// 当有新组件生成之时,删除所有内容为空的输入组件
export const pruneWidget = () => {
wholeTextInputWidgets.value = wholeTextInputWidgets.value.filter(i => i.text.length > 0)
}
export const clearAll = () => {
wholeTextInputWidgets.value.length = 0
currentActionWidgetId.value = ""
}
\ No newline at end of file
export type BaseDrawAttachment = {
ctx: CanvasRenderingContext2D
}
export type ComplexDrawAttachment = {
o_ctx: CanvasRenderingContext2D
} & BaseDrawAttachment
export type ContinuousDrawAttachment = {
points: Array<any>
} & BaseDrawAttachment
export type ConfigDrawAttachment = {
configType: string
} & BaseDrawAttachment
export type PayloadDrawAttachment = {
payload: any
} & BaseDrawAttachment
\ No newline at end of file
import {ProcessCycleItem, ProcessCycleMapping} from "./HandleProcessCycle";
export type CoreInternalState = {
sx: number | undefined,
sy: number | undefined,
ex: number | undefined,
ey: number | undefined,
isMouseDown: boolean,
currentDrawOption: string,
currentDrawAttachment: any,
currentImmediateOption: any,
startProcess: ProcessCycleMapping,
stampProcess: ProcessCycleMapping,
stopProcess: ProcessCycleMapping
persistedStartPs: Array<ProcessCycleItem>,
persistedStampPs: Array<ProcessCycleItem>,
persistedStopPs: Array<ProcessCycleItem>
immediatePs: Array<ProcessCycleItem>,
isHandleImmediate: boolean
}
\ No newline at end of file
export enum ProcessCycle {
Immediate,
BeforeStart,
Start,
Stamp,
Stop
}
export type ProcessCycleItem = {
func: Function,
attachment?:any,
name?: string,
persist?: boolean
}
export interface ProcessCycleMapping {
[key: string]: ProcessCycleItem
}
\ No newline at end of file
export type ComplexDrawHandleFunc = (
sx: number | undefined,
sy: number | undefined,
ex: number | undefined,
ey: number | undefined,
attachment: any
) => void
export type DefaultNoopFunc = () => void
export type RecordRelatedFunc = (shouldRecord: boolean) => void
export type GenericDrawFunc = DefaultNoopFunc | ComplexDrawHandleFunc | RecordRelatedFunc
export interface OperationRecordTyping {
[name: string]: RecordType
}
export interface OperationRecordAlias {
[name: string]: string
}
export interface VectorRecord {
sx: number,
sy: number,
ex: number,
ey: number,
payload?:any
}
export type ContinuousRecordItem = {
ex: number,
ey: number
}
export type RawRecordItem = {
name: string,
record: any
}
export type PointPayloadItem = ContinuousRecordItem & {
payload: any
}
export type ContinuousRecord = Array<ContinuousRecordItem>
export type IndexRecord = number
export type RecordTypeOperation = number
export type GenericRecordType = VectorRecord | ContinuousRecord | IndexRecord | null | PointPayloadItem | RecordTypeOperation
export enum RecordType {
UNSET,
Vector,
Continuous,
Index,
Immediate,
VectorPayload,
Revoke,
Recover
}
export type OperationRecordInternalState = {
operationRecordTypeSet: OperationRecordTyping
operationRecordAlias: OperationRecordAlias
operationRecord: string,
rawRecord: Array<any>
}
\ No newline at end of file
export type ExtendTextInputWidgetInputProps = {
id: string,
display: boolean
} & TextInputWidgetInputProps
export type TextInputWidgetInputProps = {
sx: number,
sy: number,
text: string,
width: number,
height: number
}
export type OptionalTypeInputWidgetProps = {
id: string,
sx?: number | Number,
sy?: number | Number,
text?: string | String,
width?: number | Number,
height?: number | Number
}
\ No newline at end of file
export function clamp(value: number, min: number, max: number): number {
if (min >= value) return min
if (max <= value) return max
return value
}
// 创建唯一标识
export const createUUID = function () {
return window.crypto.getRandomValues(new Uint8Array(8)).reduce((a, v) => a + v.toString(16).padStart(2, '0'), '')
}
\ No newline at end of file
export const deepClone = (o: any): any => {
if (Array.isArray(o)) {
let cloned: Array<any> = []
o.forEach(item => {
cloned.push(deepClone(item))
})
return cloned
}
if (typeof o === 'object' && o !== null) {
let cloned: any = {}
for (const [k, v] of Object.entries(o)) {
cloned[k] = deepClone(v)
}
return cloned
}
return o
}
export const unifyArray = (o: Array<any>): Array<any> => {
let clonedArray: Array<any> = []
o.forEach(item => {
if (clonedArray.find(pushedItem => JSON.stringify(pushedItem) === JSON.stringify(item)) === undefined) {
clonedArray.push(item)
}
})
return clonedArray
}
\ No newline at end of file
<!--主要思路如下:-->
<!--1.该组件在几何上限制在外部容器内,不能超出-->
<!--2.当输入内容应当跨行之时,需要自动换行,高度自行填充-->
<!--3.为了解决在文字上绘画遮罩的不连续问题,当该元素获得焦点之时,隐藏真实canvas所绘制的文字,但仍旧保持记录;若该元素被删除(记录删除或者手动bs删除),重绘canvas并且去除文字记录-->
<template>
<div
:contenteditable="enableEdit"
ref="inputElement"
v-focus-mounted
:class="inputElementClass"
:style="{
top: currentY + 'px',
left: currentX + 'px',
width: currentInputWidth + 'px',
fontSize: currentFontSize + 'px',
maxHeight: currentInputMaxHeight + 'px',
maxWidth: currentInputMaxWidth + 'px',
minWidth: currentInputMinWidth + 'px'
}"
:id="props.id"
@click.stop=""
@mousedown.stop=""
@mousemove.stop=""
@mouseup.stop=""
@mouseleave.stop=""
@input.stop="userInput"
@dblclick.stop="setEditableWhenNotNull(true)"
@blur="setEditableWhenNotNull(false)"
@keydown.delete.stop="shouldDeleteCurrentComponent"
>{{ initialInputText }}
</div>
</template>
<script lang="ts">
import {vFocus} from "../directives/vFocus";
export default {
directives: {
"focus-mounted": vFocus
}
}
</script>
<script setup lang="ts">
import {computed, onMounted, Ref, ref, watch} from "vue";
import {pruneWidget, updateCurrentOperatingWidget} from "../state/wholeTextInput";
import {deepClone} from "../utils/object";
import {clamp} from "../utils/math";
const props = defineProps({
// 外部容器
parentElement: {
type: HTMLElement,
default: null
},
// 外部可以传入的输入文字
inputText: {
type: String,
default: ''
},
// 如下为几何点数据
sx: {
type: Number,
default: 0
},
sy: {
type: Number,
default: 0
},
id: {
type: String,
required: true,
default: ''
}
})
// 深克隆,防止对象属性修改之时影响到视图层
const extractedPropsClone = deepClone(props)
const initialInputText = ref(extractedPropsClone.inputText) as Ref<string>
const LINE_HEIGHT_RATIO = 1.5
const DEFAULT_WIDTH = 100
const emits = defineEmits(['input-cancel'])
const currentX = ref(props.sx) as Ref<number>
const currentY = ref(props.sy) as Ref<number>
const currentFontSize = ref(14) as Ref<number>
const inputElement = ref() as Ref<HTMLTextAreaElement>
const currentInput = ref(props.inputText) as Ref<String>
const currentInputWidth = ref() as Ref<number>
const currentInputMinWidth = ref(100) as Ref<number>
const enableEdit = ref(true) as Ref<boolean>
// 在这里要求外层容器宽度不变!!!!
const parentStyle = window.getComputedStyle(props.parentElement)
const parentWidth = ref(Number.parseInt(parentStyle.width)) as Ref<number>
const parentHeight = ref(Number.parseInt(parentStyle.height)) as Ref<number>
// 每次动态延展的高度
const expandedHeight = computed(() => LINE_HEIGHT_RATIO * currentFontSize.value)
const inputElementClass = computed(() => {
return enableEdit.value ? 'input-dimension default-cursor' : 'input-dimension move-cursor'
})
const currentInputMaxWidth = computed(() => {
return props.parentElement.clientWidth - currentX.value
})
const currentInputMaxHeight = computed(()=>{
return props.parentElement.clientHeight - currentY.value
})
const rX = ref(0) as Ref<number>
const rY = ref(0) as Ref<number>
// 当存在文字输入之时,需要检测是否存在换行,如果存在,则需要及时改变输入容器的宽度和高度
const dynamicConfigureGEO = () => {
if (currentY.value + inputElement.value.clientHeight >= parentHeight.value) {
if (currentY.value > expandedHeight.value) {
currentY.value -= expandedHeight.value
}
}
}
window.addEventListener('resize', ()=>{
currentX.value = rX.value * props.parentElement.clientWidth
currentY.value = rY.value * props.parentElement.clientHeight
})
const setEditableWhenNotNull = (v:boolean) => {
if (currentInput.value.length > 0) {
enableEdit.value = v
}
}
// 当初次插入DOM节点之时,需要将该元素强行限制在外层容器之内
const setupInsertedGEO = () => {
// 限制宽度
if (props.sx + DEFAULT_WIDTH >= parentWidth.value) {
currentInputWidth.value = parentWidth.value - props.sx
}
// 限制y坐标,保证至少有一行可以输入
if (props.sy + expandedHeight.value > parentHeight.value) {
currentY.value = parentHeight.value - expandedHeight.value
}
currentInputMinWidth.value = Math.min(currentInputMinWidth.value, parentWidth.value - props.sx)
let startX: number = 0
let startY: number = 0
let isActStart: boolean = false
const actionEndFunc = function () {
isActStart = false
}
inputElement.value.addEventListener('mousedown', e => {
e.stopPropagation()
if (enableEdit.value) return
pruneWidget()
if (!isActStart) {
isActStart = true
startX = e.offsetX
startY = e.offsetY
}
})
inputElement.value.addEventListener('mousemove', e => {
e.stopPropagation()
if (isActStart) {
const {offsetX, offsetY} = e
currentX.value = clamp(currentX.value + offsetX - startX, 0, props.parentElement.clientWidth - inputElement.value.clientWidth)
currentY.value = clamp(currentY.value + offsetY - startY, 0, props.parentElement.clientHeight - inputElement.value.clientHeight)
rX.value = currentX.value / props.parentElement.clientWidth
rY.value = currentY.value / props.parentElement.clientHeight
}
})
inputElement.value.addEventListener('mouseup', actionEndFunc)
inputElement.value.addEventListener('mouseleave', actionEndFunc)
rX.value = currentX.value / props.parentElement.clientWidth
rY.value = currentY.value / props.parentElement.clientHeight
}
const userInput = () => {
currentInput.value = inputElement.value.textContent as string
}
watch(currentInput, () => {
if (currentInput.value.length > 0) {
updateCurrentOperatingWidget({
id: props.id,
text: currentInput.value
})
dynamicConfigureGEO()
}
})
const shouldDeleteCurrentComponent = () => {
if (currentInput.value.length === 0) {
emits('input-cancel', props.id)
}
}
onMounted(setupInsertedGEO)
</script>
<style scoped>
@import url("../resources/styles/input-div.css");
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment