photo-editor
Version:
Simple and customizable photo editor for web applications.
289 lines (282 loc) • 7.66 kB
JavaScript
import {
Tool
} from "./chunk-W32WS4AD.js";
// src/PhotoEditor.ts
import { EventEmitter } from "eventemitter3";
// src/getInitialState.ts
import { unwrap } from "krustykrab";
// src/waitForImageComplete.ts
var waitForImageComplete = (image) => new Promise((resolve, reject) => {
const onLoad = () => {
image.removeEventListener("load", onLoad);
image.removeEventListener("error", onError);
resolve();
};
const onError = () => {
image.removeEventListener("load", onLoad);
image.removeEventListener("error", onError);
reject();
};
image.addEventListener("load", onLoad);
image.addEventListener("error", onError);
});
// src/getInitialState.ts
var emptyImageWarning = "PhotoEditor: source image is loaded with error";
var imageToBase64 = async (image) => {
if (image.complete) {
if (image.naturalWidth === 0) {
console.warn(emptyImageWarning);
}
} else {
try {
await waitForImageComplete(image);
} catch (e) {
console.warn(emptyImageWarning);
}
}
const fakeCanvasEl = document.createElement("canvas");
const ctx = unwrap(fakeCanvasEl.getContext("2d"));
fakeCanvasEl.height = image.naturalHeight;
fakeCanvasEl.width = image.naturalWidth;
ctx.drawImage(image, 0, 0);
return fakeCanvasEl.toDataURL();
};
var getInitialState = async (el, options) => {
switch (options.sourceType) {
case "current-canvas":
return el.toDataURL();
case "canvas":
return options.source.toDataURL();
case "img": {
const base64image = await imageToBase64(
options.source
);
return base64image;
}
case "base64":
return options.source;
default:
throw new Error(
'"sourceType" should be one of: "current-canvas", "canvas", "img", "base64"'
);
}
};
// src/validateSource.ts
var validateSource = (options) => {
switch (options.sourceType) {
case "current-canvas":
return;
case "canvas": {
const { source } = options;
if (!(source instanceof HTMLElement) || source.tagName !== "CANVAS") {
throw new Error(
'PhotoEditor source for sourceType "canvas" should be a canvas'
);
}
return;
}
case "img": {
const { source } = options;
if (!(source instanceof HTMLElement) || source.tagName !== "IMG") {
throw new Error(
'PhotoEditor source for sourceType "img" should be an image'
);
}
return;
}
case "base64": {
const { source } = options;
if (typeof source !== "string") {
throw new Error(
'PhotoEditor source for sourceType "base64" should be a string'
);
}
return;
}
default:
throw new Error(
'"sourceType" should be one of: "current-canvas", "canvas", "img", "base64"'
);
}
};
// src/validateOptions.ts
var validateOptions = (options) => {
if (typeof options !== "object") {
throw new Error("PhotoEditor options should be an object");
}
if (options === null) {
throw new Error("PhotoEditor options can't be null");
}
if (typeof options.tools !== "object") {
throw new Error("PhotoEditor tools should be an object");
}
if (options.tools === null) {
throw new Error("PhotoEditor tools can't be null");
}
validateSource(options);
};
// src/PhotoEditor.ts
var defaultOptions = {
sourceType: "current-canvas"
};
var PhotoEditor = class extends EventEmitter {
_el = null;
_options = null;
_currentState = -1;
_states = [];
_enabledToolId = null;
_touched = false;
_destroyed = false;
tools = {};
constructor(el, editorOptions) {
super();
const options = {
...defaultOptions,
...editorOptions
};
if (!(el instanceof HTMLElement) || el.tagName !== "CANVAS") {
throw new Error("Element for init PhotoEditor should be a canvas");
}
validateOptions(options);
this._el = el;
this._options = options;
this._init();
}
async _init() {
if (!this._el) {
throw new Error("Canvas is not provided");
}
if (!this._options) {
throw new Error("Options are not provided");
}
const initialState = await getInitialState(this._el, this._options);
this._currentState = 0;
this._states = [initialState];
if (this._options.sourceType !== "current-canvas") {
await this._drawCurrentState();
}
this._initTools();
this.emit("ready");
}
_initTools() {
if (!this._el) {
throw new Error("Canvas is not provided");
}
if (!this._options) {
throw new Error("Options are not provided");
}
const { tools } = this._options;
const el = this._el;
for (const toolIdRaw of Object.keys(tools)) {
const toolId = toolIdRaw;
const ToolConstructor = tools[toolId];
if (typeof ToolConstructor !== "function") {
throw new Error(
`PhotoEditor tool "${toolId}": should be a class that extends Tool`
);
}
const tool = new ToolConstructor({
el,
pushState: this._pushState,
updateState: this._updateState,
disable: this.disableTool,
touch: this.touch
});
if (!(tool instanceof Tool)) {
throw new Error(
`PhotoEditor tool "${toolId}": should be an instance of Tool `
);
}
this.tools[toolId] = tool;
}
}
_pushState = (state) => {
const slicedStates = this._states.slice(0, this._currentState + 1);
slicedStates.push(state);
++this._currentState;
this._states = slicedStates;
};
_updateState = (state) => {
const slicedStates = this._states.slice(0, this._currentState);
slicedStates.push(state);
this._states = slicedStates;
};
async _drawCurrentState() {
const base64 = this.getCurrentState();
const image = new Image();
image.src = base64;
await waitForImageComplete(image);
if (this._destroyed) {
return;
}
if (!this._el) {
throw new Error("Canvas is not provided");
}
this._el.width = image.naturalWidth;
this._el.height = image.naturalHeight;
const ctx = this._el.getContext("2d");
if (!ctx) {
throw new Error("Context is not found");
}
ctx.drawImage(image, 0, 0);
}
destroy() {
this._destroyed = true;
for (const toolId of Object.keys(this.tools)) {
const tool = this.tools[toolId];
tool.destroy();
}
}
getCurrentState() {
return this._states[this._currentState];
}
enableTool(toolId) {
if (!this.tools[toolId]) {
throw new Error(`PhotoEditor tool with id "${toolId}" is not defined`);
}
this.disableTool();
this._enabledToolId = toolId;
this.tools[toolId].enableFromEditor();
this.emit("enableTool", toolId);
}
disableTool = () => {
if (this._enabledToolId) {
this.tools[this._enabledToolId].disableFromEditor();
this.emit("disableTool", this._enabledToolId);
if (this._touched) {
this._drawCurrentState();
}
this._touched = false;
this._enabledToolId = null;
}
};
toggleTool(toolId) {
if (this._enabledToolId === toolId) {
this.disableTool();
} else {
this.enableTool(toolId);
}
}
touch = () => {
this._touched = true;
};
undo() {
if (this._currentState > 0) {
--this._currentState;
this.disableTool();
this._drawCurrentState();
}
}
redo() {
if (this._currentState < this._states.length - 1) {
++this._currentState;
this.disableTool();
this._drawCurrentState();
}
}
};
export {
PhotoEditor,
Tool
};
//# sourceMappingURL=index.js.map