refactor: refactor uitl for v0.4

This commit is contained in:
chenshenhai 2023-04-15 12:06:48 +08:00
parent 5b5aa90743
commit aaa217ed58
17 changed files with 311 additions and 505 deletions

View file

@ -1,5 +1,3 @@
{
"tabWidth": 2,
"useTabs": false,
@ -7,5 +5,6 @@
"singleQuote": true,
"semi": true,
"trailingComma": "none",
"bracketSpacing": true
}
"bracketSpacing": true,
"printWidth": 160
}

View file

@ -11,7 +11,25 @@ interface ElementRectDesc {
borderRadius?: number;
}
interface ElementEllipseDesc {
interface ElemenTextDesc {
text: string;
color: string;
fontSize: number;
lineHeight?: number;
fontWeight?: 'bold' | '';
fontFamily?: string;
textAlign?: 'center' | 'left' | 'right';
verticalAlign?: 'middle' | 'top' | 'bottom';
bgColor?: string;
strokeColor?: string;
strokeWidth?: number;
textShadowColor?: string;
textShadowOffsetX?: number;
textShadowOffsetY?: number;
textShadowBlur?: number;
}
interface ElementCircleDesc {
radius: number;
bgColor?: string;
borderWidth?: number;
@ -19,7 +37,10 @@ interface ElementEllipseDesc {
}
interface ElementBaseDesc {
// TODO
shadowColor?: string;
shadowOffsetX?: number;
shadowOffsetY?: number;
shadowBlur?: number;
}
interface ElementHTMLDesc extends ElementBaseDesc {
@ -38,16 +59,14 @@ interface ElementSVGDesc extends ElementBaseDesc {
interface ElementDescMap {
rect: ElementRectDesc;
ellipse: ElementEllipseDesc;
polygon: ElementBaseDesc; // TODO
paint: ElementBaseDesc; // TODO
pen: ElementBaseDesc; // TODO
circle: ElementCircleDesc;
text: ElemenTextDesc;
image: ElementImageDesc;
html: ElementHTMLDesc;
svg: ElementSVGDesc;
}
export type ElementType = 'rect' | 'ellipse' | 'polygon' | 'paint' | 'pen' | 'image' | 'html' | 'svg'; // TODO
export type ElementType = 'text' | 'rect' | 'circle' | 'image' | 'svg' | 'html';
export interface ElementOperation {
lock?: boolean;

View file

@ -1,5 +1,5 @@
import '../../../../__tests__/polyfill/image';
import { loadHTML, loadImage, loadSVG } from '../../src/lib/loader';
import { loadHTML, loadImage, loadSVG } from '../../src/lib/load';
import { parseHTMLToDataURL, parseSVGToDataURL } from '../../src/lib/parser';
describe('@idraw/util: lib/loader', () => {

View file

@ -1,48 +1,13 @@
import { delay, compose, throttle } from './lib/time';
import { downloadImageFromCanvas } from './lib/file';
import { toColorHexStr, toColorHexNum, isColorStr } from './lib/color';
import { createUUID } from './lib/uuid';
import { deepClone } from './lib/data';
import istype from './lib/istype';
import { loadImage, loadSVG, loadHTML } from './lib/loader';
import Context from './lib/context';
import is from './lib/is';
import check from './lib/check';
export {
is,
check,
delay,
compose,
throttle,
loadImage,
loadSVG,
loadHTML,
downloadImageFromCanvas,
toColorHexStr,
toColorHexNum,
isColorStr,
createUUID,
istype,
deepClone,
Context
};
export default {
is,
check,
delay,
compose,
throttle,
loadImage,
loadSVG,
loadHTML,
downloadImageFromCanvas,
toColorHexStr,
toColorHexNum,
isColorStr,
createUUID,
istype,
deepClone,
Context
};
export { delay, compose, throttle } from './lib/time';
export { downloadImageFromCanvas } from './lib/file';
export { toColorHexStr, toColorHexNum, isColorStr } from './lib/color';
export { createUUID } from './lib/uuid';
export { deepClone } from './lib/data';
export { istype } from './lib/istype';
export { loadImage, loadSVG, loadHTML } from './lib/load';
export { is } from './lib/is';
export { check } from './lib/check';
export { createBoardContexts, createContext2D, createOffscreenContext2D } from './lib/canvas';
export { EventEmitter } from './lib/event';
export { calcDistance, calcSpeed, equalPoint, equalTouchPoint, vaildPoint, vaildTouchPoint } from './lib/point';
export { Store } from './lib/store';

View file

@ -0,0 +1,46 @@
import type { ViewContent } from '@idraw/types';
export function createContext2D(opts: {
width: number;
height: number;
}): CanvasRenderingContext2D {
const { width, height } = opts;
const canvas: HTMLCanvasElement = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context: CanvasRenderingContext2D = canvas.getContext(
'2d'
) as CanvasRenderingContext2D;
return context;
}
export function createOffscreenContext2D(opts: {
width: number;
height: number;
}) {
const { width, height } = opts;
const offCanvas = new OffscreenCanvas(width, height);
const offRenderCtx = offCanvas.getContext('2d') as OffscreenRenderingContext;
const offCtx: CanvasRenderingContext2D | OffscreenRenderingContext =
offRenderCtx.canvas.getContext('2d') as
| CanvasRenderingContext2D
| OffscreenRenderingContext;
return offCtx;
}
export function createBoardContexts(
ctx: CanvasRenderingContext2D
): ViewContent {
const opts = {
width: ctx.canvas.width,
height: ctx.canvas.height
};
const viewContext = createContext2D(opts);
const helperContext = createContext2D(opts);
const content: ViewContent = {
viewContext,
helperContext,
boardContext: ctx
};
return content;
}

View file

@ -1,5 +1,5 @@
// import { DataElementAttrs } from '@idraw/types';
import is from './is';
import { is } from './is';
function attrs(attrs: any): boolean {
const { x, y, w, h, angle } = attrs;
@ -125,7 +125,7 @@ function textDesc(desc: any): boolean {
return true;
}
const check = {
export const check = {
attrs,
textDesc,
rectDesc,
@ -134,5 +134,3 @@ const check = {
svgDesc,
htmlDesc
};
export default check;

View file

@ -7,5 +7,8 @@ export function toColorHexStr(color: number): string {
}
export function isColorStr(color?: string): boolean {
return typeof color === 'string' && /^\#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(color);
}
return (
typeof color === 'string' &&
/^\#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(color)
);
}

View file

@ -1,366 +0,0 @@
import { IDrawContext, BoardSizeOptions } from '@idraw/types';
type Options = {
width: number;
height: number;
contextWidth: number;
contextHeight: number;
devicePixelRatio: number;
};
type Transform = {
scale?: number;
scrollX?: number;
scrollY?: number;
};
type PrivateTransform = {
scale: number;
scrollX: number;
scrollY: number;
};
class Context implements IDrawContext {
private _opts: Options;
private _ctx: CanvasRenderingContext2D;
private _transform: PrivateTransform;
// private _scale: number;
// private _scrollX: number;
// private _scrollY: number;
constructor(ctx: CanvasRenderingContext2D, opts: Options) {
this._opts = opts;
this._ctx = ctx;
this._transform = {
scale: 1,
scrollX: 0,
scrollY: 0
};
}
getContext(): CanvasRenderingContext2D {
return this._ctx;
}
resetSize(opts: BoardSizeOptions) {
this._opts = { ...this._opts, ...opts };
}
calcDeviceNum(num: number): number {
return num * this._opts.devicePixelRatio;
}
calcScreenNum(num: number): number {
return num / this._opts.devicePixelRatio;
}
getSize() {
return {
width: this._opts.width,
height: this._opts.height,
contextWidth: this._opts.contextWidth,
contextHeight: this._opts.contextHeight,
devicePixelRatio: this._opts.devicePixelRatio
};
}
setTransform(config: Transform) {
this._transform = { ...this._transform, ...config };
}
getTransform() {
return {
scale: this._transform.scale,
scrollX: this._transform.scrollX,
scrollY: this._transform.scrollY
};
}
setFillStyle(color: string | CanvasPattern | CanvasGradient) {
this._ctx.fillStyle = color;
}
fill(fillRule?: CanvasFillRule | undefined) {
return this._ctx.fill(fillRule || 'nonzero');
}
arc(
x: number,
y: number,
radius: number,
startAngle: number,
endAngle: number,
anticlockwise?: boolean | undefined
): void {
return this._ctx.arc(
this._doSize(x),
this._doSize(y),
this._doSize(radius),
startAngle,
endAngle,
anticlockwise
);
}
rect(x: number, y: number, w: number, h: number) {
return this._ctx.rect(
this._doSize(x),
this._doSize(y),
this._doSize(w),
this._doSize(h)
);
}
fillRect(x: number, y: number, w: number, h: number) {
return this._ctx.fillRect(
this._doSize(x),
this._doSize(y),
this._doSize(w),
this._doSize(h)
);
}
clearRect(x: number, y: number, w: number, h: number) {
return this._ctx.clearRect(
this._doSize(x),
this._doSize(y),
this._doSize(w),
this._doSize(h)
);
}
beginPath() {
return this._ctx.beginPath();
}
closePath() {
return this._ctx.closePath();
}
lineTo(x: number, y: number) {
return this._ctx.lineTo(this._doSize(x), this._doSize(y));
}
moveTo(x: number, y: number) {
return this._ctx.moveTo(this._doSize(x), this._doSize(y));
}
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {
return this._ctx.arcTo(
this._doSize(x1),
this._doSize(y1),
this._doSize(x2),
this._doSize(y2),
this._doSize(radius)
);
}
setLineWidth(w: number) {
return (this._ctx.lineWidth = this._doSize(w));
}
setLineDash(nums: number[]) {
return this._ctx.setLineDash(nums.map((n) => this._doSize(n)));
}
isPointInPath(x: number, y: number) {
return this._ctx.isPointInPath(this._doX(x), this._doY(y));
}
isPointInPathWithoutScroll(x: number, y: number) {
return this._ctx.isPointInPath(this._doSize(x), this._doSize(y));
}
setStrokeStyle(color: string) {
this._ctx.strokeStyle = color;
}
stroke() {
return this._ctx.stroke();
}
translate(x: number, y: number) {
return this._ctx.translate(this._doSize(x), this._doSize(y));
}
rotate(angle: number) {
return this._ctx.rotate(angle);
}
drawImage(...args: any[]) {
const image: CanvasImageSource = args[0];
const sx: number = args[1];
const sy: number = args[2];
const sw: number = args[3];
const sh: number = args[4];
const dx: number = args[args.length - 4];
const dy: number = args[args.length - 3];
const dw: number = args[args.length - 2];
const dh: number = args[args.length - 1];
if (args.length === 9) {
return this._ctx.drawImage(
image,
this._doSize(sx),
this._doSize(sy),
this._doSize(sw),
this._doSize(sh),
this._doSize(dx),
this._doSize(dy),
this._doSize(dw),
this._doSize(dh)
);
} else {
return this._ctx.drawImage(
image,
this._doSize(dx),
this._doSize(dy),
this._doSize(dw),
this._doSize(dh)
);
}
}
createPattern(
image: CanvasImageSource,
repetition: string | null
): CanvasPattern | null {
return this._ctx.createPattern(image, repetition);
}
measureText(text: string): TextMetrics {
return this._ctx.measureText(text);
}
setTextAlign(align: CanvasTextAlign): void {
this._ctx.textAlign = align;
}
fillText(
text: string,
x: number,
y: number,
maxWidth?: number | undefined
): void {
if (maxWidth !== undefined) {
return this._ctx.fillText(
text,
this._doSize(x),
this._doSize(y),
this._doSize(maxWidth)
);
} else {
return this._ctx.fillText(text, this._doSize(x), this._doSize(y));
}
}
strokeText(
text: string,
x: number,
y: number,
maxWidth?: number | undefined
): void {
if (maxWidth !== undefined) {
return this._ctx.strokeText(
text,
this._doSize(x),
this._doSize(y),
this._doSize(maxWidth)
);
} else {
return this._ctx.strokeText(text, this._doSize(x), this._doSize(y));
}
}
setFont(opts: {
fontSize: number;
fontFamily?: string;
fontWeight?: 'bold';
}): void {
const strList: string[] = [];
if (opts.fontWeight === 'bold') {
strList.push(`${opts.fontWeight}`);
}
strList.push(`${this._doSize(opts.fontSize || 12)}px`);
strList.push(`${opts.fontFamily || 'sans-serif'}`);
this._ctx.font = `${strList.join(' ')}`;
// console.log('this._ctx.font =',this._ctx.font);
}
setTextBaseline(baseline: CanvasTextBaseline): void {
this._ctx.textBaseline = baseline;
}
setGlobalAlpha(alpha: number): void {
this._ctx.globalAlpha = alpha;
}
save() {
this._ctx.save();
}
restore() {
this._ctx.restore();
}
scale(ratioX: number, ratioY: number) {
this._ctx.scale(ratioX, ratioY);
}
setShadowColor(color: string): void {
this._ctx.shadowColor = color;
}
setShadowOffsetX(offsetX: number): void {
this._ctx.shadowOffsetX = this._doSize(offsetX);
}
setShadowOffsetY(offsetY: number): void {
this._ctx.shadowOffsetY = this._doSize(offsetY);
}
setShadowBlur(blur: number): void {
this._ctx.shadowBlur = this._doSize(blur);
}
ellipse(
x: number,
y: number,
radiusX: number,
radiusY: number,
rotation: number,
startAngle: number,
endAngle: number,
counterclockwise?: boolean | undefined
) {
this._ctx.ellipse(
this._doSize(x),
this._doSize(y),
this._doSize(radiusX),
this._doSize(radiusY),
rotation,
startAngle,
endAngle,
counterclockwise
);
}
private _doSize(num: number) {
return this._opts.devicePixelRatio * num;
}
private _doX(x: number) {
const { scale, scrollX } = this._transform;
const _x = (x - scrollX) / scale;
return this._doSize(_x);
}
private _doY(y: number) {
const { scale, scrollY } = this._transform;
const _y = (y - scrollY) / scale;
return this._doSize(_y);
}
}
export default Context;

View file

@ -1,8 +1,9 @@
export function deepClone(target: any): any {
function _clone(t: any) {
const type = is(t);
if (['Null', 'Number', 'String', 'Boolean', 'Undefined'].indexOf(type) >= 0) {
if (
['Null', 'Number', 'String', 'Boolean', 'Undefined'].indexOf(type) >= 0
) {
return t;
} else if (type === 'Array') {
const arr: any[] = [];
@ -11,7 +12,7 @@ export function deepClone(target: any): any {
});
return arr;
} else if (type === 'Object') {
const obj: {[key: string]: any} = {};
const obj: { [key: string]: any } = {};
const keys = Object.keys(t);
keys.forEach((key) => {
obj[key] = _clone(t[key]);
@ -23,5 +24,8 @@ export function deepClone(target: any): any {
}
function is(data: any): string {
return Object.prototype.toString.call(data).replace(/[\]|\[]{1,1}/ig, '').split(' ')[1];
}
return Object.prototype.toString
.call(data)
.replace(/[\]|\[]{1,1}/gi, '')
.split(' ')[1];
}

View file

@ -0,0 +1,56 @@
import type { UtilEventEmitter } from '@idraw/types';
export class EventEmitter<T extends Record<string, any>> implements UtilEventEmitter<T> {
private _listeners: Map<keyof T, ((e: any) => void)[]>;
constructor() {
this._listeners = new Map<keyof T, ((e: any) => void)[]>();
}
on<K extends keyof T>(eventKey: K, callback: (e: T[K]) => void) {
if (this._listeners.has(eventKey)) {
const callbacks: Array<(e: T[K]) => void> = this._listeners.get(eventKey) || [];
callbacks?.push(callback);
this._listeners.set(eventKey, callbacks);
} else {
this._listeners.set(eventKey, [callback]);
}
}
off<K extends keyof T>(eventKey: K, callback: (e: T[K]) => void) {
if (this._listeners.has(eventKey)) {
const callbacks = this._listeners.get(eventKey);
if (Array.isArray(callbacks)) {
for (let i = 0; i < callbacks?.length; i++) {
if (callbacks[i] === callback) {
callbacks.splice(i, 1);
break;
}
}
}
this._listeners.set(eventKey, callbacks || []);
}
}
trigger<K extends keyof T>(eventKey: K, e: T[K]) {
const callbacks = this._listeners.get(eventKey);
if (Array.isArray(callbacks)) {
callbacks.forEach((cb) => {
cb(e);
});
return true;
} else {
return false;
}
}
has<K extends keyof T>(name: K | string): boolean {
if (this._listeners.has(name)) {
const list: ((p: T[K]) => void)[] | undefined = this._listeners.get(name);
if (Array.isArray(list) && list.length > 0) {
return true;
}
}
return false;
}
}

View file

@ -1,9 +1,8 @@
type ImageType = 'image/jpeg' | 'image/png';
export function downloadImageFromCanvas (
canvas: HTMLCanvasElement,
opts: { filename: string, type: ImageType }
export function downloadImageFromCanvas(
canvas: HTMLCanvasElement,
opts: { filename: string; type: ImageType }
): void {
const { filename, type = 'image/jpeg' } = opts;
const stream = canvas.toDataURL(type);
@ -13,4 +12,4 @@ export function downloadImageFromCanvas (
const downloadClickEvent = document.createEvent('MouseEvents');
downloadClickEvent.initEvent('click', true, false);
downloadLink.dispatchEvent(downloadClickEvent);
}
}

View file

@ -1,7 +1,7 @@
import { isColorStr } from './color';
function number(value: any) {
return (typeof value === 'number' && (value > 0 || value <= 0));
return typeof value === 'number' && (value > 0 || value <= 0);
}
function x(value: any) {
@ -13,15 +13,15 @@ function y(value: any) {
}
function w(value: any) {
return (typeof value === 'number' && value >= 0);
return typeof value === 'number' && value >= 0;
}
function h(value: any) {
return (typeof value === 'number' && value >= 0);
return typeof value === 'number' && value >= 0;
}
function angle(value: any) {
return (typeof value === 'number' && value >= -360 && value <= 360);
return typeof value === 'number' && value >= -360 && value <= 360;
}
function borderWidth(value: any) {
@ -37,19 +37,26 @@ function color(value: any) {
}
function imageURL(value: any) {
return (typeof value === 'string' && /^(http:\/\/|https:\/\/|\.\/|\/)/.test(`${value}`));
return (
typeof value === 'string' &&
/^(http:\/\/|https:\/\/|\.\/|\/)/.test(`${value}`)
);
}
function imageBase64(value: any) {
return (typeof value === 'string' && /^(data:image\/)/.test(`${value}`));
return typeof value === 'string' && /^(data:image\/)/.test(`${value}`);
}
function imageSrc(value: any) {
return (imageBase64(value) || imageURL(value));
return imageBase64(value) || imageURL(value);
}
function svg(value: any) {
return (typeof value === 'string' && /^(<svg[\s]{1,}|<svg>)/i.test(`${value}`.trim()) && /<\/[\s]{0,}svg>$/i.test(`${value}`.trim()));
return (
typeof value === 'string' &&
/^(<svg[\s]{1,}|<svg>)/i.test(`${value}`.trim()) &&
/<\/[\s]{0,}svg>$/i.test(`${value}`.trim())
);
}
function html(value: any) {
@ -93,13 +100,26 @@ function fontWeight(value: any) {
return ['bold'].includes(value);
}
const is = {
x, y, w, h, angle, number,
borderWidth, borderRadius, color,
imageSrc, imageURL, imageBase64, svg, html,
text, fontSize, lineHeight, textAlign, fontFamily, fontWeight,
strokeWidth,
export const is = {
x,
y,
w,
h,
angle,
number,
borderWidth,
borderRadius,
color,
imageSrc,
imageURL,
imageBase64,
svg,
html,
text,
fontSize,
lineHeight,
textAlign,
fontFamily,
fontWeight,
strokeWidth
};
export default is;

View file

@ -1,50 +1,49 @@
function parsePrototype (data: any): string {
function parsePrototype(data: any): string {
const typeStr = Object.prototype.toString.call(data) || '';
const result = typeStr.replace(/(\[object|\])/ig, '').trim();
const result = typeStr.replace(/(\[object|\])/gi, '').trim();
return result;
}
const istype = {
export const istype = {
type(data: any, lowerCase?: boolean): string {
const result = parsePrototype(data);
return lowerCase === true ? result.toLocaleLowerCase() : result;
},
array (data: any): boolean {
array(data: any): boolean {
return parsePrototype(data) === 'Array';
},
json (data: any): boolean {
json(data: any): boolean {
return parsePrototype(data) === 'Object';
},
function (data: any): boolean {
function(data: any): boolean {
return parsePrototype(data) === 'Function';
},
asyncFunction (data: any): boolean {
asyncFunction(data: any): boolean {
return parsePrototype(data) === 'AsyncFunction';
},
string (data: any): boolean {
string(data: any): boolean {
return parsePrototype(data) === 'String';
},
number (data: any): boolean {
number(data: any): boolean {
return parsePrototype(data) === 'Number';
},
undefined (data: any): boolean {
undefined(data: any): boolean {
return parsePrototype(data) === 'Undefined';
},
null (data: any): boolean {
null(data: any): boolean {
return parsePrototype(data) === 'Null';
},
promise (data: any): boolean {
promise(data: any): boolean {
return parsePrototype(data) === 'Promise';
},
}
// nodeList (data: any): boolean {
// return parsePrototype(data) === 'NodeList';
@ -53,7 +52,4 @@ const istype = {
// imageData(data: any): boolean {
// return parsePrototype(data) === 'ImageData';
// }
};
export default istype;

View file

@ -3,10 +3,10 @@ const { Image } = window;
export function loadImage(src: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image;
const img = new Image();
// img.setAttribute('crossOrigin', 'anonymous');
img.crossOrigin = 'anonymous';
img.onload = function() {
img.onload = function () {
resolve(img);
};
img.onabort = reject;
@ -15,23 +15,22 @@ export function loadImage(src: string): Promise<HTMLImageElement> {
});
}
export async function loadSVG(svg: string): Promise<HTMLImageElement> {
const dataURL = await parseSVGToDataURL(svg);
const image = await loadImage(dataURL);
return image;
}
function filterAmpersand(str: string): string {
return str.replace(/\&/ig, '&amp;');
return str.replace(/\&/gi, '&amp;');
}
export async function loadHTML(html: string, opts: { width: number, height: number }): Promise<HTMLImageElement> {
export async function loadHTML(
html: string,
opts: { width: number; height: number }
): Promise<HTMLImageElement> {
html = filterAmpersand(html);
const dataURL = await parseHTMLToDataURL(html, opts);
const image = await loadImage(dataURL);
return image;
}

View file

@ -1,8 +1,14 @@
export function parseHTMLToDataURL(html: string, opts: { width: number, height: number }): Promise<string> {
export function parseHTMLToDataURL(
html: string,
opts: { width: number; height: number }
): Promise<string> {
const { width, height } = opts;
return new Promise((resolve, reject) => {
const _svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${width || ''}" height = "${height || ''}">
<svg
xmlns="http://www.w3.org/2000/svg"
width="${width || ''}"
height = "${height || ''}">
<foreignObject width="100%" height="100%">
<div xmlns = "http://www.w3.org/1999/xhtml">
${html}
@ -10,31 +16,30 @@ export function parseHTMLToDataURL(html: string, opts: { width: number, height:
</foreignObject>
</svg>
`;
const blob = new Blob([_svg], { type: 'image/svg+xml;charset=utf-8'});
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (event: ProgressEvent<FileReader>) {
const base64: string = event?.target?.result as string;
resolve(base64)
};
reader.onerror = function(err) {
reject(err);
};
});
}
export function parseSVGToDataURL(svg: string): Promise<string> {
return new Promise((resolve, reject) => {
const _svg = svg;
const blob = new Blob([_svg], { type: 'image/svg+xml;charset=utf-8'});
const blob = new Blob([_svg], { type: 'image/svg+xml;charset=utf-8' });
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (event: ProgressEvent<FileReader>) {
const base64: string = event?.target?.result as string;
resolve(base64);
};
reader.onerror = function(err) {
reader.onerror = function (err) {
reject(err);
};
});
}
export function parseSVGToDataURL(svg: string): Promise<string> {
return new Promise((resolve, reject) => {
const _svg = svg;
const blob = new Blob([_svg], { type: 'image/svg+xml;charset=utf-8' });
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (event: ProgressEvent<FileReader>) {
const base64: string = event?.target?.result as string;
resolve(base64);
};
reader.onerror = function (err) {
reject(err);
};
});

View file

@ -0,0 +1,32 @@
import type { Point, TouchPoint } from '@idraw/types';
export function calcDistance(start: Point, end: Point) {
const distance = (start.x - end.x) * (start.x - end.x) + (start.y - end.y) * (start.y - end.y);
return distance === 0 ? distance : Math.sqrt(distance);
}
export function calcSpeed(start: Point, end: Point) {
const distance = calcDistance(start, end);
const speed = distance / Math.abs(end.t - start.t);
return speed;
}
export function equalPoint(p1: Point, p2: Point) {
return p1.x === p2.x && p1.y === p2.y && p1.t === p2.t;
}
export function equalTouchPoint(p1: TouchPoint, p2: TouchPoint) {
return equalPoint(p1, p2) === true && p1.f === p2.f;
}
function isNum(num: any): boolean {
return num >= 0 || num < 0;
}
export function vaildPoint(p: Point) {
return isNum(p.x) && isNum(p.y) && p.t > 0;
}
export function vaildTouchPoint(p: TouchPoint) {
return vaildPoint(p) === true && p.f >= 0;
}

View file

@ -0,0 +1,31 @@
import { deepClone } from './data';
export class Store<T extends Record<string, any>> {
private _temp: T;
private _backUpDefaultStorage: T;
constructor(opts: { defaultStorage: T }) {
this._backUpDefaultStorage = deepClone(opts.defaultStorage);
this._temp = this._createTempStorage();
}
set<K extends keyof T>(name: K, value: T[K]) {
this._temp[name] = value;
}
get<K extends keyof T>(name: K): T[K] {
return this._temp[name];
}
getSnapshot(): T {
return deepClone(this._temp);
}
clear() {
this._temp = this._createTempStorage();
}
private _createTempStorage() {
return deepClone(this._backUpDefaultStorage);
}
}