From 5289b4a1cce42fc87699eb8d136fc7e47470b58a Mon Sep 17 00:00:00 2001 From: Daniel Hauschildt Date: Mon, 12 Feb 2024 10:07:01 +0100 Subject: [PATCH] wip: Polyfill experiements --- TODO.md | 18 +- examples/web/package.json | 3 +- examples/web/src/App.tsx | 49 ++- examples/web/src/CommandPalette.tsx | 24 +- examples/web/src/addPlugins.ts | 8 +- package.json | 5 +- packages/background-removal/package.json | 3 +- .../src/registerComponents.ts | 1 + packages/polyfill-commands/LICENSE.md | 1 + packages/polyfill-commands/README.md | 40 +++ packages/polyfill-commands/STRUCTURE.md | 1 + .../TODO.md} | 0 packages/polyfill-commands/esbuild/config.mjs | 58 ++++ .../polyfill-commands/esbuild/global.d.ts | 3 + packages/polyfill-commands/package.json | 66 ++++ packages/polyfill-commands/scripts/build.mjs | 5 + packages/polyfill-commands/scripts/watch.mjs | 19 ++ packages/polyfill-commands/src/index.ts | 33 ++ packages/polyfill-commands/src/manifest.ts | 4 + .../polyfill-commands/src/utils/polyfills.ts | 31 ++ packages/polyfill-commands/src/worker.ts | 0 packages/polyfill-commands/tsconfig.json | 16 + .../types/index.d.ts} | 8 +- .../polyfill-commands/types/manifest.d.ts | 4 + .../types/utils/polyfills.d.ts | 12 + packages/polyfill-commands/types/worker.d.ts | 0 packages/vectorizer/package.json | 6 +- packages/vectorizer/src/commands.ts | 301 +++++++++--------- packages/vectorizer/src/defaults.ts | 0 packages/vectorizer/src/i18n.ts | 2 +- packages/vectorizer/src/index.ts | 81 +++-- packages/vectorizer/src/manifest.ts | 14 +- packages/vectorizer/src/ui.ts | 30 +- packages/vectorizer/src/{utils => }/utils.ts | 19 +- packages/vectorizer/src/utils/constants.ts | 8 - packages/vectorizer/src/utils/polyfills.ts | 8 +- .../vectorizer/src/utils/worker.shared.ts | 5 +- packages/vectorizer/types/actions.d.ts | 7 - packages/vectorizer/types/cesdk+utils.d.ts | 3 - packages/vectorizer/types/commands.d.ts | 2 +- packages/vectorizer/types/defaults.d.ts | 0 packages/vectorizer/types/manifest.d.ts | 8 +- packages/vectorizer/types/manitfest.d.ts | 9 - .../vectorizer/types/proposal/actions.d.ts | 7 - packages/vectorizer/types/proposal/i18n.d.ts | 15 - .../vectorizer/types/proposal/manitfest.d.ts | 6 - packages/vectorizer/types/proposal/ui.d.ts | 4 - .../vectorizer/types/proposal/worker.d.ts | 1 - packages/vectorizer/types/types.d.ts | 34 -- packages/vectorizer/types/ui.d.ts | 2 +- packages/vectorizer/types/utils.d.ts | 8 +- .../vectorizer/types/utils/cesdk+utils.d.ts | 2 +- .../vectorizer/types/utils/constants.d.ts | 4 +- .../vectorizer/types/utils/polyfills.d.ts | 2 +- packages/vectorizer/types/utils/utils.d.ts | 6 + .../vectorizer/types/utils/worker.shared.d.ts | 2 +- packages/vectorizer/types/worker.shared.d.ts | 6 - yarn.lock | 7 +- 58 files changed, 643 insertions(+), 378 deletions(-) create mode 100644 packages/polyfill-commands/LICENSE.md create mode 100644 packages/polyfill-commands/README.md create mode 100644 packages/polyfill-commands/STRUCTURE.md rename packages/{vectorizer/types/utils/supports.d.ts => polyfill-commands/TODO.md} (100%) create mode 100644 packages/polyfill-commands/esbuild/config.mjs create mode 100644 packages/polyfill-commands/esbuild/global.d.ts create mode 100644 packages/polyfill-commands/package.json create mode 100644 packages/polyfill-commands/scripts/build.mjs create mode 100644 packages/polyfill-commands/scripts/watch.mjs create mode 100644 packages/polyfill-commands/src/index.ts create mode 100644 packages/polyfill-commands/src/manifest.ts create mode 100644 packages/polyfill-commands/src/utils/polyfills.ts create mode 100644 packages/polyfill-commands/src/worker.ts create mode 100644 packages/polyfill-commands/tsconfig.json rename packages/{vectorizer/types/plugin.d.ts => polyfill-commands/types/index.d.ts} (52%) create mode 100644 packages/polyfill-commands/types/manifest.d.ts create mode 100644 packages/polyfill-commands/types/utils/polyfills.d.ts create mode 100644 packages/polyfill-commands/types/worker.d.ts create mode 100644 packages/vectorizer/src/defaults.ts rename packages/vectorizer/src/{utils => }/utils.ts (94%) delete mode 100644 packages/vectorizer/src/utils/constants.ts delete mode 100644 packages/vectorizer/types/actions.d.ts delete mode 100644 packages/vectorizer/types/cesdk+utils.d.ts create mode 100644 packages/vectorizer/types/defaults.d.ts delete mode 100644 packages/vectorizer/types/manitfest.d.ts delete mode 100644 packages/vectorizer/types/proposal/actions.d.ts delete mode 100644 packages/vectorizer/types/proposal/i18n.d.ts delete mode 100644 packages/vectorizer/types/proposal/manitfest.d.ts delete mode 100644 packages/vectorizer/types/proposal/ui.d.ts delete mode 100644 packages/vectorizer/types/proposal/worker.d.ts delete mode 100644 packages/vectorizer/types/types.d.ts delete mode 100644 packages/vectorizer/types/worker.shared.d.ts diff --git a/TODO.md b/TODO.md index cea5d94..eda1223 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,13 @@ -- ? How can I list all plugins that are active -- ? How can I deactivate a plugin -- ? we should pass the PluginContext with seperate `{engine?: ImglyEngine, editor?: ImglyEditor, ui?: ImglyUI` - -- `feature.isEnabled` should be feature.isEnabledFor(blockId) -> test for multiple blockIds at once +- [ ] How can I list all plugins that are active +- [ ] How can I deactivate a plugin later completely? `enableFeatures`, but this must be per "command". A plugin might contribute multiple features? +- [ ] we should pass the PluginContext with separate `imgly: {engine?: ImglyEngine, editor?: ImglyEditor, ui?: ImglyUI` +- [ ] We can already establish the name 'imgly" for the PLUGINS +- [ ] `unstable_getCanvasMenuOrder` should maybe be called `unstable_getCanvasMenuEntries` +- [ ] `unstable_setCanvasMenuOrder` should maybe be called `unstable_setCanvasMenuEntries` +- [ ] `unstable_enableFeatures` what is it used for. the button is not displayed when I check it internally +- [ ] `unstable_enableFeatures` should get the blocks it should evaluate it for. It's not always the selected ones in every scenario. +- [ ] `enable_features` could probably better be named `enableFeatureInContext()` +- [ ] What is the intention of the `builder.Button` first parameter, where is the id used later? +- [ ] (Exkurs) How can I change the type of a block to another. E.g. Change a Graphic Block into a Group Block for Vectorizer and the ID should stay the same and all properties that are relevant. "Turn into" +- [ ] The separation of ui and engine is sometimes too hard to use. I propose not to make it dependent on initUI and init. But more like lifecycles in general and always pass {ui?, engine, editor} +- [ ] `upload` should maybe be part of the asset sources and/or part of engine diff --git a/examples/web/package.json b/examples/web/package.json index 65353aa..bba735c 100644 --- a/examples/web/package.json +++ b/examples/web/package.json @@ -14,7 +14,8 @@ "@imgly/plugin-vectorizer-web": "*", "react": "^18.2.0", "react-cmdk": "^1.3.9", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "share-api-polyfill": "^1.1.1" }, "devDependencies": { "@types/react": "^18.2.43", diff --git a/examples/web/src/App.tsx b/examples/web/src/App.tsx index bf44e1c..e459b7d 100644 --- a/examples/web/src/App.tsx +++ b/examples/web/src/App.tsx @@ -3,27 +3,52 @@ import CreativeEditorSDK, { Configuration } from "@cesdk/cesdk-js"; import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web'; import VectorizerPlugin, { Manifest as VectorizerManifest } from '@imgly/plugin-vectorizer-web'; -import { ActionsMenu } from "./CommandPalette" +import { CommandPalette } from "./CommandPalette" +import 'share-api-polyfill'; const plugins = [VectorizerPlugin(), BackgroundRemovalPlugin()] +function downloadBlob(blob: Blob, filename: string) { + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + link.click(); + URL.revokeObjectURL(url); +} + +const downloadBlocks = (cesdk: CreativeEditorSDK, blobs: Blob[], options: { mimeType: string, pages?: number[] }) => { + const postfix = options.mimeType.split("/")[1] + const pageIds = options.pages ?? [] + blobs.forEach((blob, index) => { + const pageId = pageIds[index] + let pageName = `page-${index}` + if (pageId) { + const name = cesdk.engine.block.getName(pageId) + pageName = name?.length ? name : pageName + } + const filename = `${pageName}.${postfix}`; + downloadBlob(blob, filename); + }) + return Promise.resolve(); +} function App() { const cesdk = useRef(); const config: Configuration = { license: import.meta.env.VITE_CESDK_LICENSE_KEY, - callbacks: { + callbacks: { onUpload: "local", onDownload: "download", - onSave: (s) => { - console.log("Save", s); - return Promise.resolve(); - }, - onExport: (blobs, options) => { - // why does this only export 1 page - console.log("Export", blobs, options); - return Promise.resolve(); + onSave: async (str: string) => { + // improve + return downloadBlocks(cesdk.current!, [new Blob([str])], { mimeType: 'application/imgly' }) + }, + + onExport: async (blobs, options) => { + return downloadBlocks(cesdk.current!, blobs, { mimeType: options.mimeType, pages: options.pages }) + }, onLoad: "upload", }, @@ -41,7 +66,7 @@ function App() { save: true, load: true, export: true, - share: true, + // share: true, } } } @@ -51,7 +76,7 @@ function App() { return ( <> - +
{ diff --git a/examples/web/src/CommandPalette.tsx b/examples/web/src/CommandPalette.tsx index 4252e57..e33087c 100644 --- a/examples/web/src/CommandPalette.tsx +++ b/examples/web/src/CommandPalette.tsx @@ -1,10 +1,10 @@ import "react-cmdk/dist/cmdk.css"; -import CommandPalette, { filterItems, getItemIndex } from "react-cmdk"; +import CMDK , { filterItems, getItemIndex } from "react-cmdk"; import { useState, useEffect, RefObject } from "react"; import CreativeEditorSDK from "@cesdk/cesdk-js"; // https://github.com/albingroen/react-cmdk -export const ActionsMenu = (params: { cesdkRef: RefObject, actions: Array }) => { +export const CommandPalette = (params: { cesdkRef: RefObject, actions: Array }) => { const [page, _setPage] = useState<"root">("root"); const [search, setSearch] = useState(""); const [isOpen, setIsOpen] = useState(false); @@ -138,8 +138,8 @@ export const ActionsMenu = (params: { cesdkRef: RefObject { return { id: action.id, @@ -158,32 +158,32 @@ export const ActionsMenu = (params: { cesdkRef: RefObject - + {filteredItems.length ? ( filteredItems.map((list) => ( - + {list.items.map(({ id, ...rest }) => ( - ))} - + )) ) : ( - + )} - + - + ); }; diff --git a/examples/web/src/addPlugins.ts b/examples/web/src/addPlugins.ts index a08ada0..31af477 100644 --- a/examples/web/src/addPlugins.ts +++ b/examples/web/src/addPlugins.ts @@ -1,10 +1,12 @@ import CreativeEditorSDK from '@cesdk/cesdk-js'; - +// import PolyfillCommandsPlugin from "@imgly/plugin-polyfill-commands" import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web'; import VectorizerPlugin from '@imgly/plugin-vectorizer-web'; -const plugins = [VectorizerPlugin(), BackgroundRemovalPlugin()] - +const plugins = [ + // PolyfillCommandsPlugin(), + VectorizerPlugin(), + BackgroundRemovalPlugin()] async function addPlugins(cesdk: CreativeEditorSDK) { try { diff --git a/package.json b/package.json index 859014b..f18858a 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,9 @@ "name": "imgly-plugins", "version": "0.0.0", "workspaces": [ - "examples/*", - "packages/*" + "examples/web", + "packages/background-removal", + "packages/vectorizer" ], "scripts": { "build": "turbo run build --force", diff --git a/packages/background-removal/package.json b/packages/background-removal/package.json index f27f946..f9ee4f7 100644 --- a/packages/background-removal/package.json +++ b/packages/background-removal/package.json @@ -14,7 +14,7 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/imgly/plugin-background-removal-web.git" + "url": "git+https://github.com/imgly/plugins.git" }, "license": "SEE LICENSE IN LICENSE.md", "author": { @@ -57,7 +57,6 @@ "types:create": "tsc --emitDeclarationOnly" }, "devDependencies": { - "@cesdk/cesdk-js": "~1.20.0", "@types/ndarray": "^1.0.14", "chalk": "^5.3.0", "concurrently": "^8.2.2", diff --git a/packages/background-removal/src/registerComponents.ts b/packages/background-removal/src/registerComponents.ts index ac4039b..a0821bd 100644 --- a/packages/background-removal/src/registerComponents.ts +++ b/packages/background-removal/src/registerComponents.ts @@ -36,6 +36,7 @@ export function registerComponents(cesdk: CreativeEditorSDK) { return; } + // Why is that needed. The feature enable should already handle that const [id] = engine.block.findAllSelected(); if (!cesdk.engine.block.hasFill(id)) return; diff --git a/packages/polyfill-commands/LICENSE.md b/packages/polyfill-commands/LICENSE.md new file mode 100644 index 0000000..a099036 --- /dev/null +++ b/packages/polyfill-commands/LICENSE.md @@ -0,0 +1 @@ +TBD diff --git a/packages/polyfill-commands/README.md b/packages/polyfill-commands/README.md new file mode 100644 index 0000000..e4197b5 --- /dev/null +++ b/packages/polyfill-commands/README.md @@ -0,0 +1,40 @@ +# IMG.LY CE.SDK Plugin Vectorizer + +This plugin introduces a vectorizer for the CE.SDK editor. + +## Installation + +You can install the plugin via npm or yarn. Use the following commands to install the package: + +``` +yarn add @imgly/plugin-vectorizer-web +npm install @imgly/plugin-vectorizer-web +``` + +## Usage + +Adding the plugin to CE.SDK will automatically add a vectorizer +canvas menu entry for every block with an image fill. + +```typescript +import CreativeEditorSDK from '@cesdk/cesdk-js'; +import VectorizerPlugin from '@imgly/plugin-vectorizer-web'; + +const config = { + license: '', + callbacks: { + // Please note that the vectorizer plugin depends on an correctly + // configured upload. 'local' will work for local testing, but in + // production you will need something stable. Please take a look at: + // https://img.ly/docs/cesdk/ui/guides/upload-images/ + onUpload: 'local' + } +}; + +const cesdk = await CreativeEditorSDK.create(container, config); +await cesdk.addDefaultAssetSources(), + await cesdk.addDemoAssetSources({ sceneMode: 'Design' }), + await cesdk.unstable_addPlugin(VectorizerPlugin()); + +await cesdk.createDesignScene(); +``` diff --git a/packages/polyfill-commands/STRUCTURE.md b/packages/polyfill-commands/STRUCTURE.md new file mode 100644 index 0000000..13b1472 --- /dev/null +++ b/packages/polyfill-commands/STRUCTURE.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/vectorizer/types/utils/supports.d.ts b/packages/polyfill-commands/TODO.md similarity index 100% rename from packages/vectorizer/types/utils/supports.d.ts rename to packages/polyfill-commands/TODO.md diff --git a/packages/polyfill-commands/esbuild/config.mjs b/packages/polyfill-commands/esbuild/config.mjs new file mode 100644 index 0000000..37b437c --- /dev/null +++ b/packages/polyfill-commands/esbuild/config.mjs @@ -0,0 +1,58 @@ +import chalk from 'chalk'; +import { readFile } from 'fs/promises'; + +// import packageJson from '../package.json' assert { type: 'json' }; +// Avoid the Experimental Feature warning when using the above. +const packageJson = JSON.parse( + await readFile(new URL('../package.json', import.meta.url)) +); + + +const dependencies = Object.keys(packageJson.dependencies) +const peerDependencies = Object.keys(packageJson.peerDependencies) +const externals = [...dependencies, ...peerDependencies] + +console.log( + chalk.yellow('Building version: '), + chalk.green(packageJson.version) +); + +const configs = [ + { + entryPoints: ['src/index.ts', "src/worker.ts"], + define: { + PLUGIN_VERSION: `"${packageJson.version}"` + }, + minify: true, + bundle: true, + sourcemap: true, + external: externals, + platform: 'node', + format: 'esm', + outdir: 'dist', + outExtension: { '.js': '.mjs' }, + plugins: [ + { + name: 'reporter', + setup(build) { + build.onEnd((result) => { + console.log( + `[${new Date().toLocaleTimeString(undefined, { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + hour12: false + })}] Build ${ + result.errors.length + ? chalk.red('failed') + : chalk.green('succeeded') + }` + ); + }); + } + } + ] + } +]; + +export default configs; diff --git a/packages/polyfill-commands/esbuild/global.d.ts b/packages/polyfill-commands/esbuild/global.d.ts new file mode 100644 index 0000000..de80fd8 --- /dev/null +++ b/packages/polyfill-commands/esbuild/global.d.ts @@ -0,0 +1,3 @@ +// These constants here are added by the base esbuild config + +declare const PLUGIN_VERSION: string; diff --git a/packages/polyfill-commands/package.json b/packages/polyfill-commands/package.json new file mode 100644 index 0000000..f07b6cc --- /dev/null +++ b/packages/polyfill-commands/package.json @@ -0,0 +1,66 @@ +{ + "name": "@imgly/plugin-polyfill-commands", + "version": "0.1.0", + "description": "Polyfill for Commands plugin for the CE.SDK editor", + "keywords": [ + "CE.SDK", + "plugin", + "polyfill" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/imgly/plugins.git" + }, + "license": "SEE LICENSE IN LICENSE.md", + "author": { + "name": "IMG.LY GmbH", + "email": "support@img.ly", + "url": "https://img.ly" + }, + "bugs": { + "email": "support@img.ly" + }, + "source": "./src/index.ts", + "module": "./dist/index.mjs", + "types": "./types/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "types": "./types/index.d.ts" + } + }, + "homepage": "https://img.ly", + "files": [ + "LICENSE.md", + "README.md", + "CHANGELOG.md", + "dist/", + "types/", + "bin/" + ], + "scripts": { + "start": "npm run watch", + "clean": "npx rimraf dist && npx rimraf types", + "build": "yarn run types:create && node scripts/build.mjs", + "dev": "yarn run types:create && node scripts/watch.mjs", + "publish:latest": "npm run clean && npm run build && npm publish --tag latest --access public", + "publish:next": "npm run clean && npm run build && npm publish --tag next --access public", + "check:all": "concurrently -n lint,type,pretty \"yarn check:lint\" \"yarn check:type\" \"yarn check:pretty\"", + "check:lint": "eslint --max-warnings 0 './src/**/*.{ts,tsx}'", + "check:pretty": "prettier --list-different './src/**/*.{ts,tsx}'", + "check:type": "tsc --noEmit", + "types:create": "tsc --emitDeclarationOnly" + }, + "devDependencies": { + "chalk": "^5.3.0", + "concurrently": "^8.2.2", + "esbuild": "^0.19.11", + "eslint": "^8.51.0", + "typescript": "^5.3.3" + }, + "peerDependencies": { + "@cesdk/cesdk-js": "~1.20.0" + }, + "dependencies": { + } +} diff --git a/packages/polyfill-commands/scripts/build.mjs b/packages/polyfill-commands/scripts/build.mjs new file mode 100644 index 0000000..13d12e1 --- /dev/null +++ b/packages/polyfill-commands/scripts/build.mjs @@ -0,0 +1,5 @@ +import * as esbuild from 'esbuild'; + +import configs from '../esbuild/config.mjs'; + +await Promise.all(configs.map(async (config) => await esbuild.build(config))); diff --git a/packages/polyfill-commands/scripts/watch.mjs b/packages/polyfill-commands/scripts/watch.mjs new file mode 100644 index 0000000..15dbb21 --- /dev/null +++ b/packages/polyfill-commands/scripts/watch.mjs @@ -0,0 +1,19 @@ +import chalk from 'chalk'; +import * as esbuild from 'esbuild'; + +import configs from '../esbuild/config.mjs'; + +console.log( + `[${new Date().toLocaleTimeString(undefined, { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + hour12: false + })}] ${chalk.green('Watching...')}` +); + +const contexts = await Promise.all( + configs.map((config) => esbuild.context(config)) +); + +await Promise.any(contexts.map((ctx) => ctx.watch())); diff --git a/packages/polyfill-commands/src/index.ts b/packages/polyfill-commands/src/index.ts new file mode 100644 index 0000000..3aed9fb --- /dev/null +++ b/packages/polyfill-commands/src/index.ts @@ -0,0 +1,33 @@ +import CreativeEditorSDK, { CreativeEngine } from '@cesdk/cesdk-js'; + +import Manifest from './manifest'; + + +import { polyfillEngineWithCommands, CreativeEngineWithPolyfills } from './utils/polyfills'; + + +export interface PluginConfiguration { + // uploader ? +} + +export { Manifest }; + +export default (pluginConfiguration: PluginConfiguration = {}) => { + return { + initialize(engine: CreativeEngineWithPolyfills) { + + }, + initializeUserInterface({ cesdk }: { cesdk: CreativeEditorSDK }) { + const engine = cesdk.engine as CreativeEngineWithPolyfills; + polyfillEngineWithCommands(engine); + + }, + + + // maybe this should be just engint.event.onUpdate() + update() { + + }, + + }; +}; diff --git a/packages/polyfill-commands/src/manifest.ts b/packages/polyfill-commands/src/manifest.ts new file mode 100644 index 0000000..0972d63 --- /dev/null +++ b/packages/polyfill-commands/src/manifest.ts @@ -0,0 +1,4 @@ +export default { + id: "@imgly/polyfill-commands", + +} \ No newline at end of file diff --git a/packages/polyfill-commands/src/utils/polyfills.ts b/packages/polyfill-commands/src/utils/polyfills.ts new file mode 100644 index 0000000..399a757 --- /dev/null +++ b/packages/polyfill-commands/src/utils/polyfills.ts @@ -0,0 +1,31 @@ +import { CreativeEngine } from '@cesdk/cesdk-js'; + +export type CreativeEngineWithPolyfills = CreativeEngine & { polyfill_commands?: Commands }; + +export type CommandType = (params: any) => Promise; + +export class Commands { + #engine: CreativeEngineWithPolyfills + #entries = new Map() + constructor(engine: CreativeEngineWithPolyfills) { + this.#engine = engine; + } + registerCommand(label: string, callback: (params: any) => Promise) { + this.#entries.set(label, callback); + } + async executeCommand(label: string, params: any) { + const command = this.#entries.get(label); + if (command) { + await command(params); + } else { + throw new Error(`Command ${label} not found`); + } + } + +} +export function polyfillEngineWithCommands(engine: CreativeEngineWithPolyfills) { + //polyfill + if (!engine.polyfill_commands) { + engine.polyfill_commands = new Commands(engine); + } +} \ No newline at end of file diff --git a/packages/polyfill-commands/src/worker.ts b/packages/polyfill-commands/src/worker.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/polyfill-commands/tsconfig.json b/packages/polyfill-commands/tsconfig.json new file mode 100644 index 0000000..47d465c --- /dev/null +++ b/packages/polyfill-commands/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es2017", + "module": "es2020", + "lib": ["es2018", "dom"], + "moduleResolution": "node", + "isolatedModules": true, + "esModuleInterop": true, + "declaration": true, + "declarationDir": "types/", + "skipLibCheck": true + }, + "include": ["src/**/*", "esbuild/global.d.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/vectorizer/types/plugin.d.ts b/packages/polyfill-commands/types/index.d.ts similarity index 52% rename from packages/vectorizer/types/plugin.d.ts rename to packages/polyfill-commands/types/index.d.ts index 53dc685..7e77099 100644 --- a/packages/vectorizer/types/plugin.d.ts +++ b/packages/polyfill-commands/types/index.d.ts @@ -1,12 +1,14 @@ -import CreativeEditorSDK, { CreativeEngine } from '@cesdk/cesdk-js'; +import CreativeEditorSDK from '@cesdk/cesdk-js'; +import Manifest from './manifest'; +import { CreativeEngineWithPolyfills } from './utils/polyfills'; export interface PluginConfiguration { } +export { Manifest }; declare const _default: (pluginConfiguration?: PluginConfiguration) => { - initialize(engine: CreativeEngine): void; + initialize(engine: CreativeEngineWithPolyfills): void; initializeUserInterface({ cesdk }: { cesdk: CreativeEditorSDK; }): void; update(): void; }; export default _default; -export declare function enableFeatures(cesdk: CreativeEditorSDK): void; diff --git a/packages/polyfill-commands/types/manifest.d.ts b/packages/polyfill-commands/types/manifest.d.ts new file mode 100644 index 0000000..e1ead83 --- /dev/null +++ b/packages/polyfill-commands/types/manifest.d.ts @@ -0,0 +1,4 @@ +declare const _default: { + id: string; +}; +export default _default; diff --git a/packages/polyfill-commands/types/utils/polyfills.d.ts b/packages/polyfill-commands/types/utils/polyfills.d.ts new file mode 100644 index 0000000..ad9669b --- /dev/null +++ b/packages/polyfill-commands/types/utils/polyfills.d.ts @@ -0,0 +1,12 @@ +import { CreativeEngine } from '@cesdk/cesdk-js'; +export type CreativeEngineWithPolyfills = CreativeEngine & { + polyfill_commands?: Commands; +}; +export type CommandType = (params: any) => Promise; +export declare class Commands { + #private; + constructor(engine: CreativeEngineWithPolyfills); + registerCommand(label: string, callback: (params: any) => Promise): void; + executeCommand(label: string, params: any): Promise; +} +export declare function polyfillEngineWithCommands(engine: CreativeEngineWithPolyfills): void; diff --git a/packages/polyfill-commands/types/worker.d.ts b/packages/polyfill-commands/types/worker.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/vectorizer/package.json b/packages/vectorizer/package.json index 89bfd4c..30cfcef 100644 --- a/packages/vectorizer/package.json +++ b/packages/vectorizer/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/imgly/plugin-vectorizer-web.git" + "url": "git+https://github.com/imgly/plugins.git" }, "license": "SEE LICENSE IN LICENSE.md", "author": { @@ -30,7 +30,7 @@ "types": "./types/index.d.ts" } }, - "homepage": "https://img.ly/products/creative-sdk", + "homepage": "https://img.ly", "files": [ "LICENSE.md", "README.md", @@ -53,8 +53,6 @@ "types:create": "tsc --emitDeclarationOnly" }, "devDependencies": { - "@cesdk/cesdk-js": "~1.20.0", - "@types/ndarray": "^1.0.14", "chalk": "^5.3.0", "concurrently": "^8.2.2", "esbuild": "^0.19.11", diff --git a/packages/vectorizer/src/commands.ts b/packages/vectorizer/src/commands.ts index ebe9246..3f08b4a 100644 --- a/packages/vectorizer/src/commands.ts +++ b/packages/vectorizer/src/commands.ts @@ -1,6 +1,7 @@ import type CreativeEditorSDK from '@cesdk/cesdk-js'; -import { PLUGIN_ACTION_VECTORIZE_LABEL } from './utils/constants'; +import { PLUGIN_ACTION_VECTORIZE_LABEL } from './manifest'; + import { getPluginMetadata, @@ -8,163 +9,163 @@ import { isMetadataConsistent, recoverInitialImageData, setPluginMetadata -} from './utils/utils'; + +} from './utils'; import { runInWorker } from './utils/worker.shared'; import { createVectorPathBlocks } from './utils/cesdk+utils'; -const vectorize = async (cesdk: CreativeEditorSDK, params: { blockId: number }) => { + +// FIXME: The vectorize technically does not need image fills, it can vectorize every block by rendering the block and then processing the image +const vectorize = async (cesdk: CreativeEditorSDK, params: { blockIds?: number[] }) => { const uploader = cesdk.unstable_upload.bind(cesdk) const engine = cesdk.engine; // the only function that needs the ui is the upload function const blockApi = engine.block; - let { blockId } = params ?? {}; - if (blockId === undefined) { - const selected = engine.block.findAllSelected() - if (selected.length !== 1) { - return; - } - blockId = selected[0]; - } - const isValid = engine.block.isValid(blockId) - if (!isValid) return - - if (!isBlockSupported(engine, blockId)) return; + // this shouldn't be necessay here + const blockIds = params.blockIds ?? engine.block.findAllSelected(); + blockIds.forEach(async blockId => { + // this should happen before already and only be called if the feature is enabled for a certain block + if (!isBlockSupported(engine, blockId)) return; - if (!blockApi.hasFill(blockId)) - throw new Error('Block has no fill to vectorize'); - const fillId = blockApi.getFill(blockId); + if (!blockApi.hasFill(blockId)) + throw new Error('Block has no fill to vectorize'); + const fillId = blockApi.getFill(blockId); - // Get the current image URI and source set as initial values. - const initialSourceSet = blockApi.getSourceSet( - fillId, - 'fill/image/sourceSet' - ); - const initialImageFileURI = blockApi.getString( - fillId, - 'fill/image/imageFileURI' - ); - const initialPreviewFileURI = blockApi.getString( - fillId, - 'fill/image/previewFileURI' - ); + // FIXME: Tis is only needed to tell the engin that we are processing something and it cannot export or save the scene file. + // Practicalle, we are not using the images directly but render the visible part of the block and then process this image + // Get the current image URI and source set as initial values. + const initialSourceSet = blockApi.getSourceSet(fillId, 'fill/image/sourceSet'); + const initialImageFileURI = blockApi.getString(fillId, 'fill/image/imageFileURI'); + const initialPreviewFileURI = blockApi.getString(fillId, 'fill/image/previewFileURI'); - const uriToProcess = - // Source sets have priority in the engine - initialSourceSet.length > 0 - ? // Choose the highest resolution image in the source set - initialSourceSet.sort( - (a, b) => b.width * b.height - a.height * a.width - )[0].uri - : initialImageFileURI; + const uriToProcess = + // Source sets have priority in the engine + initialSourceSet.length > 0 + ? // Choose the highest resolution image in the source set + initialSourceSet.sort( + (a, b) => b.width * b.height - a.height * a.width + )[0].uri + : initialImageFileURI; - if (uriToProcess === undefined || uriToProcess === '') - return; // We shall return early if the uri is not defined or invalid + if (uriToProcess === undefined || uriToProcess === '') + return; // We shall return early if the uri is not defined or invalid + try { + // Clear values in the engine to trigger the loading spinner + // @ts-ignore + const blob = await engine.block.export(blockId, "image/png"); + // go into busy state + blockApi.setString(fillId, 'fill/image/imageFileURI', ''); + blockApi.setSourceSet(fillId, 'fill/image/sourceSet', []); + // ensure we show the last image while processsing. Some images don't have the preview set + if (initialPreviewFileURI === undefined || initialPreviewFileURI === '') { + blockApi.setString(fillId, 'fill/image/previewFileURI', uriToProcess); + } - try { - // Clear values in the engine to trigger the loading spinner - // @ts-ignore - const blob = await engine.block.export(blockId, "image/png"); - - // go into busy state - blockApi.setString(fillId, 'fill/image/imageFileURI', ''); - blockApi.setSourceSet(fillId, 'fill/image/sourceSet', []); - // ensure we show the last image while processsing. Some images don't have the preview set - if (initialPreviewFileURI === undefined || initialPreviewFileURI === '') { - blockApi.setString(fillId, 'fill/image/previewFileURI', uriToProcess); - - - } - const metadata = getPluginMetadata(engine, blockId); - setPluginMetadata(engine, blockId, { - ...metadata, - version: PLUGIN_VERSION, - initialSourceSet, - initialImageFileURI, - blockId, - fillId, - status: 'PROCESSING' - }); - - const vectorized: Blob = await runInWorker(blob) - - if ( - getPluginMetadata(engine, blockId).status !== 'PROCESSING' || - !isMetadataConsistent(engine, blockId) - )return; - if (engine.block.isValid(blockId)) { + const metadata = getPluginMetadata(engine, blockId); setPluginMetadata(engine, blockId, { + ...metadata, version: PLUGIN_VERSION, initialSourceSet, initialImageFileURI, blockId, fillId, - status: 'PROCESSED', + status: 'PROCESSING' }); - } - - if (vectorized.type.length === 0 || vectorized.type === 'image/svg+xml') { - const pathname = new URL(uriToProcess).pathname; - const parts = pathname.split('/'); - const filename = parts[parts.length - 1]; + const vectorized: Blob = await runInWorker(blob) - const uploadedAssets = await uploader( - new File([vectorized], filename, { type: vectorized.type }), - () => { - // TODO Delegate process to UI component - } - ); - - // Check for externally changed state while we were uploading and - // do not proceed if the state was reset. if ( getPluginMetadata(engine, blockId).status !== 'PROCESSING' || !isMetadataConsistent(engine, blockId) - ) - return; - - const url = uploadedAssets.meta?.uri;; - if (url == null) { - throw new Error('Could not upload vectorized image'); + ) return; + if (engine.block.isValid(blockId)) { + setPluginMetadata(engine, blockId, { + version: PLUGIN_VERSION, + initialSourceSet, + initialImageFileURI, + blockId, + fillId, + status: 'PROCESSED', + }); } - // Workaround Processing is done, restore state of the initial block - blockApi.setSourceSet(fillId, 'fill/image/sourceSet', initialSourceSet); - blockApi.setString(fillId, 'fill/image/imageFileURI', initialImageFileURI); - blockApi.setString(fillId, 'fill/image/previewFileURI', initialPreviewFileURI); - setPluginMetadata(engine, blockId, { - version: PLUGIN_VERSION, - initialSourceSet, - initialImageFileURI, - blockId, - fillId, - status: 'PROCESSED', - }); + if (vectorized.type.length === 0 || vectorized.type === 'image/svg+xml') { + const pathname = new URL(uriToProcess).pathname; + const parts = pathname.split('/'); + const filename = parts[parts.length - 1]; - blockApi.setString(fillId, 'fill/image/imageFileURI', url); - } else if (vectorized.type === 'application/json') { - - const json = await vectorized.text() - const blocks = JSON.parse(json) - const blockIds = createVectorPathBlocks(engine, blocks) - - const origRotation = engine.block.getRotation(blockId) - const origX = engine.block.getPositionX(blockId) - const origY = engine.block.getPositionY(blockId) - const origSelected = engine.block.isSelected(blockId) - - switch (engine.block.getType(blockId)) { - case "//ly.img.ubq/page": - { - const parentId = blockId; + const uploadedAssets = await uploader( + new File([vectorized], filename, { type: vectorized.type }), + () => { + // TODO Delegate process to UI component + } + ); + + // Check for externally changed state while we were uploading and + // do not proceed if the state was reset. + if ( + getPluginMetadata(engine, blockId).status !== 'PROCESSING' || + !isMetadataConsistent(engine, blockId) + ) + return; + + const url = uploadedAssets.meta?.uri;; + if (url == null) { + throw new Error('Could not upload vectorized image'); + } + + // Workaround Processing is done, restore state of the initial block + blockApi.setSourceSet(fillId, 'fill/image/sourceSet', initialSourceSet); + blockApi.setString(fillId, 'fill/image/imageFileURI', initialImageFileURI); + blockApi.setString(fillId, 'fill/image/previewFileURI', initialPreviewFileURI); + + setPluginMetadata(engine, blockId, { + version: PLUGIN_VERSION, + initialSourceSet, + initialImageFileURI, + blockId, + fillId, + status: 'PROCESSED', + }); + + blockApi.setString(fillId, 'fill/image/imageFileURI', url); + } else if (vectorized.type === 'application/json') { + + const json = await vectorized.text() + const blocks = JSON.parse(json) + const blockIds = createVectorPathBlocks(engine, blocks) + + const origRotation = engine.block.getRotation(blockId) + const origX = engine.block.getPositionX(blockId) + const origY = engine.block.getPositionY(blockId) + const origSelected = engine.block.isSelected(blockId) + + switch (engine.block.getType(blockId)) { + case "//ly.img.ubq/page": + { + const parentId = blockId; + const containerId = engine.block.group(blockIds); + engine.block.appendChild(parentId, containerId); + const scale = engine.block.getFrameWidth(blockId) / engine.block.getFrameWidth(containerId) + engine.block.setPositionX(containerId, origX) + engine.block.setPositionY(containerId, origY) + engine.block.setRotation(containerId, origRotation) + engine.block.scale(containerId, scale) + engine.block.setFillEnabled(parentId, false) + engine.block.setSelected(containerId, origSelected) + break; + } + case "//ly.img.ubq/graphic": + default: { // replace the current block with the a new group of the vectors + const parentId = engine.block.getParent(blockId)! const containerId = engine.block.group(blockIds); engine.block.appendChild(parentId, containerId); const scale = engine.block.getFrameWidth(blockId) / engine.block.getFrameWidth(containerId) @@ -172,45 +173,33 @@ const vectorize = async (cesdk: CreativeEditorSDK, params: { blockId: number }) engine.block.setPositionY(containerId, origY) engine.block.setRotation(containerId, origRotation) engine.block.scale(containerId, scale) - engine.block.setFillEnabled(parentId, false) + engine.block.destroy(blockId) engine.block.setSelected(containerId, origSelected) break; } - case "//ly.img.ubq/graphic": - default: { // replace the current block with the a new group of the vectors - const parentId = engine.block.getParent(blockId)! - const containerId = engine.block.group(blockIds); - engine.block.appendChild(parentId, containerId); - const scale = engine.block.getFrameWidth(blockId) / engine.block.getFrameWidth(containerId) - engine.block.setPositionX(containerId, origX) - engine.block.setPositionY(containerId, origY) - engine.block.setRotation(containerId, origRotation) - engine.block.scale(containerId, scale) - engine.block.destroy(blockId) - engine.block.setSelected(containerId, origSelected) - break; } } + // Finally, create an undo step + engine.editor.addUndoStep(); + + } catch (error) { + if (engine.block.isValid(blockId)) { + setPluginMetadata(engine, blockId, { + version: PLUGIN_VERSION, + initialSourceSet, + initialImageFileURI, + blockId, + fillId, + status: 'ERROR' + }); + + recoverInitialImageData(engine, blockId); + } + // eslint-disable-next-line no-console + console.error(error); } - // Finally, create an undo step - engine.editor.addUndoStep(); - - } catch (error) { - if (engine.block.isValid(blockId)) { - setPluginMetadata(engine, blockId, { - version: PLUGIN_VERSION, - initialSourceSet, - initialImageFileURI, - blockId, - fillId, - status: 'ERROR' - }); - - recoverInitialImageData(engine, blockId); - } - // eslint-disable-next-line no-console - console.error(error); - } + }) } + export default { [PLUGIN_ACTION_VECTORIZE_LABEL]: vectorize } diff --git a/packages/vectorizer/src/defaults.ts b/packages/vectorizer/src/defaults.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/vectorizer/src/i18n.ts b/packages/vectorizer/src/i18n.ts index ee99b0d..90b5033 100644 --- a/packages/vectorizer/src/i18n.ts +++ b/packages/vectorizer/src/i18n.ts @@ -1,4 +1,4 @@ -import { PLUGIN_ACTION_VECTORIZE_LABEL } from './utils/constants' +import { PLUGIN_ACTION_VECTORIZE_LABEL } from './manifest' export const en = { [PLUGIN_ACTION_VECTORIZE_LABEL]: 'Turn into Vector' } export const de = { [PLUGIN_ACTION_VECTORIZE_LABEL]: 'Wandle in Vector' } diff --git a/packages/vectorizer/src/index.ts b/packages/vectorizer/src/index.ts index aafe740..1d12e3b 100644 --- a/packages/vectorizer/src/index.ts +++ b/packages/vectorizer/src/index.ts @@ -3,18 +3,17 @@ import CreativeEditorSDK, { CreativeEngine } from '@cesdk/cesdk-js'; import ui from './ui'; import commands from './commands'; import i18n from './i18n'; -import Manifest from './manifest'; +import Manifest, { PLUGIN_ACTION_VECTORIZE_LABEL, PLUGIN_COMPONENT_BUTTON_ID, PLUGIN_ID } from './manifest'; -import { PLUGIN_ID } from './utils/constants'; -import { PLUGIN_CANVAS_MENU_COMPONENT_ID } from './utils/constants'; import { polyfillEngineWithCommands, CreativeEngineWithPolyfills } from './utils/polyfills'; import { clearPluginMetadata, fixDuplicateMetadata, getPluginMetadata, isDuplicate, - isMetadataConsistent -} from './utils/utils'; + isMetadataConsistent, + areBlocksSupported +} from './utils'; export interface PluginConfiguration { @@ -28,36 +27,43 @@ export default (pluginConfiguration: PluginConfiguration = {}) => { id: PLUGIN_ID, version: PLUGIN_VERSION, initialize(engine: CreativeEngineWithPolyfills) { - + // it is unclear for a user which one to call and what happens and if we have to put code in both or just one + // we should have a clear separation of concerns + // also maybe better naming + // onInitEngine + // onInitUI }, initializeUserInterface({ cesdk }: { cesdk: CreativeEditorSDK }) { - const engine = cesdk.engine as CreativeEngineWithPolyfills; - polyfillEngineWithCommands(engine); - console.log("checking if engine has polyfill_commands", engine.polyfill_commands? "yes": "no") + // This should move into a seperate plugin + const engine = polyfillEngineWithCommands(cesdk.engine); + if (!engine.polyfill_commands) { + console.error("Polyfill engine.commands not available!") + return; + } + + console.log("Subscribing to events"); engine.event.subscribe([], async (events) => { events - .filter((e) => engine.block.isValid(e.block) && engine.block.hasMetadata(e.block, PLUGIN_ID)) - .forEach((e) => { - const id = e.block; - if (e.type === 'Created') { - const metadata = getPluginMetadata(engine, id); - if (isDuplicate(engine, id, metadata)) { - fixDuplicateMetadata(engine, id); + .filter(({ block: blockId }) => engine.block.isValid(blockId) && engine.block.hasMetadata(blockId, PLUGIN_ID)) + .forEach(({ type, block: blockId }) => { + if (type === 'Created') { + const metadata = getPluginMetadata(engine, blockId); + if (isDuplicate(engine, blockId, metadata)) { + fixDuplicateMetadata(engine, blockId); } } }); }); - console.log("checking if engine has polyfill_commands", engine.polyfill_commands? "yes": "no") + console.log("checking if engine has polyfill_commands", engine.polyfill_commands ? "yes" : "no") engine.event.subscribe([], async (events) => { events - .filter((e) => engine.block.isValid(e.block) && cesdk.engine.block.hasMetadata(e.block, PLUGIN_ID)) - .filter((e) => e.type === 'Updated') - .forEach((e) => { handleUpdateEvent(cesdk, e.block); }); + .filter(e => engine.block.isValid(e.block) && cesdk.engine.block.hasMetadata(e.block, PLUGIN_ID)) + .filter(e => e.type === 'Updated') + .forEach(e => handleUpdateEvent(engine, e.block)) }); - console.info("Registering plugin actions") Object.keys(commands).forEach((action) => { console.info(`Registering action: ${action}`) @@ -80,16 +86,29 @@ export default (pluginConfiguration: PluginConfiguration = {}) => { cesdk.ui.unstable_registerComponent(componentId, component); }) - // Always prepend the registered component to the canvas menu order. + // DEFAULTS + // define what blocks the component button is enabled for + // WHERE IS THIS USED? AND HOW DOES IT WORK? It seems the button is shown no matter what. + console.info("Enabling plugin component button for supported blocks") + cesdk.feature.unstable_enable(PLUGIN_COMPONENT_BUTTON_ID, (context: any) => { + return areBlocksSupported(engine, engine.block.findAllSelected()) + }) + + cesdk.feature.unstable_enable(PLUGIN_COMPONENT_BUTTON_ID, false); + cesdk.feature.unstable_enable(PLUGIN_ID, false); + cesdk.feature.unstable_enable(PLUGIN_ACTION_VECTORIZE_LABEL, false); + + console.info("Changing canvas menu order") - cesdk.ui.unstable_setCanvasMenuOrder([ - PLUGIN_CANVAS_MENU_COMPONENT_ID, + const canvasMenuEntries = [ + PLUGIN_COMPONENT_BUTTON_ID, ...cesdk.ui.unstable_getCanvasMenuOrder() - ]); + ] + cesdk.ui.unstable_setCanvasMenuOrder(canvasMenuEntries); }, - // maybe this should be just engint.event.onUpdate() + // maybe this should be just engine.event.onUpdate() update() { }, @@ -101,15 +120,13 @@ export default (pluginConfiguration: PluginConfiguration = {}) => { * Handle every possible state of the vectorization state if the block was * updated. */ -async function handleUpdateEvent(cesdk: CreativeEditorSDK, blockId: number) { - const metadata = getPluginMetadata(cesdk.engine, blockId); - +async function handleUpdateEvent(engine: CreativeEngine, blockId: number) { + const metadata = getPluginMetadata(engine, blockId); switch (metadata.status) { - case 'PROCESSING': case 'PROCESSED': { - if (!isMetadataConsistent(cesdk.engine, blockId)) { - clearPluginMetadata(cesdk.engine, blockId); + if (!isMetadataConsistent(engine, blockId)) { + clearPluginMetadata(engine, blockId); } break; } diff --git a/packages/vectorizer/src/manifest.ts b/packages/vectorizer/src/manifest.ts index f32117d..7407bbb 100644 --- a/packages/vectorizer/src/manifest.ts +++ b/packages/vectorizer/src/manifest.ts @@ -1,9 +1,19 @@ -import { PLUGIN_ID, PLUGIN_ACTION_VECTORIZE_LABEL } from './utils/constants'; +export const PLUGIN_ID = '@imgly/plugin-vectorizer-web'; +export const PLUGIN_COMPONENT_BUTTON_ID = `component.${PLUGIN_ID}.button`; +export const PLUGIN_ACTION_VECTORIZE_LABEL = `plugin.${PLUGIN_ID}.vectorize` + + export default { id: PLUGIN_ID, + contributes: { - actions: [ + ui: [ + { + id: PLUGIN_COMPONENT_BUTTON_ID, // not sure will + } + ], + commands: [ { id: PLUGIN_ACTION_VECTORIZE_LABEL, } diff --git a/packages/vectorizer/src/ui.ts b/packages/vectorizer/src/ui.ts index a183974..9b93931 100644 --- a/packages/vectorizer/src/ui.ts +++ b/packages/vectorizer/src/ui.ts @@ -1,42 +1,40 @@ import { PLUGIN_COMPONENT_BUTTON_ID, - PLUGIN_CANVAS_MENU_COMPONENT_ID, - PLUGIN_ACTION_VECTORIZE_LABEL, - PLUGIN_ICON -} from './utils/constants'; + PLUGIN_ACTION_VECTORIZE_LABEL +} from './manifest'; import { getPluginMetadata, isBlockSupported, -} from './utils/utils'; +} from './utils'; import { CreativeEngineWithPolyfills } from './utils/polyfills'; const button = (params: any) => { const engine = params.engine! as CreativeEngineWithPolyfills const builder = params.builder! + // the button might need the ids it is shown for + // the isSupported const selected = engine.block.findAllSelected(); const candidates = selected.filter(id => isBlockSupported(engine, id)) if (candidates.length === 0) return; - let isLoading = candidates.some(id => { - const metadata = getPluginMetadata(engine, id); - return metadata.status === 'PROCESSING' - }) - - const loadingProgress = undefined + let isLoading = candidates.some(id => getPluginMetadata(engine, id).status === 'PROCESSING') - builder.Button(PLUGIN_COMPONENT_BUTTON_ID, { + // @maerch: Why do we need the Button ID here? + builder.Button("DO I NEED THIS", { label: PLUGIN_ACTION_VECTORIZE_LABEL, - icon: PLUGIN_ICON, + icon: '@imgly/icons/Vectorize', isActive: false, isLoading: isLoading, isDisabled: isLoading, - loadingProgress, - onClick: () => candidates.forEach(id => engine.polyfill_commands?.executeCommand(PLUGIN_ACTION_VECTORIZE_LABEL, { blockId: id })) + loadingProgress: undefined, // creates infinite spinner + onClick: () => engine + .polyfill_commands + ?.executeCommand(PLUGIN_ACTION_VECTORIZE_LABEL, { blockIds: candidates }) }); } export default { - [PLUGIN_CANVAS_MENU_COMPONENT_ID]: button + [PLUGIN_COMPONENT_BUTTON_ID]: button } // end of export default diff --git a/packages/vectorizer/src/utils/utils.ts b/packages/vectorizer/src/utils.ts similarity index 94% rename from packages/vectorizer/src/utils/utils.ts rename to packages/vectorizer/src/utils.ts index d0c914d..14e682b 100644 --- a/packages/vectorizer/src/utils/utils.ts +++ b/packages/vectorizer/src/utils.ts @@ -1,15 +1,18 @@ -import CreativeEditorSDK, { CreativeEngine } from '@cesdk/cesdk-js'; +import { CreativeEngine } from '@cesdk/cesdk-js'; import isEqual from 'lodash/isEqual'; -import { PLUGIN_ID } from './constants'; +import { PLUGIN_ID } from './manifest'; import { PluginMetadata, PluginStatusError, PluginStatusProcessed, PluginStatusProcessing -} from './types'; +} from './utils/types'; +export const areBlocksSupported = (engine: CreativeEngine, blockIds: number[]) => { + return blockIds.some(id => isBlockSupported(engine, id)) +} /** * Checks if a block is supported by the given CreativeEngine. * @param engine - The CreativeEngine instance. @@ -17,9 +20,9 @@ import { * @returns A boolean indicating whether the block is supported or not. */ export const isBlockSupported = (engine: CreativeEngine, blockId: number) => { + if (!engine.block.isValid(blockId)) return false; const blockType = engine.block.getType(blockId); if (blockType === "//ly.img.ubq/page") return false; // There is some bug with the page block - if (engine.block.hasFill(blockId)) { const fillId = engine.block.getFill(blockId); @@ -80,11 +83,7 @@ export function isDuplicate( metadata: PluginMetadata ): boolean { if (!engine.block.isValid(blockId)) return false; - if ( - metadata.status === 'IDLE' || - // metadata.status === 'PENDING' || - metadata.status === 'ERROR' - ) + if (metadata.status === 'IDLE' || metadata.status === 'ERROR') return false; if (!engine.block.hasFill(blockId)) return false; @@ -188,7 +187,7 @@ export function isMetadataConsistent( } else { if (metadata.status === 'PROCESSED') { if ( - imageFileURI !== metadata.initialImageFileURI + imageFileURI !== metadata.initialImageFileURI // &&imageFileURI !== metadata.processedAsset ) { return false; diff --git a/packages/vectorizer/src/utils/constants.ts b/packages/vectorizer/src/utils/constants.ts deleted file mode 100644 index 2bf91fb..0000000 --- a/packages/vectorizer/src/utils/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const PLUGIN_ID = '@imgly/plugin-vectorizer-web'; -export const PLUGIN_CANVAS_MENU_COMPONENT_ID = `${PLUGIN_ID}.canvasMenu`; -export const PLUGIN_COMPONENT_BUTTON_ID = `${PLUGIN_CANVAS_MENU_COMPONENT_ID}.button`; -export const PLUGIN_FEATURE_ID = `${PLUGIN_ID}`; -export const PLUGIN_ACTION_VECTORIZE_LABEL = `plugin.${PLUGIN_ID}.vectorize` -export const PLUGIN_ICON = '@imgly/icons/Vectorize' - - diff --git a/packages/vectorizer/src/utils/polyfills.ts b/packages/vectorizer/src/utils/polyfills.ts index 399a757..4d33237 100644 --- a/packages/vectorizer/src/utils/polyfills.ts +++ b/packages/vectorizer/src/utils/polyfills.ts @@ -23,9 +23,11 @@ export class Commands { } } -export function polyfillEngineWithCommands(engine: CreativeEngineWithPolyfills) { +export function polyfillEngineWithCommands(engine: CreativeEngine) { + const polyfilled = engine as CreativeEngineWithPolyfills; //polyfill - if (!engine.polyfill_commands) { - engine.polyfill_commands = new Commands(engine); + if (!polyfilled.polyfill_commands ) { + polyfilled.polyfill_commands = new Commands(engine); } + return polyfilled } \ No newline at end of file diff --git a/packages/vectorizer/src/utils/worker.shared.ts b/packages/vectorizer/src/utils/worker.shared.ts index fa09338..c270865 100644 --- a/packages/vectorizer/src/utils/worker.shared.ts +++ b/packages/vectorizer/src/utils/worker.shared.ts @@ -4,11 +4,12 @@ export interface MessageBody { error?: Error } - - +const TIMEOUT_DEFAULT = 10000 +// we need a timeout export const runInWorker = (blob: Blob) => new Promise((resolve, reject) => { const worker = new Worker(new URL('./worker', import.meta.url), { type: 'module' }); const msg: MessageBody = { method: "imageToJson", data: blob } + setTimeout(() => reject(new Error("Timeout")), TIMEOUT_DEFAULT); worker.postMessage(msg) worker.onmessage = (e: MessageEvent) => { const msg = e.data diff --git a/packages/vectorizer/types/actions.d.ts b/packages/vectorizer/types/actions.d.ts deleted file mode 100644 index 03d9ba2..0000000 --- a/packages/vectorizer/types/actions.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type CreativeEditorSDK from '@cesdk/cesdk-js'; -declare const _default: { - "plugin.@imgly/plugin-vectorizer-web.vectorize": (cesdk: CreativeEditorSDK, params: { - blockId: number; - }) => Promise; -}; -export default _default; diff --git a/packages/vectorizer/types/cesdk+utils.d.ts b/packages/vectorizer/types/cesdk+utils.d.ts deleted file mode 100644 index bb1aa1d..0000000 --- a/packages/vectorizer/types/cesdk+utils.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { CreativeEngine } from "@cesdk/cesdk-js"; -export declare const createVectorPathBlocks: (engine: CreativeEngine, blocks: any[]) => number; -export declare const createVectorPathBlock: (engine: CreativeEngine, block: any) => number; diff --git a/packages/vectorizer/types/commands.d.ts b/packages/vectorizer/types/commands.d.ts index 03d9ba2..5075718 100644 --- a/packages/vectorizer/types/commands.d.ts +++ b/packages/vectorizer/types/commands.d.ts @@ -1,7 +1,7 @@ import type CreativeEditorSDK from '@cesdk/cesdk-js'; declare const _default: { "plugin.@imgly/plugin-vectorizer-web.vectorize": (cesdk: CreativeEditorSDK, params: { - blockId: number; + blockIds?: number[] | undefined; }) => Promise; }; export default _default; diff --git a/packages/vectorizer/types/defaults.d.ts b/packages/vectorizer/types/defaults.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/vectorizer/types/manifest.d.ts b/packages/vectorizer/types/manifest.d.ts index c4400b4..74d1f84 100644 --- a/packages/vectorizer/types/manifest.d.ts +++ b/packages/vectorizer/types/manifest.d.ts @@ -1,7 +1,13 @@ +export declare const PLUGIN_ID = "@imgly/plugin-vectorizer-web"; +export declare const PLUGIN_COMPONENT_BUTTON_ID = "component.@imgly/plugin-vectorizer-web.button"; +export declare const PLUGIN_ACTION_VECTORIZE_LABEL = "plugin.@imgly/plugin-vectorizer-web.vectorize"; declare const _default: { id: string; contributes: { - actions: { + ui: { + id: string; + }[]; + commands: { id: string; }[]; }; diff --git a/packages/vectorizer/types/manitfest.d.ts b/packages/vectorizer/types/manitfest.d.ts deleted file mode 100644 index c4400b4..0000000 --- a/packages/vectorizer/types/manitfest.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -declare const _default: { - id: string; - contributes: { - actions: { - id: string; - }[]; - }; -}; -export default _default; diff --git a/packages/vectorizer/types/proposal/actions.d.ts b/packages/vectorizer/types/proposal/actions.d.ts deleted file mode 100644 index 03d9ba2..0000000 --- a/packages/vectorizer/types/proposal/actions.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type CreativeEditorSDK from '@cesdk/cesdk-js'; -declare const _default: { - "plugin.@imgly/plugin-vectorizer-web.vectorize": (cesdk: CreativeEditorSDK, params: { - blockId: number; - }) => Promise; -}; -export default _default; diff --git a/packages/vectorizer/types/proposal/i18n.d.ts b/packages/vectorizer/types/proposal/i18n.d.ts deleted file mode 100644 index c1327c7..0000000 --- a/packages/vectorizer/types/proposal/i18n.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export declare const en: { - "plugin.@imgly/plugin-vectorizer-web.vectorize": string; -}; -export declare const de: { - "plugin.@imgly/plugin-vectorizer-web.vectorize": string; -}; -declare const _default: { - de: { - "plugin.@imgly/plugin-vectorizer-web.vectorize": string; - }; - en: { - "plugin.@imgly/plugin-vectorizer-web.vectorize": string; - }; -}; -export default _default; diff --git a/packages/vectorizer/types/proposal/manitfest.d.ts b/packages/vectorizer/types/proposal/manitfest.d.ts deleted file mode 100644 index 4c0f97c..0000000 --- a/packages/vectorizer/types/proposal/manitfest.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export declare const id = "@imgly/plugin-vectorizer-web"; -export declare const contributes: { - actions: { - id: string; - }[]; -}; diff --git a/packages/vectorizer/types/proposal/ui.d.ts b/packages/vectorizer/types/proposal/ui.d.ts deleted file mode 100644 index 2a7467d..0000000 --- a/packages/vectorizer/types/proposal/ui.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare const _default: { - "@imgly/plugin-vectorizer-web.canvasMenu": (params: any) => void; -}; -export default _default; diff --git a/packages/vectorizer/types/proposal/worker.d.ts b/packages/vectorizer/types/proposal/worker.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/packages/vectorizer/types/proposal/worker.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/packages/vectorizer/types/types.d.ts b/packages/vectorizer/types/types.d.ts deleted file mode 100644 index c00d57f..0000000 --- a/packages/vectorizer/types/types.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { type Source } from '@cesdk/cesdk-js'; -export type PluginStatusIdle = { - status: 'IDLE'; -}; -export type PluginStatusProcessing = { - version: string; - status: 'PROCESSING'; - initialImageFileURI: string; - initialSourceSet: Source[]; - blockId: number; - fillId: number; - progress?: { - key: string; - current: number; - total: number; - }; -}; -export type PluginStatusProcessed = { - version: string; - status: 'PROCESSED'; - initialImageFileURI: string; - initialSourceSet: Source[]; - blockId: number; - fillId: number; -}; -export type PluginStatusError = { - version: string; - status: 'ERROR'; - initialImageFileURI: string; - initialSourceSet: Source[]; - blockId: number; - fillId: number; -}; -export type PluginMetadata = PluginStatusIdle | PluginStatusError | PluginStatusProcessing | PluginStatusProcessed; diff --git a/packages/vectorizer/types/ui.d.ts b/packages/vectorizer/types/ui.d.ts index 2a7467d..4746d3f 100644 --- a/packages/vectorizer/types/ui.d.ts +++ b/packages/vectorizer/types/ui.d.ts @@ -1,4 +1,4 @@ declare const _default: { - "@imgly/plugin-vectorizer-web.canvasMenu": (params: any) => void; + "component.@imgly/plugin-vectorizer-web.button": (params: any) => void; }; export default _default; diff --git a/packages/vectorizer/types/utils.d.ts b/packages/vectorizer/types/utils.d.ts index 7a389e0..4a58386 100644 --- a/packages/vectorizer/types/utils.d.ts +++ b/packages/vectorizer/types/utils.d.ts @@ -1,5 +1,6 @@ import { CreativeEngine } from '@cesdk/cesdk-js'; import { PluginMetadata } from './utils/types'; +export declare const areBlocksSupported: (engine: CreativeEngine, blockIds: number[]) => boolean; /** * Checks if a block is supported by the given CreativeEngine. * @param engine - The CreativeEngine instance. @@ -48,5 +49,8 @@ export declare class Scheduler { #private; schedule(task: () => Promise): Promise; } -export declare function registerAction(engine: CreativeEngine, label: string, callback: (params: any) => Promise): void; -export declare function executeAction(label: string, params: any): Promise; +/** + * Generates a unique filename. + * @returns A string representing the unique filename. + */ +export declare function generateUniqueFilename(): string; diff --git a/packages/vectorizer/types/utils/cesdk+utils.d.ts b/packages/vectorizer/types/utils/cesdk+utils.d.ts index bb1aa1d..a5b5a9e 100644 --- a/packages/vectorizer/types/utils/cesdk+utils.d.ts +++ b/packages/vectorizer/types/utils/cesdk+utils.d.ts @@ -1,3 +1,3 @@ import { CreativeEngine } from "@cesdk/cesdk-js"; -export declare const createVectorPathBlocks: (engine: CreativeEngine, blocks: any[]) => number; +export declare const createVectorPathBlocks: (engine: CreativeEngine, blocks: any[]) => number[]; export declare const createVectorPathBlock: (engine: CreativeEngine, block: any) => number; diff --git a/packages/vectorizer/types/utils/constants.d.ts b/packages/vectorizer/types/utils/constants.d.ts index 32b989f..ddad552 100644 --- a/packages/vectorizer/types/utils/constants.d.ts +++ b/packages/vectorizer/types/utils/constants.d.ts @@ -1,6 +1,4 @@ export declare const PLUGIN_ID = "@imgly/plugin-vectorizer-web"; -export declare const PLUGIN_CANVAS_MENU_COMPONENT_ID = "@imgly/plugin-vectorizer-web.canvasMenu"; -export declare const PLUGIN_CANVAS_MENU_COMPONENT_BUTTON_ID = "@imgly/plugin-vectorizer-web.canvasMenu.button"; -export declare const PLUGIN_FEATURE_ID = "@imgly/plugin-vectorizer-web"; +export declare const PLUGIN_COMPONENT_BUTTON_ID = "component.@imgly/plugin-vectorizer-web.button"; export declare const PLUGIN_ACTION_VECTORIZE_LABEL = "plugin.@imgly/plugin-vectorizer-web.vectorize"; export declare const PLUGIN_ICON = "@imgly/icons/Vectorize"; diff --git a/packages/vectorizer/types/utils/polyfills.d.ts b/packages/vectorizer/types/utils/polyfills.d.ts index ad9669b..99db4ec 100644 --- a/packages/vectorizer/types/utils/polyfills.d.ts +++ b/packages/vectorizer/types/utils/polyfills.d.ts @@ -9,4 +9,4 @@ export declare class Commands { registerCommand(label: string, callback: (params: any) => Promise): void; executeCommand(label: string, params: any): Promise; } -export declare function polyfillEngineWithCommands(engine: CreativeEngineWithPolyfills): void; +export declare function polyfillEngineWithCommands(engine: CreativeEngine): CreativeEngineWithPolyfills; diff --git a/packages/vectorizer/types/utils/utils.d.ts b/packages/vectorizer/types/utils/utils.d.ts index 5afc33d..2166d3f 100644 --- a/packages/vectorizer/types/utils/utils.d.ts +++ b/packages/vectorizer/types/utils/utils.d.ts @@ -1,5 +1,6 @@ import { CreativeEngine } from '@cesdk/cesdk-js'; import { PluginMetadata } from './types'; +export declare const areBlocksSupported: (engine: CreativeEngine, blockIds: number[]) => boolean; /** * Checks if a block is supported by the given CreativeEngine. * @param engine - The CreativeEngine instance. @@ -48,3 +49,8 @@ export declare class Scheduler { #private; schedule(task: () => Promise): Promise; } +/** + * Generates a unique filename. + * @returns A string representing the unique filename. + */ +export declare function generateUniqueFilename(): string; diff --git a/packages/vectorizer/types/utils/worker.shared.d.ts b/packages/vectorizer/types/utils/worker.shared.d.ts index 4f39759..bf21f38 100644 --- a/packages/vectorizer/types/utils/worker.shared.d.ts +++ b/packages/vectorizer/types/utils/worker.shared.d.ts @@ -3,4 +3,4 @@ export interface MessageBody { data?: any; error?: Error; } -export declare const runInWorker: (uri: string) => Promise; +export declare const runInWorker: (blob: Blob) => Promise; diff --git a/packages/vectorizer/types/worker.shared.d.ts b/packages/vectorizer/types/worker.shared.d.ts deleted file mode 100644 index 68f6a33..0000000 --- a/packages/vectorizer/types/worker.shared.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface MessageBody { - method?: string; - data?: any; - error?: Error; -} -export declare const runInWorker: (uri: string) => Promise; diff --git a/yarn.lock b/yarn.lock index 930c7f5..2ae2cbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -220,7 +220,7 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@cesdk/cesdk-js@^1.20.0", "@cesdk/cesdk-js@~1.20.0": +"@cesdk/cesdk-js@^1.20.0": version "1.20.0" resolved "https://registry.npmjs.org/@cesdk/cesdk-js/-/cesdk-js-1.20.0.tgz" integrity sha512-vKDcnv5z5TZe1PcgvZagJ7QXVyijeTnkwPCJJFj/Uxcsef9GvLrzOVIYqPC0gqZuDlfHpADPsGAV+pZaZg8+eg== @@ -4290,6 +4290,11 @@ set-function-name@^2.0.0, set-function-name@^2.0.1: functions-have-names "^1.2.3" has-property-descriptors "^1.0.0" +share-api-polyfill@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/share-api-polyfill/-/share-api-polyfill-1.1.1.tgz#373bacac2f02828a3ee1bd7d9a2b72c60580080e" + integrity sha512-5GxXomFRth4lBpHWN136cxpv8KOo+uutljXqgO1RsptDQ5X1raRQWqLi+LoWZ/h02/TgV1STJwuMXcP/0b1vpQ== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"