Skip to content

Commit

Permalink
feat: improved new export function, using blob as output for all func…
Browse files Browse the repository at this point in the history
…tions
  • Loading branch information
matteobruni committed Jul 9, 2023
1 parent 3521561 commit df1c862
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 69 deletions.
21 changes: 13 additions & 8 deletions engine/src/Core/Container.ts
Expand Up @@ -8,7 +8,6 @@ import type { IContainerInteractivity } from "./Interfaces/IContainerInteractivi
import type { IContainerPlugin } from "./Interfaces/IContainerPlugin";
import type { ICoordinates } from "./Interfaces/ICoordinates";
import type { IDelta } from "./Interfaces/IDelta";
import type { IExportPluginData } from "./Interfaces/IExportPluginData";
import type { IMovePathGenerator } from "./Interfaces/IMovePathGenerator";
import type { IShapeDrawer } from "./Interfaces/IShapeDrawer";
import type { ISourceOptions } from "../Types/ISourceOptions";
Expand Down Expand Up @@ -424,16 +423,22 @@ export class Container {
});
}

async export(type: string, data: IExportPluginData): Promise<void> {
let supported = false;

async export(type: string, options: Record<string, unknown>): Promise<Blob | undefined> {
for (const [, plugin] of this.plugins) {
supported = !!plugin.export && (await plugin.export(type, data));
}
if (!plugin.export) {
continue;
}

const res = await plugin.export(type, options);

if (!supported) {
getLogger().error(`${errorPrefix} - Unable to export with type ${type}`);
if (!res.supported) {
continue;
}

return res.blob;
}

getLogger().error(`${errorPrefix} - Export plugin with type ${type} not found`);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions engine/src/Core/Interfaces/IContainerPlugin.ts
@@ -1,7 +1,7 @@
import type { ClickMode } from "../../Enums/Modes/ClickMode";
import type { ExportResult } from "../../Types/ExportResult";
import type { ICoordinates } from "./ICoordinates";
import type { IDelta } from "./IDelta";
import type { IExportPluginData } from "./IExportPluginData";
import type { IOptionsColor } from "../../Options/Interfaces/IOptionsColor";
import type { OutModeDirection } from "../../Enums/Directions/OutModeDirection";
import type { Particle } from "../Particle";
Expand All @@ -12,7 +12,7 @@ export interface IContainerPlugin {
clickPositionValid?: (position: ICoordinates) => boolean;
draw?: (context: CanvasRenderingContext2D, delta: IDelta) => void;
drawParticle?: (context: CanvasRenderingContext2D, particle: Particle, delta: IDelta) => void;
export?: (type: string, data: IExportPluginData) => Promise<boolean>;
export?: (type: string, data: Record<string, unknown>) => Promise<ExportResult>;
handleClickMode?: (mode: ClickMode | string) => void;
init?: () => Promise<void>;
particleBounce?: (particle: Particle, delta: IDelta, direction: OutModeDirection) => boolean;
Expand Down
4 changes: 0 additions & 4 deletions engine/src/Core/Interfaces/IExportPluginData.ts

This file was deleted.

5 changes: 5 additions & 0 deletions engine/src/Types/ExportResult.ts
@@ -0,0 +1,5 @@
export type ExportResult = {
blob?: Blob;
error?: Error;
supported: boolean;
};
2 changes: 1 addition & 1 deletion engine/src/export-types.ts
Expand Up @@ -12,7 +12,6 @@ export * from "./Core/Interfaces/IDelta";
export * from "./Core/Interfaces/IDimension";
export * from "./Core/Interfaces/IDistance";
export * from "./Core/Interfaces/IDrawParticleParams";
export * from "./Core/Interfaces/IExportPluginData";
export * from "./Core/Interfaces/IExternalInteractor";
export * from "./Core/Interfaces/IInteractor";
export * from "./Core/Interfaces/ILoadParams";
Expand Down Expand Up @@ -110,6 +109,7 @@ export * from "./Options/Interfaces/Theme/IThemeDefault";

export * from "./Types/CustomEventArgs";
export * from "./Types/CustomEventListener";
export * from "./Types/ExportResult";
export * from "./Types/ISourceOptions";
export * from "./Types/ParticlesGroups";
export * from "./Types/PathOptions";
Expand Down
35 changes: 27 additions & 8 deletions plugins/exports/image/src/ExportImageInstance.ts
@@ -1,4 +1,4 @@
import type { Container, Engine, IContainerPlugin, IExportPluginData } from "tsparticles-engine";
import type { Container, Engine, ExportResult, IContainerPlugin } from "tsparticles-engine";
import type { IExportImageData } from "./IExportImageData";

export class ExportImageInstance implements IContainerPlugin {
Expand All @@ -10,24 +10,43 @@ export class ExportImageInstance implements IContainerPlugin {
this._engine = engine;
}

async export(type: string, data: IExportPluginData): Promise<boolean> {
async export(type: string, data: Record<string, unknown>): Promise<ExportResult> {
const res: ExportResult = {
supported: false,
};

switch (type) {
case "image":
this._exportImage(data);
res.supported = true;
res.blob = await this._exportImage(data);

return true;
break;
}

return false;
return res;
}

private _exportImage(data: IExportImageData): void {
private readonly _exportImage: (data: IExportImageData) => Promise<Blob | undefined> = async (data) => {
const element = this._container.canvas.element;

if (!element) {
return;
}

element.toBlob((blob) => data.callback(blob), data.type ?? "image/png", data.quality);
}
return await new Promise<Blob | undefined>((resolve) => {

Check notice on line 36 in plugins/exports/image/src/ExportImageInstance.ts

View check run for this annotation

codefactor.io / CodeFactor

plugins/exports/image/src/ExportImageInstance.ts#L36

Unnecessary 'await'. (no-return-await)
element.toBlob(
(blob) => {
if (!blob) {
resolve(undefined);

return;
}

resolve(blob);
},
data.type ?? "image/png",
data.quality,
);
});
};
}
4 changes: 1 addition & 3 deletions plugins/exports/image/src/IExportImageData.ts
@@ -1,6 +1,4 @@
import type { IExportPluginData } from "tsparticles-engine";

export interface IExportImageData extends IExportPluginData {
export interface IExportImageData {
quality?: number;
type?: string;
}
43 changes: 23 additions & 20 deletions plugins/exports/json/src/ExportJSONInstance.ts
@@ -1,4 +1,4 @@
import type { Container, Engine, IContainerPlugin, IExportPluginData } from "tsparticles-engine";
import type { Container, Engine, ExportResult, IContainerPlugin } from "tsparticles-engine";

export class ExportJSONInstance implements IContainerPlugin {
private readonly _container: Container;
Expand All @@ -9,32 +9,35 @@ export class ExportJSONInstance implements IContainerPlugin {
this._engine = engine;
}

async export(type: string, data: IExportPluginData): Promise<boolean> {
async export(type: string): Promise<ExportResult> {
const res: ExportResult = {
supported: false,
};

switch (type) {
case "json":
this._exportJSON(data);
res.supported = true;
res.blob = await this._exportJSON();

return true;
break;
}

return false;
return res;
}

private readonly _exportJSON = (data: IExportPluginData): void => {
private readonly _exportJSON = async (): Promise<Blob | undefined> => {
const json = JSON.stringify(
this._container.actualOptions,
(key, value) => {
if (key.startsWith("_")) {
return;
}

return value;
},
2,
),
contentType = "application/json",
blob = new Blob([json], { type: contentType });

data.callback(blob);
this._container.actualOptions,
(key, value) => {
if (key.startsWith("_")) {
return;
}

return value;
},
2,
);

return new Blob([json], { type: "application/json" });
};
}
53 changes: 33 additions & 20 deletions plugins/exports/video/src/ExportVideoInstance.ts
@@ -1,4 +1,4 @@
import type { Container, Engine, IContainerPlugin, IExportPluginData } from "tsparticles-engine";
import type { Container, Engine, ExportResult, IContainerPlugin } from "tsparticles-engine";
import type { IExportVideoData } from "./IExportVideoData";

export class ExportVideoInstance implements IContainerPlugin {
Expand All @@ -10,39 +10,52 @@ export class ExportVideoInstance implements IContainerPlugin {
this._engine = engine;
}

async export(type: string, data: IExportPluginData): Promise<boolean> {
async export(type: string, data: Record<string, unknown>): Promise<ExportResult> {
const res: ExportResult = {
supported: false,
};

switch (type) {
case "video":
this._exportVideo(data);
res.supported = true;
res.blob = await this._exportVideo(data as IExportVideoData);

return true;
break;
}

return false;
return res;
}

private _exportVideo(data: IExportVideoData): void {
private readonly _exportVideo: (data: IExportVideoData) => Promise<Blob | undefined> = async (data) => {
const element = this._container.canvas.element;

if (!element) {
return;
}

const stream = element.captureStream(data.fps ?? this._container.actualOptions.fpsLimit),
mimeType = data.mimeType ?? "video/webm; codecs=vp9",
recorder = new MediaRecorder(stream, {
mimeType,
}),
chunks: Blob[] = [];
return await new Promise<Blob | undefined>((resolve) => {

Check notice on line 36 in plugins/exports/video/src/ExportVideoInstance.ts

View check run for this annotation

codefactor.io / CodeFactor

plugins/exports/video/src/ExportVideoInstance.ts#L36

Unnecessary 'await'. (no-return-await)
const stream = element.captureStream(data.fps ?? this._container.actualOptions.fpsLimit),
mimeType = data.mimeType ?? "video/webm; codecs=vp9",
recorder = new MediaRecorder(stream, {
mimeType,
}),
chunks: Blob[] = [];

recorder.addEventListener("dataavailable", (event): void => {
chunks.push(event.data);
});
let record = true;

setTimeout(() => {
recorder.stop();
recorder.addEventListener("dataavailable", (event): void => {
chunks.push(event.data);

data.callback(new Blob(chunks, { type: mimeType }));
}, data.duration ?? 1000);
}
if (!record) {
recorder.stop();

resolve(new Blob(chunks, { type: mimeType }));
}
});

setTimeout(() => {
record = false;
}, data.duration ?? 1000);
});
};
}
4 changes: 1 addition & 3 deletions plugins/exports/video/src/IExportVideoData.ts
@@ -1,6 +1,4 @@
import type { IExportPluginData } from "tsparticles-engine";

export interface IExportVideoData extends IExportPluginData {
export interface IExportVideoData {
duration?: number;
fps?: number;
mimeType?: string;
Expand Down

0 comments on commit df1c862

Please sign in to comment.