mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
feat: improve image render
This commit is contained in:
parent
14b8436ed3
commit
166c3e865d
12 changed files with 270 additions and 93 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement } from '@idraw/util';
|
||||
import { createColorStyle } from './color';
|
||||
import { drawBoxShadow } from './base';
|
||||
|
||||
export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: RendererDrawElementOptions) {
|
||||
const { detail, angle } = elem;
|
||||
|
|
@ -8,41 +9,49 @@ export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: Re
|
|||
const { calculator, viewScaleInfo, viewSizeInfo } = opts;
|
||||
// const { scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = viewScaleInfo;
|
||||
const { x, y, w, h } = calculator.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, viewScaleInfo, viewSizeInfo);
|
||||
const viewElem = { ...elem, ...{ x, y, w, h, angle } };
|
||||
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
const a = w / 2;
|
||||
const b = h / 2;
|
||||
const centerX = x + a;
|
||||
const centerY = y + b;
|
||||
|
||||
if (elem?.detail?.opacity !== undefined && elem?.detail?.opacity >= 0) {
|
||||
ctx.globalAlpha = elem.detail.opacity;
|
||||
} else {
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
// draw border
|
||||
if (typeof borderWidth === 'number' && borderWidth > 0) {
|
||||
const ba = borderWidth / 2 + a;
|
||||
const bb = borderWidth / 2 + b;
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = borderColor;
|
||||
ctx.lineWidth = borderWidth;
|
||||
ctx.circle(centerX, centerY, ba, bb, 0, 0, 2 * Math.PI);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// draw content
|
||||
ctx.beginPath();
|
||||
const fillStyle = createColorStyle(ctx, background, {
|
||||
viewElementSize: { x, y, w, h },
|
||||
drawBoxShadow(ctx, viewElem, {
|
||||
viewScaleInfo,
|
||||
opacity: ctx.globalAlpha
|
||||
viewSizeInfo,
|
||||
renderContent: () => {
|
||||
const a = w / 2;
|
||||
const b = h / 2;
|
||||
const centerX = x + a;
|
||||
const centerY = y + b;
|
||||
|
||||
if (elem?.detail?.opacity !== undefined && elem?.detail?.opacity >= 0) {
|
||||
ctx.globalAlpha = elem.detail.opacity;
|
||||
} else {
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
// draw border
|
||||
if (typeof borderWidth === 'number' && borderWidth > 0) {
|
||||
const ba = borderWidth / 2 + a;
|
||||
const bb = borderWidth / 2 + b;
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = borderColor;
|
||||
ctx.lineWidth = borderWidth;
|
||||
ctx.circle(centerX, centerY, ba, bb, 0, 0, 2 * Math.PI);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// draw content
|
||||
ctx.beginPath();
|
||||
const fillStyle = createColorStyle(ctx, background, {
|
||||
viewElementSize: { x, y, w, h },
|
||||
viewScaleInfo,
|
||||
opacity: ctx.globalAlpha
|
||||
});
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.circle(centerX, centerY, a, b, 0, 0, 2 * Math.PI);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
});
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.circle(centerX, centerY, a, b, 0, 0, 2 * Math.PI);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/
|
|||
import { rotateElement } from '@idraw/util';
|
||||
|
||||
export function drawImage(ctx: ViewContext2D, elem: Element<'image'>, opts: RendererDrawElementOptions) {
|
||||
const content = opts.loader.getContent(elem.uuid);
|
||||
const content = opts.loader.getContent(elem);
|
||||
const { calculator, viewScaleInfo, viewSizeInfo } = opts;
|
||||
const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo);
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement } from '@idraw/util';
|
||||
import { is, isColorStr } from '@idraw/util';
|
||||
import { is, isColorStr, getDefaultElementDetailConfig } from '@idraw/util';
|
||||
import { drawBox } from './base';
|
||||
|
||||
const detailConfig = getDefaultElementDetailConfig();
|
||||
|
||||
export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: RendererDrawElementOptions) {
|
||||
const { calculator, viewScaleInfo, viewSizeInfo } = opts;
|
||||
const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo);
|
||||
|
|
@ -15,17 +17,13 @@ export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: Render
|
|||
viewSizeInfo,
|
||||
renderContent: () => {
|
||||
const detail: Element<'text'>['detail'] = {
|
||||
...{
|
||||
fontSize: 12,
|
||||
fontFamily: 'sans-serif',
|
||||
textAlign: 'center'
|
||||
},
|
||||
...detailConfig,
|
||||
...elem.detail
|
||||
};
|
||||
const fontSize = detail.fontSize * viewScaleInfo.scale;
|
||||
const fontSize = (detail.fontSize || detailConfig.fontSize) * viewScaleInfo.scale;
|
||||
const lineHeight = detail.lineHeight ? detail.lineHeight * viewScaleInfo.scale : fontSize;
|
||||
|
||||
ctx.fillStyle = elem.detail.color;
|
||||
ctx.fillStyle = elem.detail.color || detailConfig.color;
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.$setFont({
|
||||
fontWeight: detail.fontWeight,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,30 @@
|
|||
import type { RendererLoader, LoaderEventMap, LoadFunc, LoadContent, LoadItem, LoadElementType, Element, ElementAssets } from '@idraw/types';
|
||||
import { loadImage, loadHTML, loadSVG, EventEmitter, deepClone } from '@idraw/util';
|
||||
import { loadImage, loadHTML, loadSVG, EventEmitter, createAssetId, isAssetId, createUUID } from '@idraw/util';
|
||||
|
||||
interface LoadItemMap {
|
||||
[uuid: string]: LoadItem;
|
||||
[assetId: string]: LoadItem;
|
||||
}
|
||||
|
||||
const supportElementTypes: LoadElementType[] = ['image', 'svg', 'html'];
|
||||
|
||||
const getAssetIdFromElement = (element: Element<'image' | 'svg' | 'html'>) => {
|
||||
let source: string | null = null;
|
||||
if (element.type === 'image') {
|
||||
source = (element as Element<'image'>)?.detail?.src || null;
|
||||
} else if (element.type === 'svg') {
|
||||
source = (element as Element<'svg'>)?.detail?.svg || null;
|
||||
} else if (element.type === 'html') {
|
||||
source = (element as Element<'html'>)?.detail?.html || null;
|
||||
}
|
||||
if (typeof source === 'string' && source) {
|
||||
if (isAssetId(source)) {
|
||||
return source;
|
||||
}
|
||||
return createAssetId(source);
|
||||
}
|
||||
return createAssetId(`${createUUID()}-${element.uuid}-${createUUID()}-${createUUID()}`);
|
||||
};
|
||||
|
||||
export class Loader extends EventEmitter<LoaderEventMap> implements RendererLoader {
|
||||
private _loadFuncMap: Record<LoadElementType | string, LoadFunc<LoadElementType, LoadContent>> = {};
|
||||
private _currentLoadItemMap: LoadItemMap = {};
|
||||
|
|
@ -76,37 +94,38 @@ export class Loader extends EventEmitter<LoaderEventMap> implements RendererLoad
|
|||
}
|
||||
|
||||
private _emitLoad(item: LoadItem) {
|
||||
const uuid = item.element.uuid;
|
||||
const storageItem = this._storageLoadItemMap[uuid];
|
||||
const assetId = getAssetIdFromElement(item.element);
|
||||
const storageItem = this._storageLoadItemMap[assetId];
|
||||
if (storageItem) {
|
||||
if (storageItem.startTime < item.startTime) {
|
||||
this._storageLoadItemMap[uuid] = item;
|
||||
this._storageLoadItemMap[assetId] = item;
|
||||
this.trigger('load', { ...item, countTime: item.endTime - item.startTime });
|
||||
}
|
||||
} else {
|
||||
this._storageLoadItemMap[uuid] = item;
|
||||
this._storageLoadItemMap[assetId] = item;
|
||||
this.trigger('load', { ...item, countTime: item.endTime - item.startTime });
|
||||
}
|
||||
}
|
||||
|
||||
private _emitError(item: LoadItem) {
|
||||
const uuid = item.element.uuid;
|
||||
const storageItem = this._storageLoadItemMap[uuid];
|
||||
const assetId = getAssetIdFromElement(item.element);
|
||||
const storageItem = this._storageLoadItemMap[assetId];
|
||||
if (storageItem) {
|
||||
if (storageItem.startTime < item.startTime) {
|
||||
this._storageLoadItemMap[uuid] = item;
|
||||
this._storageLoadItemMap[assetId] = item;
|
||||
this.trigger('error', { ...item, countTime: item.endTime - item.startTime });
|
||||
}
|
||||
} else {
|
||||
this._storageLoadItemMap[uuid] = item;
|
||||
this._storageLoadItemMap[assetId] = item;
|
||||
this.trigger('error', { ...item, countTime: item.endTime - item.startTime });
|
||||
}
|
||||
}
|
||||
|
||||
private _loadResource(element: Element<LoadElementType>, assets: ElementAssets) {
|
||||
const item = this._createLoadItem(element);
|
||||
const assetId = getAssetIdFromElement(element);
|
||||
|
||||
this._currentLoadItemMap[element.uuid] = item;
|
||||
this._currentLoadItemMap[assetId] = item;
|
||||
const loadFunc = this._loadFuncMap[element.type];
|
||||
if (typeof loadFunc === 'function') {
|
||||
item.startTime = Date.now();
|
||||
|
|
@ -128,7 +147,8 @@ export class Loader extends EventEmitter<LoaderEventMap> implements RendererLoad
|
|||
}
|
||||
|
||||
private _isExistingErrorStorage(element: Element<LoadElementType>) {
|
||||
const existItem = this._currentLoadItemMap?.[element?.uuid];
|
||||
const assetId = getAssetIdFromElement(element);
|
||||
const existItem = this._currentLoadItemMap?.[assetId];
|
||||
if (existItem && existItem.status === 'error' && existItem.source && existItem.source === this._getLoadElementSource(element)) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -140,12 +160,13 @@ export class Loader extends EventEmitter<LoaderEventMap> implements RendererLoad
|
|||
return;
|
||||
}
|
||||
if (supportElementTypes.includes(element.type)) {
|
||||
const elem = deepClone(element);
|
||||
this._loadResource(elem, assets);
|
||||
// const elem = deepClone(element);
|
||||
this._loadResource(element, assets);
|
||||
}
|
||||
}
|
||||
|
||||
getContent(uuid: string): LoadContent | null {
|
||||
return this._storageLoadItemMap?.[uuid]?.content || null;
|
||||
getContent(element: Element<LoadElementType>): LoadContent | null {
|
||||
const assetId = getAssetIdFromElement(element);
|
||||
return this._storageLoadItemMap?.[assetId]?.content || null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { ElementBaseDetail } from './element';
|
||||
import type { ElementBaseDetail, ElementTextDetail } from './element';
|
||||
|
||||
export type DefaultElementDetailConfig = Required<Omit<ElementBaseDetail, 'clipPath' | 'background'>>;
|
||||
export type DefaultElementDetailConfig = Required<Omit<ElementBaseDetail, 'clipPath' | 'background'>> &
|
||||
Required<Pick<ElementTextDetail, 'color' | 'textAlign' | 'verticalAlign' | 'fontSize' | 'fontFamily' | 'fontWeight' | 'lineHeight'>>;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export interface TransformMatrix {
|
|||
}
|
||||
|
||||
export interface ElementAssetsItem {
|
||||
type: 'svg' | 'image';
|
||||
type: 'svg' | 'image' | 'html';
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +79,6 @@ export interface ElementBaseDetail {
|
|||
shadowOffsetX?: number;
|
||||
shadowOffsetY?: number;
|
||||
shadowBlur?: number;
|
||||
// color?: string;
|
||||
background?: string | LinearGradientColor | RadialGradientColor;
|
||||
opacity?: number;
|
||||
clipPath?: ElementClipPath;
|
||||
|
|
@ -90,12 +89,12 @@ export interface ElementBaseDetail {
|
|||
// // background?: string;
|
||||
// }
|
||||
|
||||
interface ElementRectDetail extends ElementBaseDetail {}
|
||||
export interface ElementRectDetail extends ElementBaseDetail {}
|
||||
|
||||
interface ElemenTextDetail extends ElementBaseDetail {
|
||||
export interface ElementTextDetail extends ElementBaseDetail {
|
||||
text: string;
|
||||
color: string;
|
||||
fontSize: number;
|
||||
color?: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: 'bold' | string | number;
|
||||
fontFamily?: string;
|
||||
|
|
@ -107,32 +106,32 @@ interface ElemenTextDetail extends ElementBaseDetail {
|
|||
textShadowBlur?: number;
|
||||
}
|
||||
|
||||
interface ElementCircleDetail extends ElementBaseDetail {
|
||||
export interface ElementCircleDetail extends ElementBaseDetail {
|
||||
radius: number;
|
||||
background?: string;
|
||||
}
|
||||
|
||||
interface ElementHTMLDetail extends ElementBaseDetail {
|
||||
export interface ElementHTMLDetail extends ElementBaseDetail {
|
||||
html: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
interface ElementImageDetail extends ElementBaseDetail {
|
||||
export interface ElementImageDetail extends ElementBaseDetail {
|
||||
src: string;
|
||||
}
|
||||
|
||||
interface ElementSVGDetail extends ElementBaseDetail {
|
||||
export interface ElementSVGDetail extends ElementBaseDetail {
|
||||
svg: string;
|
||||
}
|
||||
|
||||
interface ElementGroupDetail extends ElementBaseDetail {
|
||||
export interface ElementGroupDetail extends ElementBaseDetail {
|
||||
children: Element<ElementType>[];
|
||||
overflow?: 'hidden';
|
||||
assets?: ElementAssets;
|
||||
}
|
||||
|
||||
interface ElementPathDetail extends ElementBaseDetail {
|
||||
export interface ElementPathDetail extends ElementBaseDetail {
|
||||
// path: string;
|
||||
commands: SVGPathCommand[];
|
||||
originX: number;
|
||||
|
|
@ -145,10 +144,10 @@ interface ElementPathDetail extends ElementBaseDetail {
|
|||
strokeLineCap?: 'butt' | 'round' | 'square';
|
||||
}
|
||||
|
||||
interface ElementDetailMap {
|
||||
export interface ElementDetailMap {
|
||||
rect: ElementRectDetail;
|
||||
circle: ElementCircleDetail;
|
||||
text: ElemenTextDetail;
|
||||
text: ElementTextDetail;
|
||||
image: ElementImageDetail;
|
||||
html: ElementHTMLDetail;
|
||||
svg: ElementSVGDetail;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export interface RendererEventMap {
|
|||
export interface RendererLoader extends UtilEventEmitter<LoaderEventMap> {
|
||||
// load(element: Element<LoadElementType>): void;
|
||||
load(element: Element<LoadElementType>, assets: ElementAssets): void;
|
||||
getContent(uuid: string): LoadContent | null;
|
||||
getContent(element: Element<LoadElementType>): LoadContent | null;
|
||||
}
|
||||
|
||||
export interface RendererDrawOptions {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export { delay, compose, throttle } from './lib/time';
|
||||
export { downloadImageFromCanvas } from './lib/file';
|
||||
export { downloadImageFromCanvas, parseFileToBase64, pickFile } from './lib/file';
|
||||
export { toColorHexStr, toColorHexNum, isColorStr, colorNameToHex, colorToCSS, colorToLinearGradientCSS, mergeHexColorAlpha } from './lib/color';
|
||||
export { createUUID, isAssetId, createAssetId } from './lib/uuid';
|
||||
export { deepClone, sortDataAsserts } from './lib/data';
|
||||
|
|
@ -34,7 +34,10 @@ export {
|
|||
findElementsFromList,
|
||||
updateElementInList,
|
||||
getGroupQueueFromList,
|
||||
getElementSize
|
||||
getElementSize,
|
||||
mergeElementAsset,
|
||||
filterElementAsset,
|
||||
isResourceElement
|
||||
} from './lib/element';
|
||||
export { checkRectIntersect } from './lib/rect';
|
||||
export {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,14 @@ export function getDefaultElementDetailConfig(): DefaultElementDetailConfig {
|
|||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0,
|
||||
shadowBlur: 0,
|
||||
opacity: 1
|
||||
opacity: 1,
|
||||
color: '#000000',
|
||||
textAlign: 'left',
|
||||
verticalAlign: 'top',
|
||||
fontSize: 16,
|
||||
lineHeight: 20,
|
||||
fontFamily: 'sans-serif',
|
||||
fontWeight: 400
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
import type { Data, Element, Elements, ElementType, ElementSize, ViewContextSize, ViewSizeInfo, RecursivePartial } from '@idraw/types';
|
||||
import type {
|
||||
Data,
|
||||
Element,
|
||||
Elements,
|
||||
ElementType,
|
||||
ElementSize,
|
||||
ViewContextSize,
|
||||
ViewSizeInfo,
|
||||
RecursivePartial,
|
||||
ElementAssets,
|
||||
ElementAssetsItem,
|
||||
LoadElementType
|
||||
} from '@idraw/types';
|
||||
import { rotateElementVertexes } from './rotate';
|
||||
import { isAssetId } from './uuid';
|
||||
import { isAssetId, createAssetId } from './uuid';
|
||||
import { istype } from './istype';
|
||||
|
||||
// // TODO need to be deprecated
|
||||
|
|
@ -328,21 +340,19 @@ function mergeElement<T extends Element<ElementType> = Element<ElementType>>(ori
|
|||
return originElem;
|
||||
}
|
||||
|
||||
export function updateElementInList(
|
||||
uuid: string,
|
||||
updateContent: RecursivePartial<Element<ElementType>>,
|
||||
elements: Element<ElementType>[]
|
||||
): Element<ElementType>[] {
|
||||
export function updateElementInList(uuid: string, updateContent: RecursivePartial<Element<ElementType>>, elements: Element[]): Element | null {
|
||||
let targetElement: Element | null = null;
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const elem = elements[i];
|
||||
if (elem.uuid === uuid) {
|
||||
mergeElement(elem, updateContent);
|
||||
targetElement = elem;
|
||||
break;
|
||||
} else if (elem.type === 'group') {
|
||||
updateElementInList(uuid, updateContent, (elem as Element<'group'>)?.detail?.children || []);
|
||||
targetElement = updateElementInList(uuid, updateContent, (elem as Element<'group'>)?.detail?.children || []);
|
||||
}
|
||||
}
|
||||
return elements;
|
||||
return targetElement;
|
||||
}
|
||||
|
||||
export function getElementSize(elem: Element): ElementSize {
|
||||
|
|
@ -350,3 +360,77 @@ export function getElementSize(elem: Element): ElementSize {
|
|||
const size: ElementSize = { x, y, w, h, angle };
|
||||
return size;
|
||||
}
|
||||
|
||||
export function mergeElementAsset<T extends Element<LoadElementType>>(element: T, assets: ElementAssets): T {
|
||||
// const elem: T = { ...element, ...{ detail: { ...element.detail } } };
|
||||
const elem = element;
|
||||
let assetId: string | null = null;
|
||||
let assetItem: ElementAssetsItem | null = null;
|
||||
if (elem.type === 'image') {
|
||||
assetId = (elem as Element<'image'>).detail.src;
|
||||
} else if (elem.type === 'svg') {
|
||||
assetId = (elem as Element<'svg'>).detail.svg;
|
||||
} else if (elem.type === 'html') {
|
||||
assetId = (elem as Element<'html'>).detail.html;
|
||||
}
|
||||
|
||||
if (assetId && assetId?.startsWith('@assets/')) {
|
||||
assetItem = assets[assetId];
|
||||
}
|
||||
|
||||
if (assetItem?.type === elem.type && typeof assetItem?.value === 'string' && assetItem?.value) {
|
||||
if (elem.type === 'image') {
|
||||
(elem as Element<'image'>).detail.src = assetItem.value;
|
||||
} else if (elem.type === 'svg') {
|
||||
(elem as Element<'svg'>).detail.svg = assetItem.value;
|
||||
} else if (elem.type === 'html') {
|
||||
(elem as Element<'html'>).detail.html = assetItem.value;
|
||||
}
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
export function filterElementAsset<T extends Element<LoadElementType>>(
|
||||
element: T
|
||||
): {
|
||||
element: T;
|
||||
assetId: string | null;
|
||||
assetItem: ElementAssetsItem | null;
|
||||
} {
|
||||
let assetId: string | null = null;
|
||||
let assetItem: ElementAssetsItem | null = null;
|
||||
let resource: string | null = null;
|
||||
|
||||
if (element.type === 'image') {
|
||||
resource = (element as Element<'image'>).detail.src;
|
||||
} else if (element.type === 'svg') {
|
||||
resource = (element as Element<'svg'>).detail.svg;
|
||||
} else if (element.type === 'html') {
|
||||
resource = (element as Element<'html'>).detail.html;
|
||||
}
|
||||
|
||||
if (typeof resource === 'string' && !isAssetId(resource)) {
|
||||
assetId = createAssetId(resource);
|
||||
assetItem = {
|
||||
type: element.type as LoadElementType,
|
||||
value: resource
|
||||
};
|
||||
if (element.type === 'image') {
|
||||
(element as Element<'image'>).detail.src = assetId;
|
||||
} else if (element.type === 'svg') {
|
||||
(element as Element<'svg'>).detail.svg = assetId;
|
||||
} else if (element.type === 'html') {
|
||||
(element as Element<'html'>).detail.html = assetId;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
element,
|
||||
assetId,
|
||||
assetItem
|
||||
};
|
||||
}
|
||||
|
||||
export function isResourceElement(elem: Element): boolean {
|
||||
return ['image', 'svg', 'html'].includes(elem?.type);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
type ImageType = 'image/jpeg' | 'image/png';
|
||||
|
||||
export function downloadImageFromCanvas(
|
||||
canvas: HTMLCanvasElement,
|
||||
opts: { filename: string; type: ImageType }
|
||||
): void {
|
||||
export function downloadImageFromCanvas(canvas: HTMLCanvasElement, opts: { filename: string; type: ImageType }): void {
|
||||
const { filename, type = 'image/jpeg' } = opts;
|
||||
const stream = canvas.toDataURL(type);
|
||||
const downloadLink = document.createElement('a');
|
||||
|
|
@ -13,3 +10,63 @@ export function downloadImageFromCanvas(
|
|||
downloadClickEvent.initEvent('click', true, false);
|
||||
downloadLink.dispatchEvent(downloadClickEvent);
|
||||
}
|
||||
|
||||
export function pickFile(opts: { success: (data: { file: File }) => void; error?: (err: ErrorEvent) => void }) {
|
||||
const { success, error } = opts;
|
||||
let input: HTMLInputElement | null = document.createElement('input') as HTMLInputElement;
|
||||
input.type = 'file';
|
||||
input.addEventListener('change', function () {
|
||||
const file: File = (input as HTMLInputElement).files?.[0] as File;
|
||||
success({
|
||||
file: file
|
||||
});
|
||||
input = null;
|
||||
});
|
||||
input.addEventListener('error', function (err) {
|
||||
if (typeof error === 'function') {
|
||||
error(err);
|
||||
}
|
||||
input = null;
|
||||
});
|
||||
input.click();
|
||||
}
|
||||
|
||||
export function parseFileToBase64(file: File): Promise<string | ArrayBuffer> {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let reader: any = new FileReader();
|
||||
reader.addEventListener('load', function () {
|
||||
resolve(reader.result);
|
||||
reader = null;
|
||||
});
|
||||
reader.addEventListener('error', function (err: Error) {
|
||||
// reader.abort();
|
||||
reject(err);
|
||||
reader = null;
|
||||
});
|
||||
reader.addEventListener('abort', function () {
|
||||
reject(new Error('abort'));
|
||||
reader = null;
|
||||
});
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
export function parseFileToText(file: File): Promise<string | ArrayBuffer> {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let reader: any = new FileReader();
|
||||
reader.addEventListener('load', function () {
|
||||
resolve(reader.result);
|
||||
reader = null;
|
||||
});
|
||||
reader.addEventListener('error', function (err: Error) {
|
||||
// reader.abort();
|
||||
reject(err);
|
||||
reader = null;
|
||||
});
|
||||
reader.addEventListener('abort', function () {
|
||||
reject(new Error('abort'));
|
||||
reader = null;
|
||||
});
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { createOffscreenContext2D } from './canvas';
|
||||
|
||||
export function compressImage(src: string, opts?: { radio?: number; type?: 'image/jpeg' | 'image/png' }): Promise<string> {
|
||||
let radio = 0.5;
|
||||
const type = opts?.type || 'image/png';
|
||||
|
|
|
|||
Loading…
Reference in a new issue