mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
refactor: refactor uitl for v0.4
This commit is contained in:
parent
5b5aa90743
commit
aaa217ed58
17 changed files with 311 additions and 505 deletions
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
|
|
@ -7,5 +5,6 @@
|
|||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": true
|
||||
}
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 160
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
46
packages/util/src/lib/canvas.ts
Normal file
46
packages/util/src/lib/canvas.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
56
packages/util/src/lib/event.ts
Normal file
56
packages/util/src/lib/event.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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, '&');
|
||||
return str.replace(/\&/gi, '&');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
32
packages/util/src/lib/point.ts
Normal file
32
packages/util/src/lib/point.ts
Normal 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;
|
||||
}
|
||||
31
packages/util/src/lib/store.ts
Normal file
31
packages/util/src/lib/store.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue