Vanilla
@sealpixel/element registers <sealpixel-editor> on import. It works in a page without a framework, and in every framework that can render a custom element and set properties on it; the bindings on the other pages are that, with the framework's idiom on top.
Install
pnpm add @sealpixel/elementnpm install @sealpixel/elementNo peer dependencies. @sealpixel/core, @sealpixel/ui, the plugins and the wasm come with the package.
The starter
examples/starters/vanilla, a Vite app. Open it on StackBlitz. The script, quoted from the starter:
import '@sealpixel/element';
import type { ExportResult } from '@sealpixel/core';
const app = document.querySelector('#app') as HTMLElement;
app.innerHTML = `
<sealpixel-editor theme="dark" locale="en"></sealpixel-editor>
<output data-testid="processed">Press Download: the file name carries the SHA-256 of the result.</output>
`;
const editor = app.querySelector('sealpixel-editor') as HTMLElement & {
src: unknown;
defaultEditLog: unknown;
};
editor.defaultEditLog = {
version: 1,
source: { id: 'landscape', width: 120, height: 80 },
plugins: { finetune: 1, filter: 1 },
ops: [
{ type: 'finetune.set', id: 'shared-1', params: { contrast: 0.2, saturation: 0.3 } },
{ type: 'filter.set', id: 'shared-2', preset: 'warm', strength: 0.6 },
],
};
editor.addEventListener('sealpixel:process', (e) => {
const r = (e as CustomEvent<ExportResult>).detail;
(app.querySelector('output') as HTMLOutputElement).textContent =
`exported ${r.width}x${r.height} sha256 ${r.hash}`;
});
editor.src = '/landscape.png';Attributes carry plain data: theme, locale, plugin-options (JSON), show-export, show-download, show-history, download-label, active-tool. Everything else is a property: src (a File, Blob, Uint8Array or URL), editLog, defaultEditLog, plugins, pluginOptions, renderer, assets, output, dictionary, tools.
defaultEditLog is uncontrolled use: set it before src, and the editor owns the log from there; read it from sealpixel:change or editor.getEditLog() when you want to save. For controlled use, set editLog to what sealpixel:change gave you, every time.
The element keeps its session when it moves in the DOM: an append into another parent, a portal or a keyed reorder keeps the image, the edit history and the gestures. An element that stays out of the document releases its session. When it is connected again, it loads its src again with the edit log it had when it left.
Build setup
Vite needs no configuration for the element. Another bundler has to resolve new Worker(new URL('…', import.meta.url), { type: 'module' }) inside the package and emit .wasm files as assets; webpack 5, Parcel and esbuild with its worker plugin do. Without a bundler, serve the package's dist/ as ES modules; the worker and the wasm are resolved relative to the module URL. If the worker script does not load (a 404, a Content Security Policy that blocks it, an import inside it that fails), the element renders inline on the main thread and warns in the console. If rendering inline fails too, the element fires sealpixel:error.
Server-side rendering
The element is defined by a script, so the tag on a server-rendered page is inert until that script runs in the browser. Render the tag wherever you like; set the object properties from a module that runs on the client.
Headless
createEditorSession from @sealpixel/core is the API underneath; Getting started shows the calls. @sealpixel/element re-exports it with the plugins, so one import serves both the element and the headless session.
Server rendering
Above the client limit the browser cannot export, see Working copy. Render the original on your server with @sealpixel/render: it takes the original and the edit log from getEditLog(). See Render on your server. Quoted from the Node starter, which renders the same log to the same hash as the browsers:
// Renders the shared fixture log on the original with @sealpixel/render and
// prints the hash. Compare it with the file name the browser starters download.
import { readFile } from 'node:fs/promises';
import { DEFAULT_FONT, defaultPlugins } from '@sealpixel/plugins';
import { createRenderer } from '@sealpixel/render';
// The renderer loads no font or image. The host reads the bytes and passes
// them. DEFAULT_FONT.url points at the font inside the installed plugin.
const font = new Uint8Array(await readFile(new URL(DEFAULT_FONT.url)));
// Once per process. A server keeps this renderer and calls render() per request.
const renderer = createRenderer({
plugins: defaultPlugins,
assets: [{ id: DEFAULT_FONT.id, bytes: font }],
});
const original = new Uint8Array(
await readFile(new URL('../public/landscape.png', import.meta.url)),
);
// What the editor's getEditLog() returns: the log and the index of the last op to render.
const editLog = {
version: 1,
source: { id: 'landscape', width: 120, height: 80 },
plugins: { finetune: 1, filter: 1 },
ops: [
{ type: 'finetune.set', id: 'shared-1', params: { contrast: 0.2, saturation: 0.3 } },
{ type: 'filter.set', id: 'shared-2', preset: 'warm', strength: 0.6 },
],
};
const index = editLog.ops.length;
const result = await renderer.render({
original,
editLog,
upTo: index,
output: { format: 'image/png' },
});
console.log(
`${result.width}x${result.height} ${result.bytes.byteLength} bytes sha256 ${result.hash}`,
);
console.log(
`The browser starters download landscape-${result.hash.slice(0, 8)}.png for the same log.`,
);TypeScript
import type { SealpixelEditor } from '@sealpixel/element';
import type { ExportResult, SealpixelError, SourceDescriptor } from '@sealpixel/core';SealpixelEditor is the element class; document.querySelector('sealpixel-editor') as SealpixelEditor gives typed properties and methods (getSession, getEditLog, undo, redo, export, download).