How to Use Effects & Filters - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/using-effects/?platform=web&language=javascript # How to Use Effects & Filters Some [design blocks](/docs/cesdk/engine/guides/blocks/) in CE.SDK such as pages and graphic blocks allow you to add effects to them. An effect can modify the visual output of a block's [fill](/docs/cesdk/engine/guides/using-fills/). CreativeEditor SDK supports many different types of effects, such as adjustments, LUT filters, pixelization, glow, vignette and more. Similarly to blocks, each effect instance has a numeric id which can be used to query and [modify its properties](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-effects?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Using+Effects&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-effects). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-effects?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Using+Effects&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-effects) ## Setup This example uses the headless Creative Engine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. We create a scene containing a graphic block with an image fill and want to apply effects to this image. ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/index.js'; const config = { license: 'mtLT-_GJwMhE7LDnO8KKEma7qSuzWuDxiKuQcxHKmz3fjaXWY2lT3o3Z2VdL5twm', userId: 'guides-user', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/assets' }; CreativeEngine.init(config).then(async (engine) => { document.getElementById('cesdk_container').append(engine.element); const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); engine.block.appendChild(scene, page); engine.scene.zoomToBlock(page, 40, 40, 40, 40); const block = engine.block.create('graphic'); engine.block.setShape(block, engine.block.createShape('rect')); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(block, imageFill); engine.block.setPositionX(block, 100); engine.block.setPositionY(block, 50); engine.block.setWidth(block, 300); engine.block.setHeight(block, 300); engine.block.appendChild(page, block); ``` ## Accessing Effects Not all types of design blocks support effects, so you should always first call the `supportsEffects(id: number): boolean` API before accessing any of the following APIs. ``` engine.block.supportsEffects(scene); // Returns false engine.block.supportsEffects(block); // Returns true ``` ## Creating an Effect In order to add effects to our block, we first have to create a new effect instance, which we can do by calling `createEffect(type: EffectType): number` and passing it the type of effect that we want. In this example, we create a pixelization and an adjustment effect. Please refer to [the API docs](/docs/cesdk/engine/api/block-effects/) for a complete list of supported effect types. ``` const pixelize = engine.block.createEffect('pixelize'); const adjustments = engine.block.createEffect('adjustments'); ``` ## Adding Effects Now we have two effects but the output of our scene looks exactly the same as before. That is because we still need to append these effects to the graphic design block's list of effects, which we can do by calling `appendEffect(id: number, effect: number): void`. We can also insert or remove effects from specific indices of a block's effect list using the `insertEffect(id: number, effect: number, index: number): void` and `removeEffect(id: number, index: number): void` APIs. Effects will be applied to the block in the order they are placed in the block's effects list. If the same effect appears multiple times in the list, it will also be applied multiple times. In our case, the adjustments effect will be applied to the image first, before the result of that is then pixelated. ``` engine.block.appendEffect(block, pixelize); engine.block.insertEffect(block, adjustments, 0); // engine.block.removeEffect(rect, 0); ``` ## Querying Effects Use the `getEffects(id: number): number[]` API to query the ordered list of effect ids of a block. ``` // This will return [adjustments, pixelize] const effectsList = engine.block.getEffects(block); ``` ## Destroying Effects If we created an effect that we don't want anymore, we have to make sure to destroy it using the same `destroy(id: number): void` API that we also call for design blocks. Effects that are attached to a design block will be automatically destroyed when the design block is destroyed. ``` const unusedEffect = engine.block.createEffect('half_tone'); engine.block.destroy(unusedEffect); ``` ## Effect Properties Just like design blocks, effects with different types have different properties that you can query and modify via the API. Use `findAllProperties(id: number): string[]` in order to get a list of all properties of a given effect. Please refer to the [API docs](/docs/cesdk/engine/api/block-effects/) for a complete list of all available properties for each type of effect. ``` const allPixelizeProperties = engine.block.findAllProperties(pixelize); const allAdjustmentProperties = engine.block.findAllProperties(adjustments); ``` Once we know the property keys of an effect, we can use the same APIs as for design blocks in order to [modify those properties](/docs/cesdk/engine/api/block-properties/). Our adjustment effect here for example will not modify the output unless we at least change one of its adjustment properties - such as the brightness - to not be zero. ``` engine.block.setInt(pixelize, 'pixelize/horizontalPixelSize', 20); engine.block.setFloat(adjustments, 'effect/adjustments/brightness', 0.2); ``` ## Disabling Effects You can temporarily disable and enable the individual effects using the `setEffectEnabled(id: number, enabled: boolean): void` API. When the effects are applied to a block, all disabled effects are simply skipped. Whether an effect is currently enabled or disabled can be queried with `isEffectEnabled(id: number): boolean`. ``` engine.block.setEffectEnabled(pixelize, false); engine.block.setEffectEnabled( pixelize, !engine.block.isEffectEnabled(pixelize) ); ``` Use the API to refresh the Asset Library - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/guides/refresh-asset-library/ CESDK/CE.SDK/Web Editor/Guides # Use the API to refresh the Asset Library Learn how to refresh the asset library in the CreativeEditorSDK on demand. In this example, we explain how to use the `refetchAssetSources` API to refresh the asset library after a specific action is taken. By default, the CreativeEditorSDK takes care of refreshing the asset library entries in most cases. For example, when uploading or deleting elements. In other cases, however, we might need to refresh the asset library on demand. An example would be a custom asset source that can be updated from outside the CreativeEditorSDK. ## API Signature The `refetchAssetSources` API will trigger a refetch of the asset source and update the asset library panel with the new items accordingly and can be used like the following: ``` // To refetch a specific asset source, you can pass the ID of the asset source as a parameter cesdk.refetchAssetSources('ly.img.audio.upload'); // To refetch multiple asset sources, you can pass an array containing the IDs of the asset sources cesdk.refetchAssetSources(['ly.img.video.upload', 'ly.img.image.upload']); // To refetch all asset sources, you can call the API without any parameters cesdk.refetchAssetSources(); ``` ## Prerequisites * [Get the latest stable version of **Node.js & NPM**](https://www.npmjs.com/get-npm) * (Optional): [Get the latest stable version of **Yarn**](https://yarnpkg.com/getting-started) ## 1\. Add the CreativeEditorSDK to Your Project ``` npm install --save @cesdk/engine@1.43.0 ``` ## 2\. Configure a custom asset source We will take Cloudinary as an example by adding a custom asset source to the configuration. Check out the [Customize The Asset Library](/docs/cesdk/ui/guides/customize-asset-library/) guide for detailed information about adding customizing the asset library. ``` const config = { /* ...configuration options */ assetSources: { 'cloudinary-images': { findAssets: () => { /* fetch the images from Cloudinary to display in the asset panel */ } } }, ui: { elements: { libraries: { insert: { /* append the Cloudinary asset source to the image asset library entry */ } } } } }; ``` ## 3\. Refresh the asset source when a new image is uploaded Let's consider that an image can be uploaded to Cloudinary from our application outside of the context of the CreativeEditorSDK. In this case, we can use the `refetchAssetSources` API to refresh the `cloudinary-images` asset source and update the asset panel with the new items. ``` CreativeEditorSDK.create(config).then(async (instance) => { await instance.createDesignScene(); // create a Cloudinary upload widget const myWidget = cloudinary.createUploadWidget( { cloudName: 'my-cloud-name' }, (error, result) => { if (!error && result && result.event === 'success') { // refresh the asset panel after the image has been successfully uploaded instance.refetchAssetSources('cloudinary-images'); } } ); // attach the widget to the upload button document.getElementById('upload_widget').addEventListener('click', () => { myWidget.open(); }); }); ``` [ Previous Fonts & Typefaces ](/docs/cesdk/ui/guides/fonts-typefaces/)[ Next Placeholders ](/docs/cesdk/ui/guides/placeholders/) Exporting to PDF with an underlayer - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/underlayer/?platform=web&language=javascript # Exporting to PDF with an underlayer When printing on a non-white medium or on a special medium like fabric or glass, printing your design over an underlayer helps achieve the desired result. An underlayer will typically be printed using a special ink and be of the exact shape of your design. When exporting to PDF, you can specify that an underlayer be automatically generated in the [ExportOptions](/docs/cesdk/engine/guides/export-blocks/). An underlayer will be generated by detecting the contour of all elements on a page and inserting a new block with the shape of the detected contour. This new block will be positioned behind all existing block. After exporting, the new block will be removed. The result will be a PDF file containing an additional shape of the same shape as your design and sitting behind it. The ink to be used by the printer is specified in the [ExportOptions](/docs/cesdk/engine/guides/export-blocks/) with a [spot color](/docs/cesdk/engine/guides/colors/). You can also adjust the scale of the underlayer shape with a negative or positive offset, in design units. #### Warning Do not flatten the resulting PDF file or you will lose the underlayer shape which sits behind your design. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Underlayer&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Underlayer&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer) ## Setup the scene We first create a new scene with a graphic block that has a color fill. ``` document.getElementById('cesdk_container').append(engine.element); const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); engine.block.appendChild(scene, page); engine.scene.zoomToBlock(page, 40, 40, 40, 40); const block = engine.block.create('graphic'); engine.block.setShape(block, engine.block.createShape('star')); engine.block.setPositionX(block, 350); engine.block.setPositionY(block, 400); engine.block.setWidth(block, 100); engine.block.setHeight(block, 100); const fill = engine.block.createFill('color'); engine.block.setFill(block, fill); const rgbaBlue = { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }; engine.block.setColor(fill, `fill/color/value`, rgbaBlue); ``` ## Add the underlayer's spot color Here we instantiate a spot color with the known name of the ink the printer should use for the underlayer. The visual color approximation is not so important, so long as the name matches what the printer expects. ``` engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); ``` ## Exporting with an underlayer We enable the automatic generation of an underlayer on export with the option `exportPdfWithUnderlayer = true`. We specify the ink to use with `underlayerSpotColorName = 'RDG_WHITE'`. In this instance, we make the underlayer a bit smaller than our design so we specify an offset of 2 design units (e.g. millimeters) with `underlayerOffset = -2.0`. ``` await block .export(page, 'application/pdf', { exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', underlayerOffset: -2.0 }) .then((blob) => { const element = document.createElement('a'); element.setAttribute('href', window.URL.createObjectURL(blob)); element.setAttribute('download', 'underlayer_example.pdf'); element.style.display = 'none'; element.click(); element.remove(); }); ``` How to Use Fills - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/using-fills/?platform=web&language=javascript # How to Use Fills Some [design blocks](/docs/cesdk/engine/guides/blocks/) in CE.SDK allow you to modify or replace their fill. The fill is an object that defines the contents within the shape of a block. CreativeEditor SDK supports many different types of fills, such as images, solid colors, gradients and videos. Similarly to blocks, each fill has a numeric id which can be used to query and [modify its properties](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-fills?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Using+Fills&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-fills). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-fills?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Using+Fills&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-fills) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/index.js'; const config = { license: 'mtLT-_GJwMhE7LDnO8KKEma7qSuzWuDxiKuQcxHKmz3fjaXWY2lT3o3Z2VdL5twm', userId: 'guides-user', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/assets' }; CreativeEngine.init(config).then(async (engine) => { document.getElementById('cesdk_container').append(engine.element); const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); engine.block.appendChild(scene, page); engine.scene.zoomToBlock(page, 40, 40, 40, 40); const block = engine.block.create('graphic'); engine.block.setShape(block, engine.block.createShape('rect')); engine.block.setFill(block, engine.block.createFill('color')); engine.block.setWidth(block, 100); engine.block.setHeight(block, 100); engine.block.appendChild(page, block); ``` ## Accessing Fills Not all types of design blocks support fills, so you should always first call the `supportsFill(id: number): boolean` API before accessing any of the following APIs. ``` engine.block.supportsFill(scene); // Returns false engine.block.supportsFill(block); // Returns true ``` In order to receive the fill id of a design block, call the `getFill(id: number): number` API. You can now pass this id into other APIs in order to query more information about the fill, e.g. its type via the `getType(id: number): ObjectType` API. ``` const colorFill = engine.block.getFill(block); const defaultRectFillType = engine.block.getType(colorFill); ``` ## Fill Properties Just like design blocks, fills with different types have different properties that you can query and modify via the API. Use `findAllProperties(id: number): string[]` in order to get a list of all properties of a given fill. For the solid color fill in this example, the call would return `["fill/color/value", "type"]`. Please refer to the [API docs](/docs/cesdk/engine/api/block-fill-types/) for a complete list of all available properties for each type of fill. ``` const allFillProperties = engine.block.findAllProperties(colorFill); ``` Once we know the property keys of a fill, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `setColor(id: number, property: string, value: Color): void` in order to change the color of the fill to red. Once we do this, our graphic block with rect shape will be filled with solid red. ``` engine.block.setColor(colorFill, 'fill/color/value', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); ``` ## Disabling Fills You can disable and enable a fill using the `setFillEnabled(id: number, enabled: boolean): void` API, for example in cases where the design block should only have a stroke but no fill. Notice that you have to pass the id of the design block and not of the fill to the API. ``` engine.block.setFillEnabled(block, false); engine.block.setFillEnabled(block, !engine.block.isFillEnabled(block)); ``` ## Changing Fill Types All design blocks that support fills allow you to also exchange their current fill for any other type of fill. In order to do this, you need to first create a new fill object using `createFill(type: FillType): number`. We currently support the following fill types: * `'//ly.img.ubq/fill/color'` * `'//ly.img.ubq/fill/gradient/linear'` * `'//ly.img.ubq/fill/gradient/radial'` * `'//ly.img.ubq/fill/gradient/conical'` * `'//ly.img.ubq/fill/image'` * `'//ly.img.ubq/fill/video'` * `'//ly.img.ubq/fill/pixelStream'` Note: short types are also accepted, e.g. `'image'` instead of `'//ly.img.ubq/fill/image'`. ``` const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); ``` In order to assign a fill to a design block, simply call `setFill(id: number, fill: number): void`. Make sure to delete the previous fill of the design block first if you don't need it any more, otherwise we will have leaked it into the scene and won't be able to access it any more, because we don't know its id. Notice that we don't use the `appendChild` API here, which only works with design blocks and not fills. When a fill is attached to one design block, it will be automatically destroyed when the block itself gets destroyed. ``` engine.block.destroy(engine.block.getFill(block)); engine.block.setFill(block, imageFill); /* The following line would also destroy imageFill */ // engine.block.destroy(circle); ``` ## Duplicating Fills If we duplicate a design block with a fill that is only attached to this block, the fill will automatically be duplicated as well. In order to modify the properties of the duplicate fill, we have to query its id from the duplicate block. ``` const duplicateBlock = engine.block.duplicate(block); engine.block.setPositionX(duplicateBlock, 450); const autoDuplicateFill = engine.block.getFill(duplicateBlock); engine.block.setString( autoDuplicateFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg' ); // const manualDuplicateFill = engine.block.duplicate(autoDuplicateFill); // /* We could now assign this fill to another block. */ // engine.block.destroy(manualDuplicateFill); ``` ## Sharing Fills It is also possible to share a single fill instance between multiple design blocks. In that case, changing the properties of the fill will apply to all of the blocks that it's attached to at once. Destroying a block with a shared fill will not destroy the fill until there are no other design blocks left that still use that fill. ``` const sharedFillBlock = engine.block.create('graphic'); engine.block.setShape(sharedFillBlock, engine.block.createShape('rect')); engine.block.setPositionX(sharedFillBlock, 350); engine.block.setPositionY(sharedFillBlock, 400); engine.block.setWidth(sharedFillBlock, 100); engine.block.setHeight(sharedFillBlock, 100); engine.block.appendChild(page, sharedFillBlock); engine.block.setFill(sharedFillBlock, engine.block.getFill(block)); ``` Control Audio & Video - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-video/?platform=web&language=javascript#time-offset-and-duration # Control Audio & Video In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to configure and control audio and video through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Time Offset and Duration The time offset determines when a block becomes active during playback on the page's timeline, and the duration decides how long this block is active. Blocks within tracks are a special case in that they have an implicitly calculated time offset that is determined by their order and the total duration of their preceding blocks in the same track. As with any audio/video-related property, not every block supports these properties. Use `supportsTimeOffset` and `supportsDuration` to check. ``` supportsTimeOffset(id: DesignBlockId): boolean ``` Returns whether the block has a time offset property. * `id`: The block to query. * Returns true, if the block has a time offset property. ``` engine.block.supportsTimeOffset(audio); ``` ``` setTimeOffset(id: DesignBlockId, offset: number): void ``` Set the time offset of the given block relative to its parent. The time offset controls when the block is first active in the timeline. The time offset is not supported by the page block. * `id`: The block whose time offset should be changed. * `offset`: The new time offset in seconds. ``` engine.block.setTimeOffset(audio, 2); ``` ``` getTimeOffset(id: DesignBlockId): number ``` Get the time offset of the given block relative to its parent. * `id`: The block whose time offset should be queried. * Returns the time offset of the block. ``` engine.block.getTimeOffset(audio); /* Returns 2 */ ``` ``` supportsDuration(id: DesignBlockId): boolean ``` Returns whether the block has a duration property. * `id`: The block to query. * Returns true if the block has a duration property. ``` engine.block.supportsDuration(page); ``` ``` setDuration(id: DesignBlockId, duration: number): void ``` Set the playback duration of the given block in seconds. The duration defines for how long the block is active in the scene during playback. If a duration is set on the page block, it becomes the duration source block. The duration is ignored when the scene is not in "Video" mode. * `id`: The block whose duration should be changed. * `duration`: The new duration in seconds. ``` engine.block.setDuration(page, 10); ``` ``` getDuration(id: DesignBlockId): number ``` Get the playback duration of the given block in seconds. * `id`: The block whose duration should be returned. * Returns The block's duration. ``` engine.block.getDuration(page); /* Returns 10 */ ``` ``` supportsPageDurationSource(page: DesignBlockId, id: DesignBlockId): boolean ``` Returns whether the block can be marked as the element that defines the duration of the given page. * `id`: The block to query. * Returns true, if the block can be marked as the element that defines the duration of the given page. ``` engine.block.supportsPageDurationSource(page, block); ``` ``` setPageDurationSource(page: DesignBlockId, id: DesignBlockId): void ``` Set an block as duration source so that the overall page duration is automatically determined by this. If no defining block is set, the page duration is calculated over all children. Only one block per page can be marked as duration source. Will automatically unmark the previously marked. Note: This is only supported for blocks that have a duration. * `page`: The page block for which it should be enabled. * `id`: The block that should be updated. ``` engine.block.setPageDurationSource(page, block); ``` ``` isPageDurationSource(id: DesignBlockId): boolean ``` Query whether the block is a duration source block for the page. * `id`: The block whose duration source property should be queried. * Returns If the block is a duration source for a page. ``` engine.block.isPageDurationSource(block); ``` ``` removePageDurationSource(id: DesignBlockId): void ``` Remove the block as duration source block for the page. If a scene or page is given as block, it is deactivated for all blocks in the scene or page. * `id`: The block whose duration source property should be removed. ``` engine.block.removePageDurationSource(page); ``` ``` setNativePixelBuffer(id: number, buffer: HTMLCanvasElement | HTMLVideoElement): void ``` Update the pixels of the given pixel stream fill block. * `id`: The pixel stream fill block. * `buffer`: Use pixel data from a canvas or a video element. ``` const pixelStreamFill = engine.block.createFill('pixelStream'); ``` ## Trim You can select a specific range of footage from your audio/video resource by providing a trim offset and a trim length. The footage will loop if the trim's length is shorter than the block's duration. This behavior can also be disabled using the `setLooping` function. ``` supportsTrim(id: DesignBlockId): boolean ``` Returns whether the block has trim properties. * `id`: The block to query. * Returns true, if the block has trim properties. ``` engine.block.supportsTrim(videoFill); ``` ``` setTrimOffset(id: DesignBlockId, offset: number): void ``` Set the trim offset of the given block or fill. Sets the time in seconds within the fill at which playback of the audio or video clip should begin. This requires the video or audio clip to be loaded. * `id`: The block whose trim should be updated. * `offset`: The new trim offset. ``` engine.block.setTrimOffset(videoFill, 1); ``` ``` getTrimOffset(id: DesignBlockId): number ``` Get the trim offset of this block. \* This requires the video or audio clip to be loaded. * `id`: The block whose trim offset should be queried. * Returns the trim offset in seconds. ``` engine.block.getTrimOffset(videoFill); /* Returns 1 */ ``` ``` setTrimLength(id: DesignBlockId, length: number): void ``` Set the trim length of the given block or fill. The trim length is the duration of the audio or video clip that should be used for playback. After reaching this value during playback, the trim region will loop. This requires the video or audio clip to be loaded. * `id`: The object whose trim length should be updated. * `length`: The new trim length in seconds. ``` engine.block.setTrimLength(videoFill, 5); ``` ``` getTrimLength(id: DesignBlockId): number ``` Get the trim length of the given block or fill. * `id`: The object whose trim length should be queried. * Returns the trim length of the object. ``` engine.block.getTrimLength(videoFill); /* Returns 5 */ ``` ## Playback Control You can start and pause playback and seek to a certain point on the scene's timeline. There's also a solo playback mode to preview audio and video blocks individually while the rest of the scene stays frozen. Finally, you can enable or disable the looping behavior of blocks and control their audio volume. ``` setPlaying(id: DesignBlockId, enabled: boolean): void ``` Set whether the block should be playing during active playback. * `id`: The block that should be updated. * `enabled`: Whether the block should be playing its contents. ``` engine.block.setPlaying(page, true); ``` ``` isPlaying(id: DesignBlockId): boolean ``` Returns whether the block is playing during active playback. * `id`: The block to query. * Returns whether the block is playing during playback. ``` engine.block.isPlaying(page); ``` ``` setSoloPlaybackEnabled(id: DesignBlockId, enabled: boolean): void ``` Set whether the given block or fill should play its contents while the rest of the scene remains paused. Setting this to true for one block will automatically set it to false on all other blocks. * `id`: The block or fill to update. * `enabled`: Whether the block's playback should progress as time moves on. ``` engine.block.setSoloPlaybackEnabled(videoFill, true); ``` ``` isSoloPlaybackEnabled(id: DesignBlockId): boolean ``` Return whether the given block or fill is currently set to play its contents while the rest of the scene remains paused. * `id`: The block or fill to query. * Returns Whether solo playback is enabled for this block. ``` engine.block.isSoloPlaybackEnabled(videoFill); ``` ``` supportsPlaybackTime(id: DesignBlockId): boolean ``` Returns whether the block has a playback time property. * `id`: The block to query. * Returns whether the block has a playback time property. ``` engine.block.supportsPlaybackTime(page); ``` ``` setPlaybackTime(id: DesignBlockId, time: number): void ``` Set the playback time of the given block. * `id`: The block whose playback time should be updated. * `time`: The new playback time of the block in seconds. ``` engine.block.setPlaybackTime(page, 1); ``` ``` getPlaybackTime(id: DesignBlockId): number ``` Get the playback time of the given block. * `id`: The block to query. * Returns the playback time of the block in seconds. ``` engine.block.getPlaybackTime(page); ``` ``` isVisibleAtCurrentPlaybackTime(id: DesignBlockId): boolean ``` Returns whether the block should be visible on the canvas at the current playback time. * `id`: The block to query. * Returns Whether the block should be visible on the canvas at the current playback time. ``` engine.block.isVisibleAtCurrentPlaybackTime(block); ``` ``` supportsPlaybackControl(id: DesignBlockId): boolean ``` Returns whether the block supports a playback control. * `block`: The block to query. * Returns Whether the block has playback control. ``` engine.block.supportsPlaybackControl(videoFill); ``` ``` setLooping(id: DesignBlockId, looping: boolean): void ``` Set whether the block should restart from the beginning again or stop. * `block`: The block or video fill to update. * `looping`: Whether the block should loop to the beginning or stop. ``` engine.block.setLooping(videoFill, true); ``` ``` isLooping(id: DesignBlockId): boolean ``` Query whether the block is looping. * `block`: The block to query. * Returns Whether the block is looping. ``` engine.block.isLooping(videoFill); ``` ``` setMuted(id: DesignBlockId, muted: boolean): void ``` Set whether the audio of the block is muted. * `block`: The block or video fill to update. * `muted`: Whether the audio should be muted. ``` engine.block.setMuted(videoFill, true); ``` ``` isMuted(id: DesignBlockId): boolean ``` Query whether the block is muted. * `block`: The block to query. * Returns Whether the block is muted. ``` engine.block.isMuted(videoFill); ``` ``` setVolume(id: DesignBlockId, volume: number): void ``` Set the audio volume of the given block. * `block`: The block or video fill to update. * `volume`: The desired volume with a range of `0, 1`. ``` engine.block.setVolume(videoFill, 0.5); /* 50% volume */ ``` ``` getVolume(id: DesignBlockId): number ``` Get the audio volume of the given block. * `block`: The block to query. * Returns The volume with a range of `0, 1`. ``` engine.block.getVolume(videoFill); ``` ``` getVideoWidth(id: DesignBlockId): number ``` Get the video width in pixels of the video resource that is attached to the given block. * `block`: The video fill. * Returns The video width in pixels or an error. ``` const videoWidth = engine.block.getVideoWidth(videoFill); ``` ``` getVideoHeight(id: DesignBlockId): number ``` Get the video height in pixels of the video resource that is attached to the given block. * `block`: The video fill. * Returns The video height in pixels or an error. ``` const videoHeight = engine.block.getVideoHeight(videoFill); ``` ## Resource Control Until an audio/video resource referenced by a block is loaded, properties like the duration of the resource aren't available, and accessing those will lead to an error. You can avoid this by forcing the resource you want to access to load using `forceLoadAVResource`. ``` forceLoadAVResource(id: DesignBlockId): Promise ``` Begins loading the required audio and video resource for the given video fill or audio block. * `id`: The video fill or audio block whose resource should be loaded. * Returns A Promise that resolves once the resource has finished loading. ``` await engine.block.forceLoadAVResource(videoFill); ``` ``` unstable_isAVResourceLoaded(id: DesignBlockId): boolean ``` Returns whether the audio and video resource for the given video fill or audio block is loaded. * `id`: The video fill or audio block. * Returns The loading state of the resource. ``` const isLoaded = engine.block.unstable_isAVResourceLoaded(videoFill); ``` ``` getAVResourceTotalDuration(id: DesignBlockId): number ``` Get the duration in seconds of the video or audio resource that is attached to the given block. * `id`: The video fill or audio block. * Returns The video or audio file duration. ``` engine.block.getAVResourceTotalDuration(videoFill); ``` ## Thumbnail Previews For a user interface, it can be helpful to have image previews in the form of thumbnails for any given video resource. For videos, the engine can provide one or more frames using `generateVideoThumbnailSequence`. Pass the video fill that references the video resource. In addition to video thumbnails, the engine can also render compositions of design blocks over time. To do this pass in the respective design block. The video editor uses these to visually represent blocks in the timeline. In order to visualize audio signals `generateAudioThumbnailSequence` can be used. This generates a sequence of values in the range of 0 to 1 that represent the loudness of the signal. These values can be used to render a waveform pattern in any custom style. Note: there can be at most one thumbnail generation request per block at any given time. If you don't want to wait for the request to finish before issuing a new request, you can cancel it by calling the returned function. ``` generateVideoThumbnailSequence(id: DesignBlockId, thumbnailHeight: number, timeBegin: number, timeEnd: number, numberOfFrames: number, onFrame: (frameIndex: number, result: ImageData | Error) => void): () => void ``` Generate a sequence of thumbnails for the given video fill or design block. Note: there can only be one thumbnail generation request in progress for a given block. * `id`: The video fill or design block. * `thumbnailHeight`: The height of a thumbnail. The width will be calculated from the video aspect ratio. * `timeBegin`: The time in seconds relative to the time offset of the design block at which the thumbnail sequence should start. * `timeEnd`: The time in seconds relative to the time offset of the design block at which the thumbnail sequence should end. * `numberOfFrames`: The number of frames to generate. * `onFrame`: Gets passed the frame index and RGBA image data. * Returns A method that will cancel any thumbnail generation request in progress for this block. ``` const cancelVideoThumbnailGeneration = engine.block.generateVideoThumbnailSequence( videoFill /* video fill or any design block */, 128 /* thumbnail height, width will be calculated from aspect ratio */, 0.5 /* begin time */, 9.5 /* end time */, 10 /* number of thumbnails to generate */, async (frame, width, height, imageData) => { const image = await createImageBitmap(imageData); // Draw the image... } ); ``` ``` generateAudioThumbnailSequence(id: DesignBlockId, samplesPerChunk: number, timeBegin: number, timeEnd: number, numberOfSamples: number, numberOfChannels: number, onChunk: (chunkIndex: number, result: Float32Array | Error) => void): () => void ``` Generate a thumbnail sequence for the given audio block or video fill. A thumbnail in this case is a chunk of samples in the range of 0 to 1. In case stereo data is requested, the samples are interleaved, starting with the left channel. * `id`: The audio block or video fill. * `samplesPerChunk`: The number of samples per chunk. `onChunk` is called when this many samples are ready. * `timeBegin`: The time in seconds at which the thumbnail sequence should start. * `timeEnd`: The time in seconds at which the thumbnail sequence should end. * `numberOfSamples`: The total number of samples to generate. * `numberOfChannels`: The number of channels in the output. 1 for mono, 2 for stereo. * `onChunk`: This gets passed an index and a chunk of samples whenever it's ready, or an error. ``` const cancelAudioThumbnailGeneration = engine.block.generateAudioThumbnailSequence( audio /* audio or video fill */, 20 /* number of samples per chunk */, 0.5 /* begin time */, 9.5 /* end time */, 10 * 20 /* total number of samples, will produce 10 calls with 20 samples per chunk */, 2, /* stereo, interleaved samples for the left and right channels */, (chunkIndex, chunkSamples) => { drawWavePattern(chunkSamples); } ); ``` Integrate Creative Editor - CE.SDK | IMG.LY Docs [web/angular/javascript] https://img.ly/docs/cesdk/ui/quickstart/?platform=web&language=javascript&framework=angular # Integrate Creative Editor In this example, we will show you how to integrate [CreativeEditor SDK](https://img.ly/products/creative-sdk) in an Angular app. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-angular?title=IMG.LY%27s+CE.SDK%3A+Integrate+With+Angular&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-angular). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-angular?title=IMG.LY%27s+CE.SDK%3A+Integrate+With+Angular&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-angular) ## Prerequisites * (Optional): [Get the latest stable version of **Node.js & NPM**](https://www.npmjs.com/get-npm) * [Setup Angular](https://angular.io/guide/setup-local) ## Setup Note that, for convenience we serve all SDK assets (e.g. images, stickers, fonts etc.) from our CDN by default. For use in a production environment we recommend [serving assets from your own servers](/docs/cesdk/ui/guides/assets-served-from-your-own-servers/). ### 1\. Add CE.SDK to your Project Install the `@cesdk/cesdk-js` dependency via `npm install --save @cesdk/cesdk-js`. The SDK is served entirely via CDN, so we just need to import the `CreativeEditorSDK` module and the stylesheet containing the default theme settings. ``` import CreativeEditorSDK, { Configuration } from '@cesdk/cesdk-js'; ``` ### 2\. Acquire a container element reference We need to provide a container to the CE.SDK. This is done through an `ElementRef`. For this example, the container is specified as a `
` that fills the whole browser window in `app.component.html`: `
` ``` @ViewChild('cesdk_container') containerRef: ElementRef = {} as ElementRef; ``` ### 3\. Instantiate CreativeEditor SDK The last step involves the configuration and instantiation of the SDK. We need to provide the aforementioned container and optionally a license file to instantiate the SDK. ``` const config: Configuration = { license: 'mtLT-_GJwMhE7LDnO8KKEma7qSuzWuDxiKuQcxHKmz3fjaXWY2lT3o3Z2VdL5twm', // Serve assets from IMG.LY cdn or locally baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.43.0/assets', // Enable local uploads in Asset Library callbacks: { onUpload: 'local' } }; CreativeEditorSDK.create(this.containerRef.nativeElement, config).then( async (instance: any) => { // Do something with the instance of CreativeEditor SDK, for example: // Populate the asset library with default / demo asset sources. instance.addDefaultAssetSources(); instance.addDemoAssetSources({ sceneMode: 'Design' }); await instance.createDesignScene(); } ); ``` ### 4\. Serving Webpage locally In order to use the editor we need run `ng serve` for a dev server. This will bring up a server at [`http://localhost:4200/`](http://localhost:4200/) ### Congratulations! You've got CE.SDK up and running. Get to know the SDK and dive into the next steps, when you're ready: * [Perform Basic Configuration](/docs/cesdk/ui/configuration/basics/) * [Serve assets from your own servers](/docs/cesdk/ui/guides/assets-served-from-your-own-servers/) * [Create and use a license key](/docs/cesdk/engine/quickstart/#licensing) * [Configure Callbacks](/docs/cesdk/ui/configuration/callbacks/) * [Add Localization](/docs/cesdk/ui/guides/i18n/) * [Adapt the User Interface](/docs/cesdk/ui/guides/theming/) Modify Appearance - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-appearance/?platform=web&language=javascript # Modify Appearance In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a blocks appearance through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Common Properties Common properties are properties that occur on multiple block types. For instance, fill color properties are available for all the shape blocks and the text block. That's why we built convenient setter and getter functions for these properties. So you don't have to use the generic setters and getters and don't have to provide a specific property path. There are also `has*` functions to query if a block supports a set of common properties. ### Opacity Set the translucency of the entire block. ``` supportsOpacity(id: DesignBlockId): boolean ``` Query if the given block has an opacity. * `id`: The block to query. * Returns true, if the block has an opacity. ``` engine.block.supportsOpacity(image); ``` ``` setOpacity(id: DesignBlockId, opacity: number): void ``` Set the opacity of the given design block. Required scope: 'layer/opacity' * `id`: The block whose opacity should be set. * `opacity`: The opacity to be set. The valid range is 0 to 1. ``` engine.block.setOpacity(image, 0.5); ``` ``` getOpacity(id: DesignBlockId): number ``` Get the opacity of the given design block. * `id`: The block whose opacity should be queried. * Returns The opacity. ``` engine.block.getOpacity(image); ``` ### Blend Mode Define the blending behavior of a block. ``` supportsBlendMode(id: DesignBlockId): boolean ``` Query if the given block has a blend mode. * `id`: The block to query. * Returns true, if the block has a blend mode. ``` engine.block.supportsBlendMode(image); ``` ``` setBlendMode(id: DesignBlockId, blendMode: BlendMode): void ``` Set the blend mode of the given design block. Required scope: 'layer/blendMode' * `id`: The block whose blend mode should be set. * `blendMode`: The blend mode to be set. * Returns ``` engine.block.setBlendMode(image, 'Multiply'); ``` ``` getBlendMode(id: DesignBlockId): BlendMode ``` Get the blend mode of the given design block. * `id`: The block whose blend mode should be queried. * Returns The blend mode. ``` engine.block.getBlendMode(image); ``` ``` type BlendMode = 'PassThrough' | 'Normal' | 'Darken' | 'Multiply' | 'ColorBurn' | 'Lighten' | 'Screen' | 'ColorDodge' | 'Overlay' | 'SoftLight' | 'HardLight' | 'Difference' | 'Exclusion' | 'Hue' | 'Saturation' | 'Color' | 'Luminosity' ``` ``` engine.block.setBlendMode(image, 'Multiply'); ``` Adding New Buttons - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/guides/addingNewButtons/#adding-a-document-button-to-the-dock CESDK/CE.SDK/Web Editor/Customization/Guides # Adding New Buttons Learn how to add new buttons to different location of the web editor Customization not only includes changing the existing behavior but also extends to adding new features tailored to your needs. This guide will show you how to add new buttons to different locations within the web editor. ## Adding a Document Button to the Dock In our default configuration of the web editor, we only show dock buttons that open asset libraries. In this example, we want to extend that functionality by adding a button that opens the document inspector. In the default view of the web editor, the inspector panel is not always visible. Additionally, the document inspector only appears if no block is selected. If your use case requires a simple user interface with just the inspector bar and an easy way for the customer to access the document inspector, we can now add a custom button to achieve this. First, we need to register a new component. This component will check if the document inspector is already open. To open it, we will deselect all blocks and use the [Panel API](/docs/cesdk/ui/customization/api/panel/) to open the inspector panel. ``` cesdk.ui.registerComponent( 'document.dock', ({ builder: { Button }, engine }) => { const inspectorOpen = cesdk.ui.isPanelOpen('//ly.img.panel/inspector'); Button('open-document', { label: 'Document', // Using the open state to mark the button as selected isSelected: inspectorOpen, onClick: () => { // Deselect all blocks to enable the document inspector engine.block.findAllSelected().forEach((blockId) => { engine.block.setSelected(blockId, false); }); if (inspectorOpen) { cesdk.ui.closePanel('//ly.img.panel/inspector'); } else { cesdk.ui.openPanel('//ly.img.panel/inspector'); } } }); } ); ``` That is all for the component for now. What is left to do is to add this component to the dock order. ``` cesdk.ui.setDockOrder([ ...cesdk.ui.getDockOrder(), // We add a spacer to push the document inspector button to the bottom 'ly.img.spacer', // The id of the component we registered earlier 'document.dock' ]); ``` ## Add a Button to the Canvas Menu to Mark Blocks In our next example, we aim to establish a workflow where a designer can review a design and mark blocks that need to be reviewed by another designer. We will define a field in the metadata for this purpose and add a button to the canvas menu that toggles this marker. Once again, we start by registering a new component. ``` cesdk.ui.registerComponent( 'marker.canvasMenu', ({ builder: { Button }, engine }) => { const selectedBlockIds = engine.block.findAllSelected(); // Only show the button if exactly one block is selected if (selectedBlockIds.length !== 1) return; const selectedBlockId = selectedBlockIds[0]; // Check if the block is already marked const isMarked = engine.block.hasMetadata(selectedBlockId, 'marker'); Button('marker-button', { label: isMarked ? 'Unmark' : 'Mark', // Change the color if the block is marked to indicate the state color: isMarked ? 'accent' : undefined, onClick: () => { if (isMarked) { engine.block.removeMetadata(selectedBlockId, 'marker'); } else { // Metadata is a simple key-value store. We do not care about the // actual value but only if it was set. engine.block.setMetadata(selectedBlockId, 'marker', 'marked'); } } }); } ); ``` Now, instead of appending this button, we want to put it at the beginning as this needs to be a prominent feature. ``` cesdk.ui.setCanvasMenuOrder([ 'marker.canvasMenu', ...cesdk.ui.getCanvasMenuOrder() ]); ``` [ Previous Moving Existing Buttons ](/docs/cesdk/ui/customization/guides/movingExistingButtons/)[ Next One-Click Quick Action Plugins ](/docs/cesdk/ui/customization/guides/oneClickQuickActionPlugins/) Save Scenes to an Archive - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/save-scene-to-archive/?platform=web&language=javascript # Save Scenes to an Archive In this example, we will show you how to save scenes as an archive with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-save-scene-to-archive?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Save+Scene+To+Archive&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-save-scene-to-archive). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-save-scene-to-archive?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Save+Scene+To+Archive&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-save-scene-to-archive) The CreativeEngine allows you to save scenes in a binary format to share them between editors or store them for later editing. As an archive, the resulting `Blob` includes all pages and any hidden elements and all the asset data. To get hold of such a `Blob`, you need to use `engine.scene.saveToArchive()`. This is an asynchronous method returning a `Promise`. After waiting for the `Promise` to resolve, we receive a `Blob` holding the entire scene currently loaded in the editor including its assets' data. ``` engine.scene .saveToArchive() .then((blob) => { const formData = new FormData(); formData.append('file', blob); fetch('/upload', { method: 'POST', body: formData }); }) .catch((error) => { console.error('Save failed', error); }); ``` That `Blob` can then be treated as a form file parameter and sent to a remote location. ``` const formData = new FormData(); formData.append('file', blob); fetch('/upload', { method: 'POST', body: formData }); ``` ## Loading Scene Archives Loading a scene archives requires unzipping the archives contents to a location, that's accessible to the CreativeEngine. One could for example unzip the archive via `unzip archive.zip` and then serve its contents using `$ npx serve`. This spins up a local test server, that serves everything contained in the current directory at `http://localhost:3000` The archive can then be loaded by calling `await engine.scene.loadFromURL('http://localhost:3000/scene.scene')`. See [loading scenes](/docs/cesdk/engine/guides/load-scene-from-url/) for more details. All asset paths in the archive are then resolved relative to the location of the `scene.scene` file. For an image, that would result in `'http://localhost:3000/images/1234.jpeg'`. After loading all URLs are fully resolved with the location of the `scene.scene` file and the scene behaves like any other scene. ## Resolving assets from a different source The engine will use its [URI resolver](/docs/cesdk/engine/guides/resolve-custom-uri/) to resolve all asset paths it encounters. This allows you to redirect requests for the assets contained in archive to a different location. To do so, you can add a custom resolver, that redirects requests for assets to a different location. Assuming you store your archived scenes in a `scenes/` directory, this would be an example of how to do so: Load Scenes From a Blob - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/load-scene-from-blob/?platform=web&language=javascript # Load Scenes From a Blob In this example, we will show you how to load scenes with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-blob?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Load+Scene+From+Blob&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-blob). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-blob?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Load+Scene+From+Blob&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-blob) Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. This can be done by passing a binary string that describes a scene to the `engine.scene.loadFromString()` function. #### Warning Saving a scene can be done as a either _scene file_ or as an _archive file_ (c.f. [Saving scenes](/docs/cesdk/engine/guides/save-scene/)). A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an _archive file_ contains within it the scene's assets and references them as relative URIs. In this example, we fetch a scene from a remote URL and load it as `sceneBlob`. ``` const sceneUrl = 'https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene'; const sceneBlob = await fetch(sceneUrl).then((response) => { return response.blob(); }); ``` To acquire a scene string from `sceneBlob`, we need to read its contents into a string. ``` const blobString = await sceneBlob.text(); ``` We can then pass that string to the `engine.scene.loadFromString(sceneContent: string): Promise` function. The editor will reset and present the given scene to the user. The function is asynchronous and the returned `Promise` resolves if the scene load succeeded. ``` let scene = await engine.scene .loadFromString(blobString) .then(() => { console.log('Load succeeded'); let text = engine.block.findByType('text')[0]; engine.block.setDropShadowEnabled(text, true); }) .catch((error) => { console.error('Load failed', error); }); ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` let text = engine.block.findByType('text')[0]; engine.block.setDropShadowEnabled(text, true); ``` Scene loads may be reverted using `cesdk.engine.editor.undo()`. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Source Sets - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/source-sets/?platform=web&language=javascript # Source Sets Source sets allow specifying an entire set of sources, each with a different size, that should be used for drawing a block. The appropriate source is then dynamically chosen based on the current drawing size. This allows using the same scene to render a preview on a mobile screen using a small image file and a high-resolution file for print in the backend. This guide will show you how to specify source sets both for existing blocks and when defining assets. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Source+Sets&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Source+Sets&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets) ### Drawing When an image needs to be drawn, the current drawing size in screen pixels is calculated and the engine looks up the most appropriate source file to draw at that resolution. 1. If a source set is set, the source with the closest size exceeding the drawing size is used 2. If no source set is set, the full resolution image is downscaled to a maximum edge length of 4096 (configurable via `maxImageSize` setting) and drawn to the target area This also gives more control about up- and downsampling to you, as all intermediate resolutions can be generated using tooling of your choice. **Source sets are also used during export of your designs and will resolve to the best matching asset for the export resolution.** ## Setup the scene We first create a new scene with a new page. ``` document.getElementById('cesdk_container').append(engine.element); const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); engine.block.appendChild(scene, page); engine.scene.zoomToBlock(page, 50, 50, 50, 50); ``` ## Using a Source Set for an existing Block To make use of a source set for an existing image fill, we use the `setSourceSet` API. This defines a set of sources and specifies height and width for each of these sources. The engine then chooses the appropriate source during drawing. You may query an existing source set using `getSourceSet`. You can add sources to an existing source set with `addImageFileURIToSourceSet`. ``` let block = engine.block.create('graphic'); engine.block.setShape(block, engine.block.createShape('rect')); const imageFill = engine.block.createFill('image'); engine.block.setSourceSet(imageFill, 'fill/image/sourceSet', [ { uri: 'https://img.ly/static/ubq_samples/sample_1_512x341.jpg', width: 512, height: 341 }, { uri: 'https://img.ly/static/ubq_samples/sample_1_1024x683.jpg', width: 1024, height: 683 }, { uri: 'https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg', width: 2048, height: 1366 } ]); engine.block.setFill(block, imageFill); console.log(engine.block.getSourceSet(imageFill, 'fill/image/sourceSet')); engine.block.appendChild(page, block); ``` ## Using a Source Set in an Asset For assets, source sets can be defined in the `payload.sourceSet` field. This is directly translated to the `sourceSet` property when applying the asset. The resulting block is configured in the same way as the one described above. The code demonstrates how to add an asset that defines a source set to a local source and how `applyAsset` handles a populated `payload.sourceSet`. ``` const assetWithSourceSet = { id: 'my-image', meta: { kind: 'image', fillType: '//ly.img.ubq/fill/image' }, payload: { sourceSet: [ { uri: 'https://img.ly/static/ubq_samples/sample_1_512x341.jpg', width: 512, height: 341 }, { uri: 'https://img.ly/static/ubq_samples/sample_1_1024x683.jpg', width: 1024, height: 683 }, { uri: 'https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg', width: 2048, height: 1366 } ] } }; ``` ## Video Source Sets Source sets can also be used for video fills. This is done by setting the `sourceSet` property of the video fill. The engine will then use the source with the closest size exceeding the drawing size. Thumbnails will use the smallest source if `features/matchThumbnailSourceToFill` is disabled, which is the default. For low end devices or scenes with large videos, you can force the preview to always use the smallest source when editing by enabling `features/forceLowQualityVideoPreview`. On export, the highest quality source is used in any case. ``` const videoFill = engine.block.createFill('video'); engine.block.setSourceSet(videoFill, 'fill/video/sourceSet', [ { uri: 'https://img.ly/static/example-assets/sourceset/1x.mp4', width: 1920, height: 1080 } ]); await engine.block.addVideoFileURIToSourceSet( videoFill, 'fill/video/sourceSet', 'https://img.ly/static/example-assets/sourceset/2x.mp4' ); ``` Observe Editing State - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/editor-state/?platform=web&language=javascript # Observe Editing State In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to set and query the editor state in the `editor` API, i.e., what type of content the user is currently able to edit. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## State The editor state consists of the current edit mode, which informs what type of content the user is currently able to edit. The edit mode can be set to either `'Transform'`, `'Crop'`, `'Text'`, or a custom user-defined one. You can also query the intended mouse cursor and the location of the text cursor while editing text. Instead of having to constantly query the state in a loop, you can also be notified when the state has changed to then act on these changes in a callback. ``` onStateChanged: (callback: () => void) => (() => void) ``` Subscribe to changes to the editor state. * `callback`: This function is called at the end of the engine update, if the editor state has changed. * Returns A method to unsubscribe. ``` const unsubscribeState = engine.editor.onStateChanged(() => ``` ``` setEditMode(mode: EditMode): void ``` Set the edit mode of the editor. An edit mode defines what type of content can currently be edited by the user. Hint: the initial edit mode is "Transform". * `mode`: "Transform", "Crop", "Text", "Playback", "Trim" or a custom value. ``` engine.editor.setEditMode('Crop'); ``` ``` getEditMode(): EditMode ``` Get the current edit mode of the editor. An edit mode defines what type of content can currently be edited by the user. * Returns "Transform", "Crop", "Text", "Playback", "Trim" or a custom value. ``` engine.editor.getEditMode(); // 'Crop' ``` ``` unstable_isInteractionHappening(): boolean ``` If an user interaction is happening, e.g., a resize edit with a drag handle or a touch gesture. * Returns True if an interaction is happening. ``` engine.editor.unstable_isInteractionHappening(); ``` ## Cursor ``` getCursorType(): 'Arrow' | 'Move' | 'MoveNotPermitted' | 'Resize' | 'Rotate' | 'Text' ``` Get the type of cursor that should be displayed by the application. * Returns The cursor type. ``` engine.editor.getCursorType(); ``` ``` getCursorRotation(): number ``` Get the rotation with which to render the mouse cursor. * Returns The angle in radians. ``` engine.editor.getCursorRotation(); ``` ``` getTextCursorPositionInScreenSpaceX(): number ``` Get the current text cursor's x position in screen space. * Returns The text cursor's x position in screen space. ``` engine.editor.getTextCursorPositionInScreenSpaceX(); ``` ``` getTextCursorPositionInScreenSpaceY(): number ``` Get the current text cursor's y position in screen space. * Returns The text cursor's y position in screen space. ``` engine.editor.getTextCursorPositionInScreenSpaceY(); ``` Creating a Scene from Scratch - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/create-scene-from-scratch/?platform=web&language=javascript # Creating a Scene from Scratch In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) from scratch and add a star shape. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-scratch?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Scratch&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-scratch). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-scratch?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Scratch&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-scratch) We create an empty scene via `engine.scene.create()` which sets up the default scene block with a camera attached. Afterwards, the scene can be populated by creating additional blocks and appending them to the scene. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` let scene = await engine.scene.create(); ``` We first add a page with `create(type: DesignBlockType): number` specifying a `"page"` and set a parent-child relationship between the scene and this page. ``` let page = engine.block.create('page'); engine.block.appendChild(scene, page); ``` To this page, we add a graphic block, again with `create(type: DesignBlockType): number`. To make it more interesting, we set a star shape and a color fill to this block to give it a visual representation. Like for the page, we set the parent-child relationship between the page and the newly added block. From then on, modifications to this block are relative to the page. ``` let block = engine.block.create('graphic'); engine.block.setShape(block, engine.block.createShape('star')); engine.block.setFill(block, engine.block.createFill('color')); engine.block.appendChild(page, block); ``` This example first appends a page child to the scene as would typically be done but it is not strictly necessary and any child block can be appended directly to a scene. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). How to Write Custom Panels - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/api/customPanel/ CESDK/CE.SDK/Web Editor/Customization/API Reference # How to Write Custom Panels Learn how to add your own content in a custom panel Similar to registering [custom components](/docs/cesdk/ui/customization/api/registerComponents/), the CE.SDK web editor allows you to render custom panels. Built-in UI elements ensure that your custom panel blends seamlessly with the editor's look and feel, while still enabling you to adapt the editor to your unique workflows. ``` registerPanel(panelId: string, renderFunction: ({ builder, engine, state }) => void) ``` Registers a panel whose content can be rendered with a panel builder, similar to how custom components are registered. The builder's render function is called with three arguments: the builder, the engine, and the state. The builder object is used to define which base components (such as buttons) should be rendered. The engine is used to retrieve any state from the engine. The render function will be called again if any engine-related state changes occur due to engine calls or the local state was updated. * `panelId`: The unique ID of the new registered panel. * `renderFunction`: The render function of the panel. ## When is a Panel Rendered? It is important concept to understand when a panel re-renders after its initial rendering. The component assumes that all its state is stored in the `engine` or the local state. Whenever we detect a change that is relevant for this panel it will re-render. ### Using the Engine Inside the render function, any calls that retrieve values from the engine will be tracked. If a change is detected, the render function will be automatically re-called. For example, in a panel, we could query for all selected blocks and decide whether to render a button based on the result. The engine detects the call to findAllSelected and knows that if the selection changes, it needs to re-render the panel. This behavior applies to all methods provided by the engine. ### Using Local State Besides the `engine`, the render function also receives a `state` argument to manage local state. This can be used to control input components or store state in general. When the state changes, the panel will be re-rendered as well. The `state` argument is a function that is called with a unique ID for the state. Any subsequent call to the state within this panel will return the same state. The second optional argument is the default value for this state. If the state has not been set yet, it will return this value. Without this argument, you'll need to handle `undefined` values manually. The return value of the state call is an object with `value` and `setValue` properties, which can be used to get and set the state. Since these property names match those used by input components, the object can be directly spread into the component options. ``` cesdk.ui.registerPanel('counter', ({ builder, state }) => { const urlState = state('url', ''); const { value, setValue } = state('counter', 0); builder.Section('counter', { children: () => { builder.TextInput('text-input-1', { inputLabel: 'TextInput 1', ...urlState }); builder.Button('submit-button', { label: 'Submit', onClick: () => { const pressed = value + 1; setValue(pressed); console.log( `Pressed ${pressed} times with the current URL ${urlState.value}` ); } }); } }); }); ``` ## Integrate the Panel into the Editor After registering a component, it is not automatically opened or integrated in any other way. The CE.SDK editor is just aware that there is a panel with this id. ### Opening a Panel Once a panel is registered, it can be controlled using the [Panel API](/docs/cesdk/ui/customization/api/panel/), just like any other panel. For instance, you can open a custom panel with `cesdk.ui.openPanel(registeredPanelId)`. Other settings, such as position and floating, can also be adjusted accordingly. In most cases, you will want to open it using a custom button, .e.g in a button inside the [Dock](/docs/cesdk/ui/customization/api/dock/) or [Inspector Bar](/docs/cesdk/ui/customization/api/inspectorBar/). ### Setting the Title of a Custom Panel The title of a panel is determined by a translation based on the panel's ID. For example, if the panel ID is `myCustomPanel`, the corresponding translation key would be `panel.myCustomPanel`. ``` cesdk.setTranslations({ en: { 'panel.myCustomPanel': 'My Custom Panel', }, de: { 'panel.myCustomPanel': 'Mein eigenes Panel', } }); ``` ## Using the Builder The `builder` object passed to the render function provides a set of methods to create UI elements. Calling this method will add a builder component to the user interface. This includes buttons, dropdowns, text, etc. ## Builder Components The following builder components can be used inside a registered panel. ### Button A simple button to react on a user click. Property Description Property `label` Description The label inside the button. If it is a i18n key, it will be used for translation. Property `labelAlignment` Description How the label inside the button is aligned. Either `left` or `center`. Property `inputLabel` Description An optional label next to/above the button. If it is a i18n key, it will be used for translation. Property `inputLabelPosition` Description Input label position relative to the button - either `'top'` or `'left'`. Property `tooltip` Description A tooltip displayed upon hover. If it is a i18n key, it will be used for translation. Property `onClick` Description Executed when the button is clicked. Property `variant` Description The variant of the button. `regular` is the default variant, `plain` is a variant without any background or border. Property `color` Description The color of the button. `accent` is the accent color to stand out, `danger` is a red color. Property `icon` Description The icon of the button. See [Icon API](/docs/cesdk/ui/customization/api/icons/) for more information about how to add new icons. Property `size` Description The size of the button. Either `'normal'` or `'large'`. Property `isActive` Description A boolean to indicate that the button is active. Property `isSelected` Description A boolean to indicate that the button is selected. Property `isDisabled` Description A boolean to indicate that the button is disabled. Property `isLoading` Description A boolean to indicate that the button is in a loading state. Property `loadingProgress` Description A number between 0 and 1 to indicate the progress of a loading button. Property `suffix` Description An object with properties similar to those of a Button. When provided, it will render a button without a label on the right side of the component. If the object includes only an icon (with a tooltip), only the icon will be displayed. An empty object will render as an empty space, which can be useful for aligning multiple components. ### ButtonGroup Grouping of multiple buttons in a single segmented control. Property Description Property `inputLabel` Description An optional label next to/above the button group. If it is a i18n key, it will be used for translation. Property `inputLabelPosition` Description Input label position relative to the button group - either `'top'` or `'left'`. Property `children` Description A function executed to render grouped buttons. Only the `Button`, `Dropdown` and `Select` builder components are allowed to be rendered inside this function. Property `suffix` Description An object with properties similar to those of a Button. When provided, it will render a button without a label on the right side of the component. If the object includes only an icon (with a tooltip), only the icon will be displayed. An empty object will render as an empty space, which can be useful for aligning multiple components. ### Checkbox A labeled checkbox. Property Description Property `inputLabel` Description An optional label next to the checkbox. If it is a i18n key, it will be used for translation. Property `inputLabelPosition` Description Input label position relative to the checkbox - either `'left'` (default) or `'right'`. Property `icon` Description The icon of the checkbox. See [Icon API](/docs/cesdk/ui/customization/api/icons/) for more information about how to add new icons. Property `isDisabled` Description A boolean to indicate that the checkbox is disabled. ### ColorInput A big color swatch that opens a color picker when clicked. Property Description Property `inputLabel` Description An optional label next to the color input. If it is a i18n key, it will be used for translation. Property `inputLabelPosition` Description Input label position relative to the color input - either `'left'` (default) or `'top'`. Property `icon` Description The icon of the checkbox. See [Icon API](/docs/cesdk/ui/customization/api/icons/) for more information about how to add new icons. Property `isDisabled` Description A boolean to indicate that the color input is disabled. Property `value` Description The color value that the input is showing. Property `setValue` Description A callback that receives the new color value when the user picks one. Property `suffix` Description An object with properties similar to those of a Button. When provided, it will render a button without a label on the right side of the component. If the object includes only an icon (with a tooltip), only the icon will be displayed. An empty object will render as an empty space, which can be useful for aligning multiple components. ### Dropdown A button that opens a dropdown with additional content when the user clicks on it. Property Description Property `label` Description The label inside the button. If it is a i18n key, it will be used for translation. Property `tooltip` Description A tooltip displayed upon hover. If it is a i18n key, it will be used for translation. Property `variant` Description The variant of the button. `regular` is the default variant, `plain` is a variant without any background or border. Property `color` Description The color of the button. `accent` is the accent color to stand out, `danger` is a red color. Property `icon` Description The icon of the button. See [Icon API](/docs/cesdk/ui/customization/api/icons/) for more information about how to add new icons. Property `isActive` Description A boolean to indicate that the button is active. Property `isSelected` Description A boolean to indicate that the button is selected. Property `isDisabled` Description A boolean to indicate that the button is disabled. Property `isLoading` Description A boolean to indicate that the button is in a loading state. Property `loadingProgress` Description A number between 0 and 1 to indicate the progress of a loading button. Property `children` Description A function executed to render the content of the dropdown. Every builder component called in this children function, will be rendered in the dropdown. Property `suffix` Description An object with properties similar to those of a Button. When provided, it will render a button without a label on the right side of the component. If the object includes only an icon (with a tooltip), only the icon will be displayed. An empty object will render as an empty space, which can be useful for aligning multiple components. ### Heading Renders its content as heading to the panel. Property Description Property `content` Description The content of the header as a string. ### Library A complete asset library with a search option. Property Description Property `entries` Description An array of Asset Library entry IDs that determines which assets are shown in which way. See [Asset Library Entry](/docs/cesdk/ui/customization/api/assetLibraryEntry/) for reference. Property `onSelect` Description A callback receiving the selected asset when the user picks one. Property `searchable` Description When set, makes the Library searchable. ### MediaPreview A large preview area showing an image or color. Optionally contains a button in the bottom right corner, offering contextual action. Property Description Property `size` Description Size of the preview, either `'small'` or `'medium'` (default). Property `preview` Description Determines the kind of preview. Can be either an image, or a color. Use an object with the following shape: `{ type: 'image', uri: string }` or `{ type: 'color', color: string }` Property `action` Description Button option object that is passed along to the overlayed button. See [Button](/docs/cesdk/ui/customization/api/customPanel/#button) for reference. ### NumberInput A number input field. Property Description Property `inputLabel` Description An optional label next to/above the number input. If it is a i18n key, it will be used for translation. Property `inputLabelPosition` Description Input label position relative to the input - either `'top'` or `'left'`. Property `value` Description The number contained in the input. Property `setValue` Description A callback that receives the new number when it is changed by the user. Property `min` Description Minimum value of the input. Property `max` Description Maximum value of the input. Property `step` Description Interval of changes when using the arrow keys to change the number. Property `suffix` Description An object with properties similar to those of a Button. When provided, it will render a button without a label on the right side of the component. If the object includes only an icon (with a tooltip), only the icon will be displayed. An empty object will render as an empty space, which can be useful for aligning multiple components. ### Select A dropdown to select one of multiple choices. Property Description Property `inputLabel` Description An optional label next to/above the control. If it is a i18n key, it will be used for translation. Property `inputLabelPosition` Description Input label position relative to the control - either `'top'` or `'left'`. Property `tooltip` Description A tooltip displayed upon hover. If it is a i18n key, it will be used for translation. Property `value` Description The currently selected value. Property `setValue` Description A callback that receives the new selection when it is changed by the user. Property `values` Description The set of values from which the user can select. Property `isDisabled` Description A boolean to indicate that the control is disabled. Property `isLoading` Description A boolean to indicate that the control is in a loading state. Property `loadingProgress` Description A number between 0 and 1 to indicate the progress of loading. Property `suffix` Description An object with properties similar to those of a Button. When provided, it will render a button without a label on the right side of the component. If the object includes only an icon (with a tooltip), only the icon will be displayed. An empty object will render as an empty space, which can be useful for aligning multiple components. ### Separator Adds a separator (`
` element) in the panel to help the visual separation of entries. This builder component has no properties. ### Slider A slider for intuitively adjusting a number. Property Description Property `inputLabel` Description An optional label next to/above the text field. If it is a i18n key, it will be used for translation. Property `inputLabelPosition` Description Input label position relative to the input - either `'top'` or `'left'`. Property `value` Description The text contained in the field. Property `setValue` Description A callback that receives the new text when it is changed by the user. Property `min` Description Minimum value of the slider. Property `max` Description Maximum value of the slider. Property `step` Description Interval of the slider movement. Property `centered` Description Adds a snapping point in the middle of the slider. Property `suffix` Description An object with properties similar to those of a Button. When provided, it will render a button without a label on the right side of the component. If the object includes only an icon (with a tooltip), only the icon will be displayed. An empty object will render as an empty space, which can be useful for aligning multiple components. ### Text A piece of non-interactive text. Property Description Property `content` Description The content of the text as a string. ### TextArea A large text field accepting user input. Property Description Property `inputLabel` Description An optional label above the text field. If it is a i18n key, it will be used for translation. Property `value` Description The text contained in the field. Property `setValue` Description A callback that receives the new text when it is changed by the user. Property `isDisabled` Description A boolean to indicate that the text area is disabled. ### TextInput A small text field accepting user input. Property Description Property `inputLabel` Description An optional label next to/above the text field. If it is a i18n key, it will be used for translation. Property `inputLabelPosition` Description Input label position relative to the input - either `'top'` or `'left'`. Property `value` Description The text contained in the field. Property `setValue` Description A callback that receives the new text when it is changed by the user. Property `isDisabled` Description A boolean to indicate that the text field is disabled. Property `suffix` Description An object with properties similar to those of a Button. When provided, it will render a button without a label on the right side of the component. If the object includes only an icon (with a tooltip), only the icon will be displayed. An empty object will render as an empty space, which can be useful for aligning multiple components. [ Previous Custom Components ](/docs/cesdk/ui/customization/api/registerComponents/)[ Next Feature ](/docs/cesdk/ui/customization/api/features/) Controlling the UI with the Feature API - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/api/features/ CESDK/CE.SDK/Web Editor/Customization/API Reference # Controlling the UI with the Feature API Use the Feature API to control what the user can do ## Enable Feature The following API allows to enable a custom or built-in feature (see the list of [built-in features](/docs/cesdk/ui/customization/api/features/#built-in-features) below): ``` enable(featureId: FeatureId | FeatureId[], predicate: FeaturePredicate) ``` The `predicate` is either a boolean that forces the features to be enabled or not, or a function that takes a context and returns a boolean value. (The type `FeaturePredicate` is `boolean | ((context: EnableFeatureContext) => boolean)`, where `EnableFeatureContext` is `{engine: CreativeEngine, isPreviousEnable: () => boolean}`.) Providing a function has the added benefit of receiving the return value of any previous predicates. This is useful for overwriting the default behavior under specific circumstances, or for chaining more complex predicates. ### Example: Chaining Predicates ``` // Enable the feature by default cesdk.feature.enable('my.feature', true); // Disable the feature after 10 pm cesdk.feature.enable( 'my.feature', ({isPreviousEnable}) => (new Date().getHours() >= 22 ? false : isPreviousEnable()) ); ``` Tip: `FeatureId` can be an arbitrary string, so you can use this mechanism to toggle your own custom features, as well. ### Example: Overwriting Defaults Let's say you introduced a new kind of block that represents an Avatar. Regardless of scopes or the current mode of the editor, you want to hide the duplicate & delete button in the canvas menu for this kind of block. You can achieve this by adding new predicates to change the default feature behavior: ``` cesdk.feature.enable( 'ly.img.delete', ({engine, isPreviousEnable}) => { const selectedBlock = engine.block.findAllSelected()[0]; const kind = engine.block.getKind( selectedBlock ); if (kind === 'avatar') { return false; } else { return isPreviousEnable(); } } ); cesdk.feature.enable( 'ly.img.duplicate', ({engine, isPreviousEnable}) => { const selectedBlock = engine.block.findAllSelected()[0]; const kind = engine.block.getKind( selectedBlock ); if (kind === 'avatar') { return false; } else { return isPreviousEnable(); } } ); ``` ## Check Feature The following API allows to query if the feature is currently enabled: ``` isEnabled(featureId: FeatureId, context: FeatureContext) ``` The type `FeatureContext` is `{engine: CreativeEngine}`, meaning that you will need to supply the active engine instance when checking a feature, for Example: ``` if (!cesdk.feature.isEnabled('ly.img.navigate.close', { engine })) { return null; } ``` ## Built-In Features The following features are built-in to CE.SDK: Feature ID Description Feature ID `ly.img.navigate.back` Description Controls visibility of the "Back" button in the Navigation Bar. Defaults to the value of the configuration option `ui.elements.navigation.action.back`, or `true` if not set. Feature ID `ly.img.navigate.close` Description Controls visibility of the "Close" button in the Navigation Bar. Defaults to the value of the configuration option `ui.elements.navigation.action.close`, or `true` if not set. Feature ID `ly.img.delete` Description Controls the ability to delete a block. Changes visibility of the "Delete" button in the Canvas Menu, as well as the ability to delete by pressing the `delete` key. Will return `false` when the operation would delete all pages of the document. Will return `false` when target block is a page and configuration option `ui.elements.blocks['//ly.img.ubq/page'].manage` is `false`, or during `'Video'` scene mode. Feature ID `ly.img.duplicate` Description Controls visibility of the "Duplicate" button in the Canvas Menu. Does currently **not** influence the ability to copy/paste using keyboard shortcuts. Will return `false` when target block is a page and configuration option `ui.elements.blocks['//ly.img.ubq/page'].manage` is `false`, or during `'Video'` scene mode, or when scene layout is set to `'Free'` or `'DepthStack'`. Feature ID `ly.img.placeholder` Description Controls visibility of the "Placeholder" button in the Canvas Menu. Will return `true` for blocks of type `'//ly.img.ubq/text'`, `'//ly.img.ubq/group'`, `'//ly.img.ubq/page'`, `'//ly.img.ubq/audio'`, or `'//ly.img.ubq/graphic'`. Feature ID `ly.img.preview` Description Controls existence of the "Preview" button in the Navigation Bar. Returns `true` when `cesdk.engine.editor.getRole()` returns `Creator`, e.g. when editing in the Creator role. Feature ID `ly.img.page.move` Description Controls the ability to move pages, by showing the "Move Up/Down/Left/Right" buttons in the Canvas Menu. Returns `false` during `'Video'` scene mode, or when scene layout is set to `'Free'` or `'DepthStack'`. Note that the "Move Up/Left" components will not show up when the target page is already the first page of the document, and "Move Down/Right" will not show up when the target page is already the last page of the document. Feature ID `ly.img.page.add` Description Controls the ability to add pages, by showing the "Add Page" button in the Canvas Bar. Returns `false` during `'Video'` scene mode, or when scene layout is set to `'Free'` or `'DepthStack'`. Feature ID `ly.img.group` Description Controls features for creating and dissolving groups, currently: The existence of "Group" and "Ungroup" buttons in the Inspector Bar. Returns `false` during `'Video'` scene mode. Feature ID `ly.img.replace` Description Controls presence of the "Replace" button in the Canvas Menu, and in the Fill Panel for image and video fills. Returns `false` by default for stickers. Feature ID `ly.img.text.edit` Description Controls presence of the "Edit" button in the Canvas Menu. Only returns `true` for text blocks by default. Feature ID `ly.img.text.typeface` Description Controls presence of the Typeface dropdown, and can disable the corresponding dropdown in the Advanced UI Inspector Panel. Only returns `true` for text blocks by default. Feature ID `ly.img.text.fontSize` Description Controls presence of the Font Size input, and can disable the corresponding input in the Advanced UI Inspector Panel. Only returns `true` for text blocks by default. Feature ID `ly.img.text.fontStyle` Description Controls presence of the Font Style controls (Bold toggle, Italic toggle), in the Canvas Menu, and can disable the corresponding inputs in the Advanced UI Inspector Panel. Only returns `true` for text blocks by default. Feature ID `ly.img.text.alignment` Description Controls presence of the Text Horizontal Alignment dropdown, and can disable the corresponding controls in the Advanced UI Inspector Panel. Only returns `true` for text blocks by default. Feature ID `ly.img.text.advanced` Description Controls the presence of the Advanced text controls in the Editor. Only returns `true` for text blocks by default. Feature ID `ly.img.adjustment` Description Controls visibility of the "Adjustments" button, and can disable the corresponding button in the Advanced UI Inspector Panel. Returns `false` for stickers by default. Feature ID `ly.img.filter` Description Controls visibility of the "Filter" button, and can disable the corresponding button in the Advanced UI Inspector Panel. Returns `false` for stickers by default. Feature ID `ly.img.effect` Description Controls visibility of the "Effect" button, and can disable the corresponding button in the Advanced UI Inspector Panel. Returns `false` for stickers by default. Feature ID `ly.img.blur` Description Controls visibility of the "Blur" button, and can disable the corresponding button in the Advanced UI Inspector Panel. Returns `false` for stickers by default. Feature ID `ly.img.shadow` Description Controls visibility of the Shadow button, and can disable the corresponding control in the Advanced UI Inspector Panel. Returns `false` for pages by default. Feature ID `ly.img.cutout` Description Controls visibility of the Cutout controls (Cutout Type, Cutout Offset, Cutout Smoothing), and can disable the corresponding controls in the Advanced UI Inspector Panel. Only returns `true` for cutouts by default. Feature ID `ly.img.fill` Description Controls presence of the Fill button (opening the Fill Panel), and can disable the corresponding button in the Advanced UI Inspector Panel. Returns `false` for stickers by default. Feature ID `ly.img.shape.options` Description Controls presence of the Shape Options dropdown in the Default UI Inspector Bar, and can disable the corresponding button in the Advanced UI Inspector Panel. Returns `true` by default, but note that the component itself checks for the target block type and will only render for shape blocks. Feature ID `ly.img.combine` Description Controls presence of the Combine dropdown, and can disable the corresponding button in the Advanced UI Inspector Panel. Returns `true` by default, but note that the component itself checks for the target block type and will only render if the selection contains only shape blocks and text, or only cutouts. Feature ID `ly.img.trim` Description Controls presence of the Trim button, and can disable the corresponding button in the Fill Panel. Only returns `true` when scene mode is `'Video'`. Feature ID `ly.img.crop` Description Controls presence of the Trim button, and can disable the corresponding button in the Fill Panel. Returns `false` for stickers by default. Feature ID `ly.img.volume` Description Controls presence of the Volume control, and can disable the corresponding button in the Fill Panel. Only returns `true` when scene mode is `'Video'`. Feature ID `ly.img.stroke` Description Controls presence of the Stroke controls (Color, Width, Stroke), and can disable the corresponding controls in the Advanced UI Inspector Panel. Returns `false` for stickers by default. Feature ID `ly.img.position` Description Controls presence of the Position dropdown, and can disable the corresponding controls in the Advanced UI Inspector Panel. Returns `false` if target block is a page. Feature ID `ly.img.options` Description Controls presence of the Options button (`...` button) in the Default UI Inspector Bar. Returns `true` by default. Feature ID `ly.img.animations` Description Controls presence of the Animations button. Returns `true` by default for Graphic and Text blocks in video mode. [ Previous Custom Panels ](/docs/cesdk/ui/customization/api/customPanel/)[ Next Asset Library Entry ](/docs/cesdk/ui/customization/api/assetLibraryEntry/) Modify Properties - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-properties/?platform=web&language=javascript # Modify Properties In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify block properties through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## UUID A universally unique identifier (UUID) is assigned to each block upon creation and can be queried. This is stable across save & load and may be used to reference blocks. ``` getUUID(id: DesignBlockId): string ``` Get a block's UUID. * `id`: The block to query. ``` const uuid = engine.block.getUUID(block); ``` ## Reflection For every block, you can get a list of all its properties by calling `findAllProperties(id: number): string[]`. Properties specific to a block are prefixed with the block's type followed by a forward slash. There are also common properties shared between blocks which are prefixed by their respective type. A list of all properties can be found in the [Blocks Overview](/docs/cesdk/engine/api/block/). ``` const propertyNamesStar = engine.block.findAllProperties(starShape); // Array [ "shape/star/innerDiameter", "shape/star/points", "opacity/value", ... ] const propertyNamesImage = engine.block.findAllProperties(imageFill); // Array [ "fill/image/imageFileURI", "fill/image/previewFileURI", "fill/image/externalReference", ... ] const propertyNamesText = engine.block.findAllProperties(text); // Array [ "text/text", "text/fontFileUri", "text/externalReference", "text/fontSize", "text/horizontalAlignment", ... ] ``` ``` findAllProperties(id: DesignBlockId): string[] ``` Get all available properties of a block. * `id`: The block whose properties should be queried. * Returns A list of the property names. ``` const propertyNamesStar = engine.block.findAllProperties(starShape); // Array [ "shape/star/innerDiameter", "shape/star/points", "opacity/value", ... ] const propertyNamesImage = engine.block.findAllProperties(imageFill); // Array [ "fill/image/imageFileURI", "fill/image/previewFileURI", "fill/image/externalReference", ... ] const propertyNamesText = engine.block.findAllProperties(text); // Array [ "text/text", "text/fontFileUri", "text/externalReference", "text/fontSize", "text/horizontalAlignment", ... ] ``` Given a property, you can query its type as an enum using `getPropertyType(property: string): 'Bool' | 'Int' | 'Float' | 'String' | 'Color' | 'Enum' | 'Struct'`. ``` const pointsType = engine.block.getPropertyType('shape/star/points'); // "Int" ``` ``` getPropertyType(property: string): PropertyType ``` Get the type of a property given its name. * `property`: The name of the property whose type should be queried. * Returns The property type. ``` const pointsType = engine.block.getPropertyType('shape/star/points'); // "Int" ``` The property type `'Enum'` is a special type. Properties of this type only accept a set of certain strings. To get a list of possible values for an enum property call `getEnumValues(enumProperty: string): string[]`. ``` const alignmentType = engine.block.getPropertyType('text/horizontalAlignment'); // "Enum" ``` ``` getEnumValues(enumProperty: string): T[] ``` Get all the possible values of an enum given an enum property. * `enumProperty`: The name of the property whose enum values should be queried. * Returns A list of the enum value names as string. ``` const alignmentType = engine.block.getPropertyType('text/horizontalAlignment'); // "Enum" ``` Some properties can only be written to or only be read. To find out what is possible with a property, you can use the `isPropertyReadable` and `isPropertyWritable` methods. ``` const readable = engine.block.isPropertyReadable('shape/star/points'); const writable = engine.block.isPropertyWritable('shape/star/points'); ``` ``` isPropertyReadable(property: string): boolean ``` Check if a property with a given name is readable * `property`: The name of the property whose type should be queried. * Returns Whether the property is readable or not. Will return false for unknown properties ``` const readable = engine.block.isPropertyReadable('shape/star/points'); ``` ``` isPropertyWritable(property: string): boolean ``` Check if a property with a given name is writable * `property`: The name of the property whose type should be queried. * Returns Whether the property is writable or not. Will return false for unknown properties ``` const writable = engine.block.isPropertyWritable('shape/star/points'); ``` ## Generic Properties There are dedicated setter and getter functions for each property type. You have to provide a block and the property path. Use `findAllProperties` to get a list of all the available properties a block has. Please make sure you call the setter and getter function matching the type of the property you want to set or query or else you will get an error. Use \`getPropertyType\` to figure out the pair of functions you need to use. ``` findAllProperties(id: DesignBlockId): string[] ``` Get all available properties of a block. * `id`: The block whose properties should be queried. * Returns A list of the property names. ``` const propertyNamesStar = engine.block.findAllProperties(starShape); // Array [ "shape/star/innerDiameter", "shape/star/points", "opacity/value", ... ] const propertyNamesImage = engine.block.findAllProperties(imageFill); // Array [ "fill/image/imageFileURI", "fill/image/previewFileURI", "fill/image/externalReference", ... ] const propertyNamesText = engine.block.findAllProperties(text); // Array [ "text/text", "text/fontFileUri", "text/externalReference", "text/fontSize", "text/horizontalAlignment", ... ] ``` ``` setBool(id: DesignBlockId, property: string, value: boolean): void ``` Set a bool property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` engine.block.setBool(scene, 'scene/aspectRatioLock', false); ``` ``` getBool(id: DesignBlockId, property: string): boolean ``` Get the value of a bool property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns The value of the property. ``` engine.block.getBool(scene, 'scene/aspectRatioLock'); ``` ``` setInt(id: DesignBlockId, property: string, value: number): void ``` Set an int property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` engine.block.setInt(starShape, 'shape/star/points', points + 2); ``` ``` getInt(id: DesignBlockId, property: string): number ``` Get the value of an int property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns The value of the property. ``` const points = engine.block.getInt(starShape, 'shape/star/points'); ``` ``` setFloat(id: DesignBlockId, property: string, value: number): void ``` Set a float property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` engine.block.setFloat(starShape, 'shape/star/innerDiameter', 0.75); ``` ``` getFloat(id: DesignBlockId, property: string): number ``` Get the value of a float property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns The value of the property. ``` engine.block.getFloat(starShape, 'shape/star/innerDiameter'); ``` ``` setDouble(id: DesignBlockId, property: string, value: number): void ``` Set a double property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` const audio = engine.block.create('audio'); engine.block.appendChild(scene, audio); engine.block.setDouble(audio, 'playback/duration', 1.0); ``` ``` getDouble(id: DesignBlockId, property: string): number ``` Get the value of a double property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns The value of the property. ``` engine.block.getDouble(audio, 'playback/duration'); ``` ``` setString(id: DesignBlockId, property: string, value: string): void ``` Set a string property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` engine.block.setString(text, 'text/text', '*o*'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_4.jpg' ); ``` ``` getString(id: DesignBlockId, property: string): string ``` Get the value of a string property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns The value of the property. ``` engine.block.getString(text, 'text/text'); engine.block.getString(imageFill, 'fill/image/imageFileURI'); ``` ``` setColor(id: DesignBlockId, property: string, value: Color): void ``` Set a color property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` engine.block.setColor(colorFill, 'fill/color/value', { ``` ``` getColor(id: DesignBlockId, property: string): Color ``` Get the value of a color property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns The value of the property. ``` engine.block.getColor(colorFill, 'fill/color/value'); ``` ``` setEnum(id: DesignBlockId, property: string, value: T): void ``` Set an enum property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The enum value as string. ``` engine.block.setEnum(text, 'text/horizontalAlignment', 'Center'); engine.block.setEnum(text, 'text/verticalAlignment', 'Center'); ``` ``` getEnum(id: DesignBlockId, property: string): T ``` Get the value of an enum property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns The value as string. ``` engine.block.getEnum(text, 'text/horizontalAlignment'); engine.block.getEnum(text, 'text/verticalAlignment'); ``` ``` type HorizontalTextAlignment = 'Left' | 'Right' | 'Center' ``` The horizontal text alignment options. ``` const alignmentType = engine.block.getPropertyType('text/horizontalAlignment'); // "Enum" ``` ``` type VerticalTextAlignment = 'Top' | 'Bottom' | 'Center' ``` The vertical text alignment options. ``` engine.block.setEnum(text, 'text/verticalAlignment', 'Center'); ``` ``` setGradientColorStops(id: DesignBlockId, property: string, colors: GradientColorStop[]): void ``` Set a gradient color stops property of the given design block. * `id`: The block whose property should be set. * `property`: The name of the property to set. ``` engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.8, b: 0.2, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.4, b: 0.7, a: 1.0 }, stop: 1 } ]); ``` ``` getGradientColorStops(id: DesignBlockId, property: string): GradientColorStop[] ``` Get the gradient color stops property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns The gradient colors. ``` engine.block.getGradientColorStops(gradientFill, 'fill/gradient/colors'); ``` ``` setSourceSet(id: DesignBlockId, property: string, sourceSet: Source[]): void ``` Set the property of the given block. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `sourceSet`: The block's new source set. ``` const imageFill = engine.block.createFill('image'); engine.block.setSourceSet(imageFill, 'fill/image/sourceSet', [ { uri: 'http://img.ly/my-image.png', width: 800, height: 600 } ]); ``` ``` getSourceSet(id: DesignBlockId, property: string): Source[] ``` Get the source set value of the given property. * `id`: The block that should be queried. * `property`: The name of the property to query. * Returns The block's source set. ``` const sourceSet = engine.block.getSourceSet(imageFill, 'fill/image/sourceSet'); ``` ``` addImageFileURIToSourceSet(id: DesignBlockId, property: string, uri: string): Promise ``` Add a source to the `sourceSet` property of the given block. If there already exists in source set an image with the same width, that existing image will be replaced. * `id`: The block to update. * `property`: The name of the property to modify. * `uri`: The source to add to the source set. ``` await engine.block.addImageFileURIToSourceSet(imageFill, 'fill/image/sourceSet', 'https://img.ly/static/ubq_samples/sample_1.jpg'); ``` ``` addVideoFileURIToSourceSet(id: DesignBlockId, property: string, uri: string): Promise ``` Add a video file URI to the `sourceSet` property of the given block. If there already exists in source set an video with the same width, that existing video will be replaced. * `id`: The block to update. * `property`: The name of the property to modify. * `uri`: The source to add to the source set. ``` const videoFill = engine.block.createFill('video'); await engine.block.addVideoFileURIToSourceSet(videoFill, 'fill/video/sourceSet', 'https://img.ly/static/example-assets/sourceset/1x.mp4'); ``` Adding Icons - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/api/icons/#built-in-icons CESDK/CE.SDK/Web Editor/Customization/API Reference # Adding Icons Explore how to add new icons in the editor. Using our API, you can register your own icon sets for usage within the CE.SDK editor. The API is as follows: ## Add new Icon Set ``` cesdk.ui.addIconSet(id: string, svgSprite: string) ``` The icon set is a string containing a SVG sprite with symbols. The id of each symbol is used to reference the icon in the editor. These ids need to start with a `@` to be recognized in the editor. Please Note: The SVG sprite will be injected into the (shadow) DOM without any sanitization. Make sure to only use trusted sources to prevent e.g. XSS attacks. If you are unsure about the source of the sprite, consider using libraries like DOMPurify to sanitize the SVG string before adding it. ## Examples ### Register an Icon Set Registering an SVG string with a single symbol might look like this: ``` cesdk.ui.addIconSet( '@imgly/custom', ` ` ); ``` ### Replace a Dock Icon You could use your custom icon set to replace the "Image" icon of the default Dock: ``` cesdk.ui.setDockOrder( cesdk.ui.getDockOrder().map((entry) => { if (entry.key === 'ly.img.image') return { ...entry, icon: '@some/icon/ZoomIn' // Note the ID referencing the SVG symbol }; else return entry; }) ); ``` ### Use within a custom Component Using your new symbol in a [custom component](/docs/cesdk/ui/customization/api/registerComponents/) is then done by referencing the symbol ID directly: ``` cesdk.ui.registerComponent('CustomIcon', ({ builder: { Button } }) => { Button('customIcon', { label: 'Custom Icon', icon: '@some/icon/ZoomIn', // Note the ID referencing the SVG symbol onClick: () => { console.log('Custom Icon clicked'); } }); }); ``` ## Built-In Icons These built-in icons can be referenced by their name string directly: ### Set 'Essentials' Name Icon Name `@imgly/Adjustments` Icon ![Adjustments Icon](/docs/cesdk/56ada47019272bb8f27a4e7dbf2aebf9/Adjustments.svg) Name `@imgly/Animation` Icon ![Animation Icon](/docs/cesdk/73c1018654fe14d85765eb4abbf0daac/Animation.svg) Name `@imgly/Appearance` Icon ![Appearance Icon](/docs/cesdk/de87c6cc400d59937294c4cf1ba41ba5/Appearance.svg) Name `@imgly/Apps` Icon ![Apps Icon](/docs/cesdk/771565116629588cdea9bac4dea3ad87/Apps.svg) Name `@imgly/ArrowDown` Icon ![ArrowDown Icon](/docs/cesdk/5aa71f383e22d5b34bc5717ee1034ecf/ArrowDown.svg) Name `@imgly/ArrowLeft` Icon ![ArrowLeft Icon](/docs/cesdk/ca4d98209815796d5484e4290c0f1181/ArrowLeft.svg) Name `@imgly/ArrowRight` Icon ![ArrowRight Icon](/docs/cesdk/ff46927c2e996c9e189f69fb10df98f1/ArrowRight.svg) Name `@imgly/ArrowUp` Icon ![ArrowUp Icon](/docs/cesdk/9914873bd4b9504a12d83e4d03952801/ArrowUp.svg) Name `@imgly/ArrowsDirectionHorizontal` Icon ![ArrowsDirectionHorizontal Icon](/docs/cesdk/8fd04162da389e0cb5555fdebc44525f/ArrowsDirectionHorizontal.svg) Name `@imgly/ArrowsDirectionVertical` Icon ![ArrowsDirectionVertical Icon](/docs/cesdk/e94817a34b159029a69a5826c31526d2/ArrowsDirectionVertical.svg) Name `@imgly/AsClip` Icon ![AsClip Icon](/docs/cesdk/d03c989c47ed318606f4dac1756c4243/AsClip.svg) Name `@imgly/AsOverlay` Icon ![AsOverlay Icon](/docs/cesdk/1e20d9b448341d5b53e0da251c5d54f1/AsOverlay.svg) Name `@imgly/Audio` Icon ![Audio Icon](/docs/cesdk/5488c67c8c463640641914493db0dde8/Audio.svg) Name `@imgly/AudioAdd` Icon ![AudioAdd Icon](/docs/cesdk/f45a58abf796ae80b3867f68cffd2c50/AudioAdd.svg) Name `@imgly/Backward` Icon ![Backward Icon](/docs/cesdk/c040dad6dee0a38dd9d0166071131ef9/Backward.svg) Name `@imgly/Blendmode` Icon ![Blendmode Icon](/docs/cesdk/bc0e6f32d7347dbdb3756100bd9ea34a/Blendmode.svg) Name `@imgly/Blur` Icon ![Blur Icon](/docs/cesdk/3cfaffe48b02e0388f6f49798c70be34/Blur.svg) Name `@imgly/BooleanExclude` Icon ![BooleanExclude Icon](/docs/cesdk/9331c9ad7f3675540ad7e0c072b160a2/BooleanExclude.svg) Name `@imgly/BooleanIntersect` Icon ![BooleanIntersect Icon](/docs/cesdk/a30cfc931ad273697927075a46101332/BooleanIntersect.svg) Name `@imgly/BooleanSubstract` Icon ![BooleanSubstract Icon](/docs/cesdk/3eb14de5db103f936d104b918391e01d/BooleanSubstract.svg) Name `@imgly/BooleanUnion` Icon ![BooleanUnion Icon](/docs/cesdk/1155ef38e20d0be8a0ccbbd079ec960c/BooleanUnion.svg) Name `@imgly/CaseAsTyped` Icon ![CaseAsTyped Icon](/docs/cesdk/1ebc90a33c80d8f02a80f564a1ea7fc8/CaseAsTyped.svg) Name `@imgly/CaseLowercase` Icon ![CaseLowercase Icon](/docs/cesdk/0b28d5b5be6792257fee4f133a77a4e5/CaseLowercase.svg) Name `@imgly/CaseSmallCaps` Icon ![CaseSmallCaps Icon](/docs/cesdk/180f71f918abf122855f176f14c43f4b/CaseSmallCaps.svg) Name `@imgly/CaseTitleCase` Icon ![CaseTitleCase Icon](/docs/cesdk/3cbae4b74f9ca73575b9f48c22c94f85/CaseTitleCase.svg) Name `@imgly/CaseUppercase` Icon ![CaseUppercase Icon](/docs/cesdk/98e338e1a28f694d11402ccc08f2fd1f/CaseUppercase.svg) Name `@imgly/CheckboxCheckmark` Icon ![CheckboxCheckmark Icon](/docs/cesdk/48a06db234b6ab2ef0d8a7ef0aa64e97/CheckboxCheckmark.svg) Name `@imgly/CheckboxMixed` Icon ![CheckboxMixed Icon](/docs/cesdk/19d549035d66af830b714d563617851d/CheckboxMixed.svg) Name `@imgly/Checkmark` Icon ![Checkmark Icon](/docs/cesdk/a8b10f0eefef3f42c306800d82e27092/Checkmark.svg) Name `@imgly/ChevronDown` Icon ![ChevronDown Icon](/docs/cesdk/8dc875a72a8498738c9025694ccac518/ChevronDown.svg) Name `@imgly/ChevronLeft` Icon ![ChevronLeft Icon](/docs/cesdk/6bb2a91898cde0378258ec9da323e20c/ChevronLeft.svg) Name `@imgly/ChevronRight` Icon ![ChevronRight Icon](/docs/cesdk/10bec69db985bb1ffc34a06b033ebda1/ChevronRight.svg) Name `@imgly/ChevronUp` Icon ![ChevronUp Icon](/docs/cesdk/3fa8372e7d0b7c9eb4bbb563ac73e5e2/ChevronUp.svg) Name `@imgly/Collage` Icon ![Collage Icon](/docs/cesdk/f5f9742f2558abcf56200d29527e218d/Collage.svg) Name `@imgly/ColorFill` Icon ![ColorFill Icon](/docs/cesdk/ec424fe365d00154e0244d22d786fe89/ColorFill.svg) Name `@imgly/ColorGradientAngular` Icon ![ColorGradientAngular Icon](/docs/cesdk/9c7c9aea71b0405551957b12db64c178/ColorGradientAngular.svg) Name `@imgly/ColorGradientLinear` Icon ![ColorGradientLinear Icon](/docs/cesdk/6cefdd4c27b8a093d50556f61efd92f2/ColorGradientLinear.svg) Name `@imgly/ColorGradientRadial` Icon ![ColorGradientRadial Icon](/docs/cesdk/16c45d0958c85d2f3ae3fbb1c8ef79b5/ColorGradientRadial.svg) Name `@imgly/ColorOpacity` Icon ![ColorOpacity Icon](/docs/cesdk/3c2af43fe56d956babecfe700851815b/ColorOpacity.svg) Name `@imgly/ColorSolid` Icon ![ColorSolid Icon](/docs/cesdk/6defbeedcc674f3fc6f52a7e53177b54/ColorSolid.svg) Name `@imgly/ConnectionLostSlash` Icon ![ConnectionLostSlash Icon](/docs/cesdk/4031f9a8748c6669c5ac22eeda5e4237/ConnectionLostSlash.svg) Name `@imgly/Copy` Icon ![Copy Icon](/docs/cesdk/a48353610412e083ca2a8640ee400695/Copy.svg) Name `@imgly/CornerJoinBevel` Icon ![CornerJoinBevel Icon](/docs/cesdk/cfe23ec5c56d95ca1d3cd99af4a0d6b6/CornerJoinBevel.svg) Name `@imgly/CornerJoinMiter` Icon ![CornerJoinMiter Icon](/docs/cesdk/70595b1619a6f710bded77642afff5a6/CornerJoinMiter.svg) Name `@imgly/CornerJoinRound` Icon ![CornerJoinRound Icon](/docs/cesdk/a2889927a5fc558131dacd168cbce2fd/CornerJoinRound.svg) Name `@imgly/Crop` Icon ![Crop Icon](/docs/cesdk/0c23c662700ef37118bfc705b646deec/Crop.svg) Name `@imgly/CropCoverMode` Icon ![CropCoverMode Icon](/docs/cesdk/1cb35c6fabfdad2735e98091f72a1dfd/CropCoverMode.svg) Name `@imgly/CropCropMode` Icon ![CropCropMode Icon](/docs/cesdk/65a52dfe458ee9e7c32e3a8c2555ff4f/CropCropMode.svg) Name `@imgly/CropFitMode` Icon ![CropFitMode Icon](/docs/cesdk/e5576847199a178ae5c7b3b2ff472755/CropFitMode.svg) Name `@imgly/CropFreefromMode` Icon ![CropFreefromMode Icon](/docs/cesdk/cefe661efba03df6b2562b8a593f1463/CropFreefromMode.svg) Name `@imgly/Cross` Icon ![Cross Icon](/docs/cesdk/710e673b463a7353d56da53d2c896960/Cross.svg) Name `@imgly/CrossRoundSolid` Icon ![CrossRoundSolid Icon](/docs/cesdk/f144acf2c523682ffc9c988881850648/CrossRoundSolid.svg) Name `@imgly/CustomLibrary` Icon ![CustomLibrary Icon](/docs/cesdk/0ac8fcddef976ff350434a292c46388d/CustomLibrary.svg) Name `@imgly/Download` Icon ![Download Icon](/docs/cesdk/326e5179fe1288ed47827a4e57466549/Download.svg) Name `@imgly/DropShadow` Icon ![DropShadow Icon](/docs/cesdk/c615253579c476a59a0a6d506f7cb93a/DropShadow.svg) Name `@imgly/Duplicate` Icon ![Duplicate Icon](/docs/cesdk/f34d88a5874ce2b83ce5dcc67e62d275/Duplicate.svg) Name `@imgly/Edit` Icon ![Edit Icon](/docs/cesdk/e82a1895d1d5e351fb0d75f410f5b1ec/Edit.svg) Name `@imgly/Effects` Icon ![Effects Icon](/docs/cesdk/f2a12726b71010b9ca3428905dd4bc10/Effects.svg) Name `@imgly/ExternalLink` Icon ![ExternalLink Icon](/docs/cesdk/e294af1b9ef26f5a879089fa11758038/ExternalLink.svg) Name `@imgly/EyeClosed` Icon ![EyeClosed Icon](/docs/cesdk/51480ed6a0845bdbd6a3456a57da0c50/EyeClosed.svg) Name `@imgly/EyeOpen` Icon ![EyeOpen Icon](/docs/cesdk/78767c0bb579ef38a60e086148156f18/EyeOpen.svg) Name `@imgly/Filter` Icon ![Filter Icon](/docs/cesdk/3f03471ad66c6db2146eec7e6c2c5aa0/Filter.svg) Name `@imgly/FlipHorizontal` Icon ![FlipHorizontal Icon](/docs/cesdk/048345ba9855c65570cc2709e12c4b32/FlipHorizontal.svg) Name `@imgly/FlipVertical` Icon ![FlipVertical Icon](/docs/cesdk/e07d0b3084d20af1bdaf54a12a8b3c6f/FlipVertical.svg) Name `@imgly/FontAutoSizing` Icon ![FontAutoSizing Icon](/docs/cesdk/5c0ebc94f1595776d18fb0fd627ec3d8/FontAutoSizing.svg) Name `@imgly/FontSize` Icon ![FontSize Icon](/docs/cesdk/ee8f0b53b09f0cf910e560e0c1dc06d5/FontSize.svg) Name `@imgly/Forward` Icon ![Forward Icon](/docs/cesdk/1876ff873a55ff933056bef7bec6c675/Forward.svg) Name `@imgly/FullscreenEnter` Icon ![FullscreenEnter Icon](/docs/cesdk/849bfdee420a6534779c112e7db40555/FullscreenEnter.svg) Name `@imgly/FullscreenLeave` Icon ![FullscreenLeave Icon](/docs/cesdk/e89b77b58a58a4e9f805ee9fd6cc11a2/FullscreenLeave.svg) Name `@imgly/Group` Icon ![Group Icon](/docs/cesdk/5c7b8b3f4eab3e02cf449653cc98cfb4/Group.svg) Name `@imgly/GroupEnter` Icon ![GroupEnter Icon](/docs/cesdk/3ac4b01cd4109a53ed8da4d8f68d9382/GroupEnter.svg) Name `@imgly/GroupExit` Icon ![GroupExit Icon](/docs/cesdk/6429468bb7a80020ce3f5f5d62a98000/GroupExit.svg) Name `@imgly/Home` Icon ![Home Icon](/docs/cesdk/ea51523f0d899662e6c92c68c766310e/Home.svg) Name `@imgly/Image` Icon ![Image Icon](/docs/cesdk/a40c3978eac528c882563c971f0d7036/Image.svg) Name `@imgly/Info` Icon ![Info Icon](/docs/cesdk/5b5e0cd6e26224f999e03cf90e4f4932/Info.svg) Name `@imgly/LayerBringForward` Icon ![LayerBringForward Icon](/docs/cesdk/615435d8c3431e627c77a2cf88142f5d/LayerBringForward.svg) Name `@imgly/LayerBringForwardArrow` Icon ![LayerBringForwardArrow Icon](/docs/cesdk/c127fa5c9a07416951c79e4423c13471/LayerBringForwardArrow.svg) Name `@imgly/LayerBringToFront` Icon ![LayerBringToFront Icon](/docs/cesdk/7ace0d7138ab3d2b8d01cea7cff4fe87/LayerBringToFront.svg) Name `@imgly/LayerBringToFrontArrow` Icon ![LayerBringToFrontArrow Icon](/docs/cesdk/6f03d5477f2e086604f44603de245a54/LayerBringToFrontArrow.svg) Name `@imgly/LayerSendBackward` Icon ![LayerSendBackward Icon](/docs/cesdk/3dc84ad74e6e61ce49e9e2b0977756c0/LayerSendBackward.svg) Name `@imgly/LayerSendBackwardArrow` Icon ![LayerSendBackwardArrow Icon](/docs/cesdk/8a3218e3e7802cee19cab013ea9932c6/LayerSendBackwardArrow.svg) Name `@imgly/LayerSendToBack` Icon ![LayerSendToBack Icon](/docs/cesdk/12e29c84e23f008678a9626db0fdf4c7/LayerSendToBack.svg) Name `@imgly/LayerSendToBackArrow` Icon ![LayerSendToBackArrow Icon](/docs/cesdk/0340505cea7701c7514a65253af600f9/LayerSendToBackArrow.svg) Name `@imgly/LayoutHorizontal` Icon ![LayoutHorizontal Icon](/docs/cesdk/34736d85c7948361aa7408301f2446ad/LayoutHorizontal.svg) Name `@imgly/LayoutVertical` Icon ![LayoutVertical Icon](/docs/cesdk/c90f589193069ded3a7611c143a932df/LayoutVertical.svg) Name `@imgly/Library` Icon ![Library Icon](/docs/cesdk/d0f8c3d2f4ff093d7f60d7f71bcfcde5/Library.svg) Name `@imgly/LineHeight` Icon ![LineHeight Icon](/docs/cesdk/11733fa4287c499223153a4f17509fe2/LineHeight.svg) Name `@imgly/LinkClosed` Icon ![LinkClosed Icon](/docs/cesdk/b6402642384cc826632e443db4b97524/LinkClosed.svg) Name `@imgly/LinkOpen` Icon ![LinkOpen Icon](/docs/cesdk/bfd98a05400e7e5cd4292e81a20a02cb/LinkOpen.svg) Name `@imgly/LoadingSpinner` Icon ![LoadingSpinner Icon](/docs/cesdk/711217d3e50fd7b26c019ad062b33890/LoadingSpinner.svg) Name `@imgly/LockClosed` Icon ![LockClosed Icon](/docs/cesdk/284b2369fed4509c916195b83800bf6f/LockClosed.svg) Name `@imgly/LockOpen` Icon ![LockOpen Icon](/docs/cesdk/1bf76d5419134c307a1df7233e62fa40/LockOpen.svg) Name `@imgly/Minus` Icon ![Minus Icon](/docs/cesdk/827b4b2eb28a633703bda8c61ec6057d/Minus.svg) Name `@imgly/MoreOptionsHorizontal` Icon ![MoreOptionsHorizontal Icon](/docs/cesdk/5018d1fb046d285a16073abf8d324306/MoreOptionsHorizontal.svg) Name `@imgly/MoreOptionsVertical` Icon ![MoreOptionsVertical Icon](/docs/cesdk/de7b65a8eadcf8086e4c8f067a26e13e/MoreOptionsVertical.svg) Name `@imgly/Move` Icon ![Move Icon](/docs/cesdk/b98202354d36e2736f53473c5ac446b4/Move.svg) Name `@imgly/Next` Icon ![Next Icon](/docs/cesdk/c0ce3a5585e29f632170634a6ad49d36/Next.svg) Name `@imgly/None` Icon ![None Icon](/docs/cesdk/2314429abfd229a10cb214cb4ae789ea/None.svg) Name `@imgly/ObjectAlignBottom` Icon ![ObjectAlignBottom Icon](/docs/cesdk/a4b67a1b9fb205b2591f729a7eeb2e68/ObjectAlignBottom.svg) Name `@imgly/ObjectAlignDistributedHorizontal` Icon ![ObjectAlignDistributedHorizontal Icon](/docs/cesdk/f3c47ea07bd613dbb80b428d8e8332ed/ObjectAlignDistributedHorizontal.svg) Name `@imgly/ObjectAlignDistributedVertical` Icon ![ObjectAlignDistributedVertical Icon](/docs/cesdk/1f2a650da70fe98a935ec97d9c3d924b/ObjectAlignDistributedVertical.svg) Name `@imgly/ObjectAlignHorizontalCenter` Icon ![ObjectAlignHorizontalCenter Icon](/docs/cesdk/4a22a1e8891cc1a43ab9d7a60690f778/ObjectAlignHorizontalCenter.svg) Name `@imgly/ObjectAlignLeft` Icon ![ObjectAlignLeft Icon](/docs/cesdk/158ccded08c5625b0fcaf9bb7bdd14de/ObjectAlignLeft.svg) Name `@imgly/ObjectAlignRight` Icon ![ObjectAlignRight Icon](/docs/cesdk/28a9aca9c5120c3f6b528ee01b81cc20/ObjectAlignRight.svg) Name `@imgly/ObjectAlignTop` Icon ![ObjectAlignTop Icon](/docs/cesdk/e0a03b2b03c167865ad8fbab4db26cde/ObjectAlignTop.svg) Name `@imgly/ObjectAlignVerticalCenter` Icon ![ObjectAlignVerticalCenter Icon](/docs/cesdk/58a198a05310c3d30cf42e85188cf7a3/ObjectAlignVerticalCenter.svg) Name `@imgly/OrientationToggleLandscape` Icon ![OrientationToggleLandscape Icon](/docs/cesdk/6afc443405070c68c5b8435cd61450cd/OrientationToggleLandscape.svg) Name `@imgly/OrientationTogglePortrait` Icon ![OrientationTogglePortrait Icon](/docs/cesdk/8239bec599bf013c4b1cd64e17d1e620/OrientationTogglePortrait.svg) Name `@imgly/PageResize` Icon ![PageResize Icon](/docs/cesdk/2d51ab0ff8c5b855ca7ea54703939917/PageResize.svg) Name `@imgly/Paste` Icon ![Paste Icon](/docs/cesdk/810bb57d23de9b50f4e23f8d2b6e3304/Paste.svg) Name `@imgly/Pause` Icon ![Pause Icon](/docs/cesdk/6aedf1081bac9509bc1e309852945c65/Pause.svg) Name `@imgly/PlaceholderConnected` Icon ![PlaceholderConnected Icon](/docs/cesdk/a261437adde5f3537bdc449ecd244f86/PlaceholderConnected.svg) Name `@imgly/PlaceholderStripes` Icon ![PlaceholderStripes Icon](/docs/cesdk/26658ccff77c837b33a5dbcf55d9d9e8/PlaceholderStripes.svg) Name `@imgly/Play` Icon ![Play Icon](/docs/cesdk/35ebeac2d1fff31f2975f693aefdd3b3/Play.svg) Name `@imgly/Plus` Icon ![Plus Icon](/docs/cesdk/3d68c8cfde29fea1a08576deb9c43814/Plus.svg) Name `@imgly/Position` Icon ![Position Icon](/docs/cesdk/a9386ad114cddf2ccc5b9e06430f5d2c/Position.svg) Name `@imgly/Previous` Icon ![Previous Icon](/docs/cesdk/c81907e9ed460d0d92bb5417ac1c15a7/Previous.svg) Name `@imgly/Redo` Icon ![Redo Icon](/docs/cesdk/809f76dd28486eba1b44d3fd68a51eac/Redo.svg) Name `@imgly/Rename` Icon ![Rename Icon](/docs/cesdk/c41c46eab1504419dd1656b76f62e8f4/Rename.svg) Name `@imgly/Reorder` Icon ![Reorder Icon](/docs/cesdk/15d46284c9c78d9da02e86698263bfef/Reorder.svg) Name `@imgly/Repeat` Icon ![Repeat Icon](/docs/cesdk/dde65c8a6f31aa49d13fb211b481219d/Repeat.svg) Name `@imgly/RepeatOff` Icon ![RepeatOff Icon](/docs/cesdk/556e81e9c880a26b0d7988d8f9baa9e3/RepeatOff.svg) Name `@imgly/Replace` Icon ![Replace Icon](/docs/cesdk/09d1a19f3a74dace9ffd07fdcfd6984f/Replace.svg) Name `@imgly/Reset` Icon ![Reset Icon](/docs/cesdk/1ea425ac6e01fc8a674ea6f8019f8df6/Reset.svg) Name `@imgly/RotateCCW90` Icon ![RotateCCW90 Icon](/docs/cesdk/577cc1c4a746046c6c3f8433ae76e06c/RotateCCW90.svg) Name `@imgly/RotateCW` Icon ![RotateCW Icon](/docs/cesdk/e3b723e3ff060ddbcca1918caad8d824/RotateCW.svg) Name `@imgly/RotateCW90` Icon ![RotateCW90 Icon](/docs/cesdk/4a3204cb5c7e519f609cf2e27442a402/RotateCW90.svg) Name `@imgly/Save` Icon ![Save Icon](/docs/cesdk/acf2ff03cfd4fdbdd32a418b75dd031e/Save.svg) Name `@imgly/Scale` Icon ![Scale Icon](/docs/cesdk/6587b6685b3953c550fab8acc239e89f/Scale.svg) Name `@imgly/Search` Icon ![Search Icon](/docs/cesdk/e777b424b19b35e9cceceb79fd60e6eb/Search.svg) Name `@imgly/Settings` Icon ![Settings Icon](/docs/cesdk/9762bedafeaea406728155fdbce3ebfc/Settings.svg) Name `@imgly/SettingsCog` Icon ![SettingsCog Icon](/docs/cesdk/7c5e25f0304fa0f8db4d2a38e51dd451/SettingsCog.svg) Name `@imgly/ShapeOval` Icon ![ShapeOval Icon](/docs/cesdk/0907f621d5c17efd14d40958b96b6888/ShapeOval.svg) Name `@imgly/ShapePolygon` Icon ![ShapePolygon Icon](/docs/cesdk/21ad37e886fa4d0296c4d11abfaca83a/ShapePolygon.svg) Name `@imgly/ShapeRectangle` Icon ![ShapeRectangle Icon](/docs/cesdk/bac21e838c22bff2e5915203560d571c/ShapeRectangle.svg) Name `@imgly/ShapeStar` Icon ![ShapeStar Icon](/docs/cesdk/70e014b3259fa578f6f0ebcbd9bee012/ShapeStar.svg) Name `@imgly/Shapes` Icon ![Shapes Icon](/docs/cesdk/878de250332b50795ce822d6f2b1cb68/Shapes.svg) Name `@imgly/Share` Icon ![Share Icon](/docs/cesdk/599ad4b21024216f3941bed7e32e5853/Share.svg) Name `@imgly/SidebarOpen` Icon ![SidebarOpen Icon](/docs/cesdk/18a599ad34638d0212e6fb55f6d94420/SidebarOpen.svg) Name `@imgly/Split` Icon ![Split Icon](/docs/cesdk/1884914a530b24423249535b23b2e5ef/Split.svg) Name `@imgly/Sticker` Icon ![Sticker Icon](/docs/cesdk/86d31abc1ed05b0f1713f4061fcd51a8/Sticker.svg) Name `@imgly/Stop` Icon ![Stop Icon](/docs/cesdk/1ebb261f90191b5f8675e30da9fb58c2/Stop.svg) Name `@imgly/Straighten` Icon ![Straighten Icon](/docs/cesdk/d1a492fdb20709ec8233492356d442e6/Straighten.svg) Name `@imgly/StrokeDash` Icon ![StrokeDash Icon](/docs/cesdk/5bc0a37d2ba8599f2d21cf3046e3a47f/StrokeDash.svg) Name `@imgly/StrokeDotted` Icon ![StrokeDotted Icon](/docs/cesdk/6d6ea1f506703f60f22112ab9a34fbe2/StrokeDotted.svg) Name `@imgly/StrokePositionCenter` Icon ![StrokePositionCenter Icon](/docs/cesdk/96f5899eea33a63bf6ee7c0025ec4aea/StrokePositionCenter.svg) Name `@imgly/StrokePositionInside` Icon ![StrokePositionInside Icon](/docs/cesdk/3e810b13046c6f658e4fc8539af0a35d/StrokePositionInside.svg) Name `@imgly/StrokePositionOutside` Icon ![StrokePositionOutside Icon](/docs/cesdk/e86ae57bf904cc7c4099760102c2c403/StrokePositionOutside.svg) Name `@imgly/StrokeSolid` Icon ![StrokeSolid Icon](/docs/cesdk/c3143852f844d45f0b9eebd96d0e4491/StrokeSolid.svg) Name `@imgly/StrokeWeight` Icon ![StrokeWeight Icon](/docs/cesdk/ce2fe200f7a69aef56c687189a2d8f54/StrokeWeight.svg) Name `@imgly/Template` Icon ![Template Icon](/docs/cesdk/a524a27276867fe34658817a241fc450/Template.svg) Name `@imgly/Text` Icon ![Text Icon](/docs/cesdk/589415ed5cf3a0ef8e8e05a00ea8d270/Text.svg) Name `@imgly/TextAlignBottom` Icon ![TextAlignBottom Icon](/docs/cesdk/4403c8bf1b2458317109a324ba694ae3/TextAlignBottom.svg) Name `@imgly/TextAlignCenter` Icon ![TextAlignCenter Icon](/docs/cesdk/b961e507529b77887adcaf929bf75071/TextAlignCenter.svg) Name `@imgly/TextAlignLeft` Icon ![TextAlignLeft Icon](/docs/cesdk/87cc7b4049b5f0f5a70f8d3d06dd93e0/TextAlignLeft.svg) Name `@imgly/TextAlignMiddle` Icon ![TextAlignMiddle Icon](/docs/cesdk/c05c45fce2fb7d600fa5d10e078ff1ce/TextAlignMiddle.svg) Name `@imgly/TextAlignRight` Icon ![TextAlignRight Icon](/docs/cesdk/47f30c501674003541e7bba73fdc0330/TextAlignRight.svg) Name `@imgly/TextAlignTop` Icon ![TextAlignTop Icon](/docs/cesdk/9b2dc11d5a861fa88f9abcbf60a0ddfb/TextAlignTop.svg) Name `@imgly/TextAutoHeight` Icon ![TextAutoHeight Icon](/docs/cesdk/d935e88da2318030d12edf23589107a2/TextAutoHeight.svg) Name `@imgly/TextAutoSize` Icon ![TextAutoSize Icon](/docs/cesdk/d9be0d931cee39ec59630cbed8b7474f/TextAutoSize.svg) Name `@imgly/TextBold` Icon ![TextBold Icon](/docs/cesdk/87ae982efc21898c278cf5a0864ee54e/TextBold.svg) Name `@imgly/TextFixedSize` Icon ![TextFixedSize Icon](/docs/cesdk/f6c22c4d11dba75c8510051c0ca68881/TextFixedSize.svg) Name `@imgly/TextItalic` Icon ![TextItalic Icon](/docs/cesdk/26c997d1adda93963da8616e98ccefe1/TextItalic.svg) Name `@imgly/Timeline` Icon ![Timeline Icon](/docs/cesdk/64696bbaa064d52218f2d7e8f9c4d3ab/Timeline.svg) Name `@imgly/ToggleIconOff` Icon ![ToggleIconOff Icon](/docs/cesdk/b7019683c4a422b13e50709f7d9685d9/ToggleIconOff.svg) Name `@imgly/ToggleIconOn` Icon ![ToggleIconOn Icon](/docs/cesdk/3976a3764d6dd71c3da7d5d4fe5273a8/ToggleIconOn.svg) Name `@imgly/TransformSection` Icon ![TransformSection Icon](/docs/cesdk/79cd3197650a29a796dbae53ae11419c/TransformSection.svg) Name `@imgly/TrashBin` Icon ![TrashBin Icon](/docs/cesdk/9975aba82d26dec3557e74c494c3d93f/TrashBin.svg) Name `@imgly/TriangleDown` Icon ![TriangleDown Icon](/docs/cesdk/d582d1e4894e70fc8206a32d61d53595/TriangleDown.svg) Name `@imgly/TriangleUp` Icon ![TriangleUp Icon](/docs/cesdk/18e2ded6176fe5a5cf48689c12da526e/TriangleUp.svg) Name `@imgly/TrimMedia` Icon ![TrimMedia Icon](/docs/cesdk/c39383ea72067ac0c70ec56136c0b72b/TrimMedia.svg) Name `@imgly/Typeface` Icon ![Typeface Icon](/docs/cesdk/75f838fdd0c7516f6f5431013927f7f9/Typeface.svg) Name `@imgly/Undo` Icon ![Undo Icon](/docs/cesdk/dae32068eb4be3921e669b1c51c2f805/Undo.svg) Name `@imgly/Ungroup` Icon ![Ungroup Icon](/docs/cesdk/4d6a393a6ed348fd39414ade5e5ac2b8/Ungroup.svg) Name `@imgly/Upload` Icon ![Upload Icon](/docs/cesdk/81affa8ca6943aa6549bb611aa0604bf/Upload.svg) Name `@imgly/Video` Icon ![Video Icon](/docs/cesdk/0e014641e16b0ec915b97d1f1bf159b1/Video.svg) Name `@imgly/VideoCamera` Icon ![VideoCamera Icon](/docs/cesdk/f6f4eea4512f4335bce717a5082b1694/VideoCamera.svg) Name `@imgly/Volume` Icon ![Volume Icon](/docs/cesdk/2144cd79cf60cc2badbb0e25e44eff37/Volume.svg) Name `@imgly/VolumeMute` Icon ![VolumeMute Icon](/docs/cesdk/c8c93d459f127875e120103b876dc561/VolumeMute.svg) Name `@imgly/ZoomIn` Icon ![ZoomIn Icon](/docs/cesdk/983c7af8366ab2c2774320f01ff47103/ZoomIn.svg) Name `@imgly/ZoomOut` Icon ![ZoomOut Icon](/docs/cesdk/34bd1e51a7a5c26fd98db42b85652d78/ZoomOut.svg) [ Previous Asset Library Entry ](/docs/cesdk/ui/customization/api/assetLibraryEntry/)[ Next Panel ](/docs/cesdk/ui/customization/api/panel/) Modify Crop - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-crop/?platform=web&language=javascript # Modify Crop In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a blocks crop through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Common Properties Common properties are properties that occur on multiple block types. For instance, fill color properties are available for all the shape blocks and the text block. That's why we built convenient setter and getter functions for these properties. So you don't have to use the generic setters and getters and don't have to provide a specific property path. There are also `has*` functions to query if a block supports a set of common properties. ### Crop Manipulate the cropping region of a block by setting its scale, rotation, and translation. ``` supportsCrop(id: DesignBlockId): boolean ``` Query if the given block has crop properties. * `id`: The block to query. * Returns true, if the block has crop properties. ``` engine.block.supportsCrop(image); ``` ``` setCropScaleX(id: DesignBlockId, scaleX: number): void ``` Set the crop scale in x direction of the given design block. Required scope: 'layer/crop' * `id`: The block whose crop should be set. * `scaleX`: The scale in x direction. ``` engine.block.setCropScaleX(image, 2.0); ``` ``` setCropScaleY(id: DesignBlockId, scaleY: number): void ``` Set the crop scale in y direction of the given design block. Required scope: 'layer/crop' * `id`: The block whose crop should be set. * `scaleY`: The scale in y direction. ``` engine.block.setCropScaleY(image, 1.5); ``` ``` setCropScaleRatio(id: DesignBlockId, scaleRatio: number): void ``` Set the crop scale ratio of the given design block. This will uniformly scale the content up or down. The center of the scale operation is the center of the crop frame. Required scope: 'layer/crop' * `id`: The block whose crop should be set. * `scaleRatio`: The crop scale ratio. ``` engine.block.setCropScaleRatio(image, 3.0); ``` ``` setCropRotation(id: DesignBlockId, rotation: number): void ``` Set the crop rotation of the given design block. Required scope: 'layer/crop' * `id`: The block whose crop should be set. * `rotation`: The rotation in radians. ``` engine.block.setCropRotation(image, Math.PI); ``` ``` setCropTranslationX(id: DesignBlockId, translationX: number): void ``` Set the crop translation in x direction of the given design block. Required scope: 'layer/crop' * `id`: The block whose crop should be set. * `translationX`: The translation in x direction. ``` engine.block.setCropTranslationX(image, -1.0); ``` ``` setCropTranslationY(id: DesignBlockId, translationY: number): void ``` Set the crop translation in y direction of the given design block. Required scope: 'layer/crop' * `id`: The block whose crop should be set. * `translationY`: The translation in y direction. ``` engine.block.setCropTranslationY(image, 1.0); ``` ``` adjustCropToFillFrame(id: DesignBlockId, minScaleRatio: number): number ``` Adjust the crop position/scale to at least fill the crop frame. * `id`: The block whose crop scale ratio should be queried. * `minScaleRatio`: The minimal crop scale ratio to go down to. ``` engine.block.adjustCropToFillFrame(image, 1.0); ``` ``` setContentFillMode(id: DesignBlockId, mode: ContentFillMode): void ``` Set a block's content fill mode. Required scope: 'layer/crop' * `id`: The block to update. * `mode`: The content fill mode mode: crop, cover or contain. ``` engine.block.setContentFillMode(image, 'Contain'); ``` ``` resetCrop(id: DesignBlockId): void ``` Resets the manually set crop of the given design block. The block's content fill mode is set to 'cover'. If the block has a fill, the crop values are updated so that it covers the block. Required scope: 'layer/crop' * `id`: The block whose crop should be reset. ``` engine.block.resetCrop(image); ``` ``` getCropScaleX(id: DesignBlockId): number ``` Get the crop scale on the x axis of the given design block. * `id`: The block whose scale should be queried. * Returns The scale on the x axis. ``` engine.block.getCropScaleX(image); ``` ``` getCropScaleY(id: DesignBlockId): number ``` Get the crop scale on the y axis of the given design block. * `id`: The block whose scale should be queried. * Returns The scale on the y axis. ``` engine.block.getCropScaleY(image); ``` ``` flipCropHorizontal(id: DesignBlockId): void ``` Adjusts the crop in order to flip the content along its own horizontal axis. * `block`: The block whose crop should be updated. ``` engine.block.flipCropHorizontal(image); ``` ``` flipCropVertical(id: DesignBlockId): void ``` Adjusts the crop in order to flip the content along its own vertical axis. * `block`: The block whose crop should be updated. ``` engine.block.flipCropVertical(image); ``` ``` getCropScaleRatio(id: DesignBlockId): number ``` Get the crop scale ratio of the given design block. * `id`: The block whose crop scale ratio should be queried. * Returns The crop scale ratio. ``` engine.block.getCropScaleRatio(image); ``` ``` getCropRotation(id: DesignBlockId): number ``` Get the crop rotation of the given design block. * `id`: The block whose crop rotation should be queried. * Returns The crop rotation. ``` engine.block.getCropRotation(image); ``` ``` getCropTranslationX(id: DesignBlockId): number ``` Get the crop translation on the x axis of the given design block. * `id`: The block whose translation should be queried. * Returns The translation on the x axis. ``` engine.block.getCropTranslationX(image); ``` ``` getCropTranslationY(id: DesignBlockId): number ``` Get the crop translation on the y axis of the given design block. * `id`: The block whose translation should be queried. * Returns The translation on the y axis. ``` engine.block.getCropTranslationY(image); ``` ``` supportsContentFillMode(id: DesignBlockId): boolean ``` Query if the given block has a content fill mode. * `id`: The block to query. * Returns true, if the block has a content fill mode. ``` engine.block.supportsContentFillMode(image); ``` ``` getContentFillMode(id: DesignBlockId): ContentFillMode ``` Query a block's content fill mode. * `id`: The block to query. * Returns The current mode: crop, cover or contain. ``` engine.block.getContentFillMode(image); ``` Manage Assets - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/assets/?platform=web&language=javascript # Manage Assets In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to manage assets through the `asset` API. To begin working with assets first you need at least one asset source. As the name might imply asset sources provide the engine with assets. These assets then show up in the editor's asset library. But they can also be independently searched and used to create design blocks. Asset sources can be added dynamically using the `asset` API as we will show in this guide. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.17.0/index.js'; const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.17.0/assets' }; CreativeEngine.init(config).then(async (engine) => { const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.appendChild(scene, page); ``` ## Defining a Custom Asset Source Asset sources need at least an `id` and a `findAssets` function. You may notice asset source functions are all `async`. This way you can use web requests or other long-running operations inside them and return results asynchronously. Let's go over these properties one by one: ``` const customSource = { ``` All functions of the `asset` API refer to an asset source by its unique `id`. That's why it has to be mandatory. Trying to add an asset source with an already registered `id` will fail. ``` id: 'foobar', ``` ## Finding and Applying Assets The `findAssets` function should return paginated asset results for the given `queryData`. The asset results have a set of mandatory and optional properties. For a listing with an explanation for each property please refer to the [Integrate a Custom Asset Source](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) guide. The properties of the `queryData` and the pagination mechanism are also explained in this guide. ``` findAssets(sourceId: string, query: AssetQueryData): Promise> ``` Finds assets of a given type in a specific asset source. * `sourceId`: The ID of the asset source. * `query`: All the options to filter the search results by. * Returns The search results. ``` const result = await engine.asset.findAssets(customSource.id, { page: 0, perPage: 100 }); const asset = result.assets[0]; const sortByNewest = await engine.asset.findAssets('ly.img.image.upload', { page: 0, perPage: 10, sortingOrder: 'Descending' }); const sortById = await engine.asset.findAssets('ly.img.image.upload', { page: 0, perPage: 10, sortingOrder: 'Ascending', sortKey: 'id' }); const sortByMetaKeyValue = await engine.asset.findAssets( 'ly.img.image.upload', { page: 0, perPage: 10, sortingOrder: 'Ascending', sortKey: 'someMetaKey' } ); const search = await engine.asset.findAssets('ly.img.asset.source.unsplash', { query: 'banana', page: 0, perPage: 100 }); ``` The optional function 'applyAsset' is to define the behavior of what to do when an asset gets applied to the scene. You can use the engine's APIs to do whatever you want with the given asset result. In this case, we always create an image block and add it to the first page we find. If you don't provide this function the engine's default behavior is to create a block based on the asset result's `meta.blockType` property, add the block to the active page, and sensibly position and size it. ``` apply(sourceId: string, assetResult: AssetResult): Promise ``` Apply an asset result to the active scene. The default behavior will instantiate a block and configure it according to the asset's properties. Note that this can be overridden by providing an `applyAsset` function when adding the asset source. * `sourceId`: The ID of the asset source. * `assetResult`: A single assetResult of a `findAssets` query. ``` await engine.asset.apply(customSource.id, asset); ``` ``` defaultApplyAsset(assetResult: AssetResult): Promise ``` The default implementation for applying an asset to the scene. This implementation is used when no `applyAsset` function is provided to `addSource`. * `assetResult`: A single assetResult of a `findAssets` query. ``` return engine.asset.defaultApplyAsset(assetResult); ``` ``` applyToBlock(sourceId: string, assetResult: AssetResult, block: DesignBlockId): Promise ``` Apply an asset result to the given block. * `sourceId`: The ID of the asset source. * `assetResult`: A single assetResult of a `findAssets` query. * `block`: The block the asset should be applied to. ``` await engine.asset.applyToBlock(customSource.id, asset); ``` ``` defaultApplyAssetToBlock(assetResult: AssetResult, block: DesignBlockId): Promise ``` The default implementation for applying an asset to an existing block. * `assetResult`: A single assetResult of a `findAssets` query. * `block`: The block to apply the asset result to. ``` engine.asset.defaultApplyAssetToBlock(assetResult, block); ``` ``` getSupportedMimeTypes(sourceId: string): string[] ``` Queries the list of supported mime types of the specified asset source. An empty result means that all mime types are supported. * `sourceId`: The ID of the asset source. ``` const mimeTypes = engine.asset.getSupportedMimeTypes( ``` ## Registering a New Asset Source ``` addSource(source: AssetSource): void ``` Adds a custom asset source. Its ID has to be unique. * `source`: The asset source. ``` engine.asset.addSource(customSource); ``` ``` addLocalSource(id: string, supportedMimeTypes?: string[], applyAsset?: (asset: CompleteAssetResult) => Promise, applyAssetToBlock?: (asset: CompleteAssetResult, block: DesignBlockId) => Promise): void ``` Adds a local asset source. Its ID has to be unique. * `source`: The asset source. * `supportedMimeTypes`: The mime types of assets that are allowed to be added to this local source. * `applyAsset`: An optional callback that can be used to override the default behavior of applying a given asset result to the active scene. * `applyAssetToBlock`: An optional callback that can be used to override the default behavior of applying an asset result to a given block. ``` engine.asset.addLocalSource('local-source'); ``` ``` findAllSources(): string[] ``` Finds all registered asset sources. * Returns A list with the IDs of all registered asset sources. ``` engine.asset.findAllSources(); ``` ``` removeSource(id: string): void ``` Removes an asset source with the given ID. * `id`: The ID to refer to the asset source. ``` engine.asset.removeSource('local-source'); ``` ``` onAssetSourceAdded: (callback: (sourceID: string) => void) => (() => void) ``` Register a callback to be called every time an asset source is added. * `callback`: The function that is called whenever an asset source is added. * Returns A method to unsubscribe. ``` engine.asset.onAssetSourceAdded((sourceID) => { ``` ``` onAssetSourceRemoved: (callback: (sourceID: string) => void) => (() => void) ``` Register a callback to be called every time an asset source is removed. * `callback`: The function that is called whenever an asset source is added. * Returns A method to unsubscribe. ``` engine.asset.onAssetSourceRemoved((sourceID) => { ``` ``` onAssetSourceUpdated: (callback: (sourceID: string) => void) => (() => void) ``` Register a callback to be called every time an asset source's contents are changed. * `callback`: The function that is called whenever an asset source is updated. * Returns A method to unsubscribe. ``` engine.asset.onAssetSourceUpdated((sourceID) => { ``` ## Scene Asset Sources A scene colors asset source is automatically available that allows listing all colors in the scene. This asset source is read-only and is updated when `findAssets` is called. ``` const sceneColorsResult = await engine.asset.findAssets( 'ly.img.scene.colors', { page: 0, perPage: 99999 } ); const colorAsset = sceneColorsResult.assets[0]; ``` ## Add an Asset ``` addAssetToSource(sourceId: string, asset: AssetDefinition): void ``` Adds the given asset to a local asset source. * `sourceId`: The local asset source ID that the asset should be added to. * `asset`: The asset to be added to the asset source. ``` engine.asset.addAssetToSource('local-source', { id: 'ocean-waves-1', label: { en: 'relaxing ocean waves' }, tags: { en: ['ocean', 'waves', 'soothing', 'slow'] }, meta: { uri: `https://example.com/ocean-waves-1.mp4`, thumbUri: `https://example.com/thumbnails/ocean-waves-1.jpg`, mimeType: 'video/mp4', width: 1920, height: 1080 } }); ``` ## Remove an Asset ``` removeAssetFromSource(sourceId: string, assetId: string): void ``` Removes the specified asset from its local asset source. * `sourceId`: The id of the local asset source that currently contains the asset. * `assetId`: The id of the asset to be removed. ``` engine.asset.removeAssetFromSource('local-source', 'ocean-waves-1'); ``` ## Asset Source Content Updates If the contents of your custom asset source change, you can call the `assetSourceUpdated` API to later notify all subscribers of the `onAssetSourceUpdated` API. ``` assetSourceContentsChanged(sourceID: string): void ``` Notifies the engine that the contents of an asset source changed. * `sourceID`: The asset source whose contents changed. ``` engine.asset.assetSourceContentsChanged(customSource.id); ``` ## Groups in Assets ``` getGroups(id: string): Promise ``` Queries the asset source's groups for a certain asset type. * `id`: The ID of the asset source. * Returns The asset groups. ``` const groups = engine.asset.getGroups(customSource.id); ``` ## Credits and License ``` getCredits(sourceId: string): { name: string; url: string | undefined; } | undefined ``` Queries the asset source's credits info. * `sourceId`: The ID of the asset source. * Returns The asset source's credits info consisting of a name and an optional URL. ``` const credits = engine.asset.getCredits(customSource.id); ``` ``` getLicense(sourceId: string): { name: string; url: string | undefined; } | undefined ``` Queries the asset source's license info. * `sourceId`: The ID of the asset source. * Returns The asset source's license info consisting of a name and an optional URL. ``` const license = engine.asset.getLicense(customSource.id); ``` Use of Emojis in Text - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/text-with-emojis/?platform=web&language=javascript # Use of Emojis in Text Text blocks in CE.SDK support the use of emojis. A default emoji font is used to render these independently from the target platform. This guide shows how to change the default font and use emojis in text blocks. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-text-with-emojis?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Text+With+Emojis&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-text-with-emojis). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-text-with-emojis?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Text+With+Emojis&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-text-with-emojis) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/index.js'; ``` ## Change the Default Emoji Font The default front URI can be changed when another emoji font should be used or when the font should be served from another website, a content delivery network (CDN), or a file path. The preset is to use the [NotoColorEmoji](https://github.com/googlefonts/noto-emoji) font loaded from our [CDN](https://cdn.img.ly/assets/v3/emoji/NotoColorEmoji.ttf). This font file supports a wide variety of Emojis and is licensed under the [Open Font License](https://cdn.img.ly/assets/v3/emoji/LICENSE.txt). In order to change the URI, call the `setSettingString(keypath: string, value: string)` [Editor API](/docs/cesdk/engine/api/editor-change-settings/) with 'defaultEmojiFontFileUri' as keypath and the new URI as value. ``` let uri = engine.editor.getSettingString('ubq://defaultEmojiFontFileUri'); // From a bundle engine.editor.setSettingString( 'ubq://defaultEmojiFontFileUri', 'bundle://ly.img.cesdk/fonts/NotoColorEmoji.ttf' ); // From a URL engine.editor.setSettingString( 'ubq://defaultEmojiFontFileUri', 'https://cdn.img.ly/assets/v2/emoji/NotoColorEmoji.ttf' ); ``` ## Add a Text Block with an Emoji To add a text block with an emoji, add a text block and set the emoji as text content. ``` const text = engine.block.create('text'); engine.block.setString(text, 'text/text', 'Text with an emoji 🧐'); engine.block.setWidthMode(text, 'Auto'); engine.block.setHeightMode(text, 'Auto'); engine.block.appendChild(page, text); ``` Strokes - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-strokes/?platform=web&language=javascript # Strokes In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify strokes through the `block` API. Strokes can be added to any shape or text and stroke styles are varying from plain solid lines to dashes and gaps of varying lengths and can have different end caps. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Strokes ``` supportsStroke(id: DesignBlockId): boolean ``` Query if the given block has a stroke property. * `id`: The block to query. * Returns True if the block has a stroke property. ``` if (engine.block.supportsStroke(block)) { ``` ``` setStrokeEnabled(id: DesignBlockId, enabled: boolean): void ``` Enable or disable the stroke of the given design block. Required scope: 'stroke/change' * `id`: The block whose stroke should be enabled or disabled. * `enabled`: If true, the stroke will be enabled. ``` engine.block.setStrokeEnabled(block, true); ``` ``` isStrokeEnabled(id: DesignBlockId): boolean ``` Query if the stroke of the given design block is enabled. * `id`: The block whose stroke state should be queried. * Returns True if the block's stroke is enabled. ``` const strokeIsEnabled = engine.block.isStrokeEnabled(block); ``` ``` setStrokeColor(id: DesignBlockId, color: Color): void ``` Set the stroke color of the given design block. Required scope: 'stroke/change' * `id`: The block whose stroke color should be set. * `color`: The color to set. ``` engine.block.setStrokeColor(block, { r: 0, g: 1, b: 0, a: 1 }); ``` ``` getStrokeColor(id: DesignBlockId): Color ``` Get the stroke color of the given design block. * `id`: The block whose stroke color should be queried. * Returns The stroke color. ``` const strokeColor = engine.block.getStrokeColor(block); ``` ``` setStrokeWidth(id: DesignBlockId, width: number): void ``` Set the stroke width of the given design block. Required scope: 'stroke/change' * `id`: The block whose stroke width should be set. * `width`: The stroke width to be set. ``` engine.block.setStrokeWidth(block, 5); ``` ``` getStrokeWidth(id: DesignBlockId): number ``` Get the stroke width of the given design block. * `id`: The block whose stroke width should be queried. * Returns The stroke's width. ``` const strokeWidth = engine.block.getStrokeWidth(block); ``` ``` setStrokeStyle(id: DesignBlockId, style: StrokeStyle): void ``` Set the stroke style of the given design block. Required scope: 'stroke/change' * `id`: The block whose stroke style should be set. * `style`: The stroke style to be set. ``` engine.block.setStrokeStyle(block, 'Dashed'); ``` ``` type StrokeStyle = 'Dashed' | 'DashedRound' | 'Dotted' | 'LongDashed' | 'LongDashedRound' | 'Solid' ``` ``` engine.block.setStrokeStyle(block, 'Dashed'); ``` ``` getStrokeStyle(id: DesignBlockId): StrokeStyle ``` Get the stroke style of the given design block. * `id`: The block whose stroke style should be queried. * Returns The stroke's style. ``` const strokeStyle = engine.block.getStrokeStyle(block); ``` ``` setStrokePosition(id: DesignBlockId, position: StrokePosition): void ``` Set the stroke position of the given design block. Required scope: 'stroke/change' * `id`: The block whose stroke position should be set. * `position`: The stroke position to be set. ``` engine.block.setStrokePosition(block, 'Outer'); ``` ``` type StrokePosition = 'Center' | 'Inner' | 'Outer' ``` ``` engine.block.setStrokePosition(block, 'Outer'); ``` ``` getStrokePosition(id: DesignBlockId): StrokePosition ``` Get the stroke position of the given design block. * `id`: The block whose stroke position should be queried. * Returns The stroke position. ``` const strokePosition = engine.block.getStrokePosition(block); ``` ``` setStrokeCornerGeometry(id: DesignBlockId, cornerGeometry: StrokeCornerGeometry): void ``` Set the stroke corner geometry of the given design block. Required scope: 'stroke/change' * `id`: The block whose stroke join geometry should be set. * `cornerGeometry`: The stroke join geometry to be set. ``` engine.block.setStrokeCornerGeometry(block, 'Round'); ``` ``` type StrokeCornerGeometry = 'Bevel' | 'Miter' | 'Round' ``` ``` engine.block.setStrokeCornerGeometry(block, 'Round'); ``` ``` getStrokeCornerGeometry(id: DesignBlockId): StrokeCornerGeometry ``` Get the stroke corner geometry of the given design block. * `id`: The block whose stroke join geometry should be queried. * Returns The stroke join geometry. ``` const strokeCornerGeometry = engine.block.getStrokeCornerGeometry(block); ``` Using a custom URI resolver - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/resolve-custom-uri/?platform=web&language=javascript # Using a custom URI resolver CE.SDK gives you full control over how URIs should be resolved. To register a custom resolver, use `setURIResolver` and pass in a function implementing your resolution logic. If a custom resolver is set, any URI requested by the engine is passed through the resolver. The URI your logic returns is then fetched by the engine. The resolved URI is just used for the current request and not stored. If, and only if, no custom resolver is set, the engine performs the default behaviour: absolute paths are unchanged and relative paths are prepended with the value of the `basePath` setting. #### Warning Your custom URI resolver must return an absolute path with a scheme. ​ Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-uri-resolver?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Uri+Resolver&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-uri-resolver). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-uri-resolver?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Uri+Resolver&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-uri-resolver) We can preview the effects of setting a custom URI resolver with the function `getAbsoluteURI`. Before setting a custom URI resolver, the default behavior is as before and the given relative path will be prepended with the contents of `basePath`. ``` /** This will return 'https://cdn.img.ly/packages/imgly/cesdk-js/1.43.0/assets/banana.jpg'. */ instance.engine.editor.getAbsoluteURI('/banana.jpg'); ``` To show that the resolver can be fairly free-form, in this example we register a custom URI resolver that replaces all `.jpg` images with our company logo. The resolved URI are expected to be absolute. Note: you can still access the default URI resolver by calling `defaultURIResolver(relativePath)`. ``` /** Replace all .jpg files with the IMG.LY logo! **/ instance.engine.editor.setURIResolver((uri, defaultURIResolver) => { if (uri.endsWith('.jpg')) { return 'https://img.ly/static/ubq_samples/imgly_logo.jpg'; } /** Make use of the default URI resolution behavior. */ return defaultURIResolver(uri); }); ``` Given the same path as earlier, the custom resolver transforms it as specified. Note that after a custom resolver is set, relative paths that the resolver does not transform remain unmodified. ``` /** * The custom resolver will return a path to the IMG.LY logo because the given path ends with '.jpg'. * This applies regardless if the given path is relative or absolute. */ instance.engine.editor.getAbsoluteURI('/banana.jpg'); /** The custom resolver will not modify this path because it ends with '.png'. */ instance.engine.editor.getAbsoluteURI('https://example.com/orange.png'); /** Because a custom resolver is set, relative paths that the resolver does not transform remain unmodified! */ instance.engine.editor.getAbsoluteURI('/orange.png'); ``` To remove a previously set custom resolver, call the function with a `null` value. The URI resolution is now back to the default behavior. ``` /** Removes the previously set resolver. */ instance.engine.editor.setURIResolver(null); /** Since we've removed the custom resolver, this will return 'https://cdn.img.ly/packages/imgly/cesdk-js/1.43.0/assets/banana.jpg' like before. */ instance.engine.editor.getAbsoluteURI('/banana.jpg'); ``` Overview - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/guides/ CESDK/CE.SDK/Web Editor/Guides # Overview Delight your users with the powerful theming capabilities of the CreativeEditor SDK. ![](/docs/cesdk/ecb3651a2ef2f642eb9b21bcb3566a28/theming.png) Adjusting the CE.SDK's style to match your corporate identity or just your taste is a breeze. ## Detailed Guides about Theming **[Controlling UI Elements](/docs/cesdk/ui/guides/elements/)** Control which UI elements are available. **[The Video Editor](/docs/cesdk/ui/guides/video/overview/)** Enable your users to edit videos. **[Predefined Themes](/docs/cesdk/ui/guides/theming/#using-the-built-in-themes-and-scale)** Get to know our hand-crafted predefined themes and how to use them. **[Theme Generator](/docs/cesdk/ui/guides/theming/#using-the-theme-generator)** Learn how to quickly adapt the predefined themes to match your needs and quickly create custom themes.. **[Configuration](/docs/cesdk/ui/guides/theming/#configuration)** Explore advanced configuration options to quickly realize your desired theme. **[Theming API](/docs/cesdk/ui/guides/theming/#theming-api)** Learn about advanced theming techniques via our dedicated API. [ Previous UI Configuration ](/docs/cesdk/ui/configuration/ui/)[ Next I18n ](/docs/cesdk/ui/guides/i18n/) How to Store Custom Metadata - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/store-metadata/?platform=web&language=javascript # How to Store Custom Metadata CE.SDK allows you to store custom metadata in your scenes. You can attach metadata to your scene or directly to your individual design blocks within the scene. This metadata is persistent across saving and loading of scenes. It simply consists of key value pairs of strings. Using any string-based serialization format such as JSON will allow you to store even complex objects. Please note that when duplicating blocks their metadata will also be duplicated. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-store-metadata?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Store+Metadata&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-store-metadata). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-store-metadata?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Store+Metadata&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-store-metadata) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/index.js'; const config = { license: 'mtLT-_GJwMhE7LDnO8KKEma7qSuzWuDxiKuQcxHKmz3fjaXWY2lT3o3Z2VdL5twm', userId: 'guides-user', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/assets' }; CreativeEngine.init(config).then(async (engine) => { let scene = await engine.scene.createFromImage( 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); // get the graphic block with the image fill // Note: In this example we know that the first block since there is only one block in the scene // at this point. You might need to filter by fill type if you have multiple blocks. const block = engine.block.findByType('graphic')[0]; ``` ## Working with Metadata We can add metadata to any design block using `setMetadata(block: number, key: string, value: string)`. This also includes the scene block. ``` engine.block.setMetadata(scene, 'author', 'img.ly'); engine.block.setMetadata(block, 'customer_id', '1234567890'); /* We can even store complex objects */ const payment = { id: 5, method: 'credit_card', received: true }; engine.block.setMetadata(block, 'payment', JSON.stringify(payment)); ``` We can retrieve metadata from any design block or scene using `getMetadata(block: number, key: string): string`. Before accessing the metadata you check for its existence using `hasMetadata(block: number, key: string): boolean`. ``` /* This will return 'img.ly' */ engine.block.getMetadata(scene, 'author'); /* This will return '1000000' */ engine.block.getMetadata(block, 'customer_id'); ``` We can query all metadata keys from any design block or scene using `findAllMetadata(block: number): string[]`. For blocks without any metadata, this will return an empty list. ``` /* This will return ['customer_id'] */ engine.block.findAllMetadata(block); ``` If you want to get rid of any metadata, you can use `removeMetadata(block: number, key: string)`. ``` engine.block.removeMetadata(block, 'payment'); /* This will return false */ engine.block.hasMetadata(block, 'payment'); ``` Metadata will automatically be saved and loaded as part the scene. So you don't have to worry about it getting lost or having to save it separately. ``` /* We save our scene and reload it from scratch */ const sceneString = await engine.scene.saveToString(); scene = await engine.scene.loadFromString(sceneString); /* This still returns 'img.ly' */ engine.block.getMetadata(scene, 'author'); /* And this still returns '1234567890' */ engine.block.getMetadata(block, 'customer_id'); ``` Creating A Custom Panel - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/guides/creatingCustomPanel/ CESDK/CE.SDK/Web Editor/Customization/Guides # Creating A Custom Panel Learn how to create and open a custom panel in the CE.SDK editor For some complex workflows, simply adding or moving buttons may not be enough. You’ll need a way for users to interact with the panel’s content to input or process information. The CE.SDK editor allows you to add fully custom panels, and in this guide, we will cover the basics. Please, also take a look at the [Custom Panel API reference](/docs/cesdk/ui/customization/api/customPanel/) for more information. ## Registering a Custom Panel The entry point for writing a custom panel is the method `registerPanel`. For a given panel identifier, you add a function that will be called repeatedly to render the content of the panel. Several arguments are passed to this function that can be used to access the current state of the `engine` and render an UI with the help of the `builder`. ``` cesdk.ui.registerPanel('myCustomPanel', ({ builder, engine }) => { // Once `findAllSelected` changes, the panel will be re-rendered // with the correct number of selected blocks const selectedIds = engine.block.findAllSelected(); // Create a new section with a title builder.Section('selected', { title: `Selected Elements: ${selectedIds.length}`, children: () => { // For every selected block, ... selectedIds.forEach((selectedId) => { // ... create a button ... builder.Button(`select.${selectedId}`, { label: `Deselect ${selectedId}`, onClick: () => { // ... that deselects the block when clicked engine.block.setSelected(selectedId, false); } }); }); } }); }); ``` Once a panel is registered, it can be controlled using the [Panel API](/docs/cesdk/ui/customization/api/panel/), just like any other panel. For instance, you can open a custom panel with `cesdk.ui.openPanel(registeredPanelId)`. Other settings, such as position and floating, can also be adjusted accordingly. In most cases, you will want to open it using a custom button, .e.g in a button inside the [Dock](/docs/cesdk/ui/customization/api/dock/) or [Inspector Bar](/docs/cesdk/ui/customization/api/inspectorBar/). ``` cesdk.ui.registerComponent('myCustomPanel.dock', ({ builder }) => { const isPanelOpen = cesdk.ui.isPanelOpen('myCustomPanel'); builder.Button('open-my-custom-panel', { label: 'My Custom Panel', onClick: () => { if (isPanelOpen) { cesdk.ui.closePanel('myCustomPanel'); } else { cesdk.ui.openPanel('myCustomPanel'); } } }); }); cesdk.ui.setDockOrder([ ...cesdk.ui.getDockOrder(), // We add a spacer to push the new button to the bottom 'ly.img.spacer', // The id of the component we registered earlier to open the panel 'myCustomPanel.dock' ]); ``` [ Previous One-Click Quick Action Plugins ](/docs/cesdk/ui/customization/guides/oneClickQuickActionPlugins/)[ Next Overview ](/docs/cesdk/ui/customization/api/) Save Scenes to a Blob - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/save-scene-to-blob/?platform=web&language=javascript # Save Scenes to a Blob In this example, we will show you how to save scenes as a `Blob` with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-save-scene-to-blob?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Save+Scene+To+Blob&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-save-scene-to-blob). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-save-scene-to-blob?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Save+Scene+To+Blob&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-save-scene-to-blob) The CreativeEngine allows you to save scenes in a binary format to share them between editors or store them for later editing. This is done by converting the contents of a scene to a string, which can then be stored or transferred. For sending these to a remote location, we wrap them in a `Blob` and treat it as a file object. #### Warning A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. To get hold of the scene contents as string, you need to use `engine.scene.saveToString()`. This is an asynchronous method returning a `Promise`. After waiting for the `Promise` to resolve, we receive a plain string holding the entire scene currently loaded in the editor. This includes all pages and any hidden elements but none of the actual asset data. ``` engine.scene .saveToString() .then((savedSceneString) => { const blob = new Blob([savedSceneString], { type: 'text/plain' }); const formData = new FormData(); formData.append('file', blob); fetch('/upload', { method: 'POST', body: formData }); }) .catch((error) => { console.error('Save failed', error); }); ``` The returned string consists solely of ASCII characters and can safely be used further or written to a database. In this case, we need a `Blob` object, so we proceed to wrap the received string into a `Blob`, a file-like object of immutable, raw data. ``` const blob = new Blob([savedSceneString], { type: 'text/plain' }); ``` That object can then be treated as a form file parameter and sent to a remote location. ``` const formData = new FormData(); formData.append('file', blob); fetch('/upload', { method: 'POST', body: formData }); ``` Exploration - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-exploration/?platform=web&language=javascript # Exploration Learn how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to explore scenes through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` findAll(): DesignBlockId[] ``` Return all blocks currently known to the engine. * Returns A list of block ids. ``` const allIds = engine.block.findAll(); ``` ``` findAllPlaceholders(): DesignBlockId[] ``` Return all placeholder blocks in the current scene. * Returns A list of block ids. ``` const allPlaceholderIds = engine.block.findAllPlaceholders(); ``` ``` findByType(type: ObjectType): DesignBlockId[] ``` Finds all blocks with the given type. * `type`: The type to search for. * Returns A list of block ids. ``` const allPages = engine.block.findByType('page'); const allImageFills = engine.block.findByType('fill/image'); const allStarShapes = engine.block.findByType('shape/star'); const allHalfToneEffects = try engine.block.findByType('effect/half_tone') const allUniformBlurs = try engine.block.findByType('blur/uniform') ``` ``` findByKind(kind: string): DesignBlockId[] ``` Finds all blocks with the given kind. * `kind`: The kind to search for. * Returns A list of block ids. ``` const allStickers = engine.block.findByKind('sticker'); ``` ``` findByName(name: string): DesignBlockId[] ``` Finds all blocks with the given name. * `name`: The name to search for. * Returns A list of block ids. ``` const ids = engine.block.findByName('someName'); ``` Building and Using Plugins - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/plugins/ CESDK/CE.SDK/Web Editor/Customization/Plugins # Building and Using Plugins Learn about building and using plugins All customization of the editor can be done during the initialization of the CE.SDK web editor by using the customization APIs described here. For most use cases, this will be sufficient. However, sometimes you might want to encapsulate functionality and make it reusable. This is where plugins come in. **[Create a Plugin](/docs/cesdk/ui/customization/plugins/createPlugin/)** Learn how to bundle your own code into a plugin. **[Background Removal](/docs/cesdk/ui/customization/plugins/backgroundRemoval/)** Install and use the official IMG.LY Background Removal plugin. **[Vectorizer](/docs/cesdk/ui/customization/plugins/vectorizer/)** Install and use the official IMG.LY Vectorizer plugin. [ Previous Basics & Principles ](/docs/cesdk/ui/customization/basics/)[ Next Create Plugin ](/docs/cesdk/ui/customization/plugins/createPlugin/) Integrate Creative Editor - CE.SDK | IMG.LY Docs [web/react/javascript] https://img.ly/docs/cesdk/ui/quickstart/?platform=web&language=javascript&framework=react # Integrate Creative Editor In this example, we will show you how to integrate [CreativeEditor SDK](https://img.ly/products/creative-sdk) with React. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-react?title=IMG.LY%27s+CE.SDK%3A+Integrate+With+React&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-react). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-react?title=IMG.LY%27s+CE.SDK%3A+Integrate+With+React&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-react) ## Prerequisites * [Get the latest stable version of **Node.js & NPM**](https://www.npmjs.com/get-npm) * [Setup Create React App](https://create-react-app.dev/docs/getting-started/) ## Setup Note that, for convenience we serve all SDK assets (e.g. images, stickers, fonts etc.) from our CDN by default. For use in a production environment we recommend [serving assets from your own servers](/docs/cesdk/ui/guides/assets-served-from-your-own-servers/). ### 1\. Add CE.SDK to your Project Install the `@cesdk/cesdk-js` dependency via `npm install --save @cesdk/cesdk-js`. The SDK is served entirely via CDN, so we just need to import the CE.SDK module and the stylesheet containing the default theme settings. ``` import CreativeEditorSDK from '@cesdk/cesdk-js'; ``` ### 2\. Create an empty Container We need to add an empty `
` as a container for the SDK. We configure the `
` element to fully fill the browser window. ### 3\. Create a component containing the SDK We'll now create a `CreativeEditorSDKComponent`, that's responsible for initializing the SDK and attaching it to the previously created container. Defining a constant configuration at the top level of your component module or import it from another module should be sufficient for most situations and is easy and safe to do if your configuration is static. If you need configuration that is dynamically created inside your react app, make sure to create it in a `useMemo`. Be careful not to change it after it has been created. If you do so, the Creative Editor SDK will be disposed and recreated with the new configuration. ``` const config = { license: 'mtLT-_GJwMhE7LDnO8KKEma7qSuzWuDxiKuQcxHKmz3fjaXWY2lT3o3Z2VdL5twm', userId: 'guides-user', // Enable local uploads in Asset Library callbacks: { onUpload: 'local' } }; export default function CreativeEditorSDKComponent() { const cesdk_container = useRef(null); const [cesdk, setCesdk] = useState(null); useEffect(() => { if (!cesdk_container.current) return; let cleanedUp = false; let instance; CreativeEditorSDK.create(cesdk_container.current, config).then( async (_instance) => { instance = _instance; if (cleanedUp) { instance.dispose(); return; } // Do something with the instance of CreativeEditor SDK, for example: // Populate the asset library with default / demo asset sources. await Promise.all([ instance.addDefaultAssetSources(), instance.addDemoAssetSources({ sceneMode: 'Design' }) ]); await instance.createDesignScene(); setCesdk(instance); } ); const cleanup = () => { cleanedUp = true; instance?.dispose(); setCesdk(null); }; return cleanup; }, [cesdk_container]); return ( ); } ``` ### 4\. Managing the container reference To let CE.SDK use the container created by our component, we use `useRef` to acquire a reference to the element and pass it as the first parameter to `CreativeEditorSDK.create`. At this point we might also want to store the instantiated CE.SDK in the react state. This instance can be passed around the app via `Context` or be used otherwise to interact with the CE.SDK. Note that this is optional and might not be necessary in every use case. ``` const cesdk_container = useRef(null); const [cesdk, setCesdk] = useState(null); ``` ### 4\. Use an effect to instantiate the SDK Instantiating the CE.SDK is an asynchronous process. As such it needs to be managed in an effect. There are few things to keep in mind to do this successfully. A common pitfall is a [feature of React 18, where during development, effects are always executed twice](https://react.dev/reference/react/useEffect#my-effect-runs-twice-when-the-component-mounts). This can make it tricky to cleanly dispose of a CE.SDK instance that hasn't finished initializing yet. The example code is written in a way to avoid that problem. First, the effect needs to keep track of * the information about whether the effect has been `cleanedUp` or not. * the CE.SDK `instance` it created. This needs to be a variable _inside_ of the effect. * **You can _not_ reliably use an outside variable, ref or state for this**. Due to the asynchronous nature of the effect, using an outside variable creates a risk for two executions of the same effect to overwrite each other's instance. * You also can't use the `_instance` parameter to the promise callback for this. This parameter is not accessible from the `cleanup` function. That's why we assign the instance to the `instance` variable as the first thing in the promise callback. Then, the asynchronous `create` method is called, loading and instantiating the CE.SDK. During this asynchonous process, React 18 will remove the effect again, before the Promise returned from `create` is resolved. We recognize this situation by checking if `cleanedUp` has been set to true. If it has, we abort the rest of the initialization and immediately dispose the fresh instance. During the second execution of the effect by React, `cleanedUp` will be false at this point, and we can proceed normally, configure the instance via the API, load/create a scene and finally update the react state with the instance. The `cleanup` function returned from the effect performs all necessary steps to safely stop the initialization and clean up an existing CE.SDK instance from React. * `cleanedUp` is set to `true`, to let us recognize this situation in the initialization process * The `instance` our effect is managing is disposed. * The react state is update to remove the reference to the CE.SDK instance ``` useEffect(() => { if (!cesdk_container.current) return; let cleanedUp = false; let instance; CreativeEditorSDK.create(cesdk_container.current, config).then( async (_instance) => { instance = _instance; if (cleanedUp) { instance.dispose(); return; } // Do something with the instance of CreativeEditor SDK, for example: // Populate the asset library with default / demo asset sources. await Promise.all([ instance.addDefaultAssetSources(), instance.addDemoAssetSources({ sceneMode: 'Design' }) ]); await instance.createDesignScene(); setCesdk(instance); } ); const cleanup = () => { cleanedUp = true; instance?.dispose(); setCesdk(null); }; return cleanup; }, [cesdk_container]); ``` ### 5\. Serving Webpage locally In order to use the editor we need to run our project via `yarn`. `yarn start` will spin up a development server at [http://localhost:3000](http://localhost:3000) ### Congratulations! You've got CE.SDK up and running. Get to know the SDK and dive into the next steps, when you're ready: * [Perform Basic Configuration](/docs/cesdk/ui/configuration/basics/) * [Serve assets from your own servers](/docs/cesdk/ui/guides/assets-served-from-your-own-servers/) * [Create and use a license key](/docs/cesdk/engine/quickstart/#licensing) * [Configure Callbacks](/docs/cesdk/ui/configuration/callbacks/) * [Add Localization](/docs/cesdk/ui/guides/i18n/) * [Adapt the User Interface](/docs/cesdk/ui/guides/theming/) Observe Events - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/events/?platform=web&language=javascript # Observe Events In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to subscribe to creation, update, and destruction events of design blocks. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-event-api?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Event+Api&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-event-api). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-event-api?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Event+Api&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-event-api) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/index.js'; const config = { license: 'mtLT-_GJwMhE7LDnO8KKEma7qSuzWuDxiKuQcxHKmz3fjaXWY2lT3o3Z2VdL5twm', userId: 'guides-user', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/assets' }; CreativeEngine.init(config).then(async (engine) => { document.getElementById('cesdk_container').append(engine.element); const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.appendChild(scene, page); ``` ## Subscribing to Events The event API provides a single function to subscribe to design block events. The types of events are: * `'Created'`: The design block was just created. * `'Updated'`: A property of the design block was updated. * `'Destroyed'`: The design block was destroyed. Note that a destroyed block will have become invalid and trying to use Block API functions on it will result in an exception. You can always use the Block API's `isValid` function to verify whether a block is valid before use. All events that occur during an engine update are batched, deduplicated, and always delivered at the very end of the engine update. Deduplication means you will receive at most one `'Updated'` event per block per subscription, even though there could potentially be multiple updates to a block during the engine update. To be clear, this also means the order of the event list provided to your event callback won't reflect the actual order of events within an engine update. * `subscribe(blocks: number[], callback: (events: {type: string, block: number}[]) => void): () => void` subscribe to events from a given list of blocks. Providing an empty list will subscribe to events from all of the engine's blocks. The provided callback will receive a list of events that occurred in the last engine update. Each event consists of the event `type` and the affected `block`. The returned function shall be used to unsubscribe from receiving further events. ``` let unsubscribe = engine.event.subscribe([block], (events) => { for (var i = 0; i < events.length; i++) { let event = events[i]; ``` ## Unsubscribing from events * `subscribe` returns a function that can be called to unsubscribe from receiving events. Note that it is important to unsubscribe when done. At the end of each update the engine has to prepare a list of events for each individual subscriber. Unsubscribing will help to reduce unnecessary work and improve performance. ``` unsubscribe(); ``` Javascript Image Editor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/photo-editor/javascript/ CESDK/CE.SDK/Web Editor/Solutions/Photo Editor # Javascript Image Editor SDK The CreativeEditor SDK provides a powerful and intuitive solution designed for seamless photo editing directly in the browser. This CE.SDK configuration is fully customizable, offering a range of features that cater to various use cases, from simple photo adjustments, image compositions with background removal, to programmatic editing at scale. Whether you are building a photo editing application for social media, e-commerce, or any other platform, the CE.SDK Javascript Image Editor provides the tools you need to deliver a best-in-class user experience. [Explore Demo](https://img.ly/showcases/cesdk/photo-editor-ui/web) ## Key Capabilities ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transform Easily perform operations like cropping, rotating, and resizing your design elements to achieve the perfect composition. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Management Import and manage stickers, images, shapes, and other assets to build intricate and visually appealing designs. ![images](/docs/cesdk/static/TextEditing-2529ca84ae5a97dd9bd3da74d0b86e85.png) ### Text Editing Add and style text blocks with a variety of fonts, colors, and effects, giving users the creative freedom to express themselves. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Processing All editing operations are performed directly in the browser, ensuring fast performance without the need for server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit designs using the engine API, allowing for automated workflows and advanced integrations within your application. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Enhance functionality with plugins and custom scripts, making it easy to tailor the editor to specific needs and use cases. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Design and integrate custom user interfaces that align with your application’s branding and user experience requirements. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Background Removal Utilize the powerful background removal plugin to allow users to effortlessly remove backgrounds from images, entirely on the Client-Side. ![images](/docs/cesdk/static/Filters-348c44197a3cde0e9982fd7560139502.png) ### Filters & Effects Choose from a wide range of filters and effects to add professional-grade finishing touches to photos, enhancing their visual appeal. ![images](/docs/cesdk/static/SizePresets-2fe404893ff6b91bb1695eb39ee9a658.png) ### Size Presets Access a variety of size presets tailored for different use cases, including social media formats and print-ready dimensions. ## Browser Support The CE.SDK Design Editor is optimized for use in modern web browsers, ensuring compatibility with the latest versions of Chrome, Firefox, Edge, and Safari. See the full list of [supported browsers here](https://img.ly/docs/cesdk/faq/browser-support/). ## Supported File Types [IMG.LY](http://img.ly/)'s Creative Editor SDK enables you to load, edit, and save **MP4 files** directly in the browser without server dependencies. The following file types can also be imported for use as assets during video editing: * MP3 * MP4 (with MP3 audio) * M4A * AAC * JPG * PNG * WEBP Individual scenes or assets from the video can be exported as JPG, PNG, Binary, or PDF files. ## Getting Started If you're ready to start integrating CE.SDK into your Javascript application, check out the CE.SDK [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/). In order to configure the editor for an image editing use case consult our [photo editor UI showcase](https://img.ly/showcases/cesdk/photo-editor-ui/web#c) and its [reference implementation](https://github.com/imgly/cesdk-web-examples/tree/main/showcase-photo-editor-ui/src/components/case/CaseComponent.jsx). ## Understanding CE.SDK Architecture & API The following sections provide an overview of the key components of the CE.SDK photo editor UI and its API architecture. If you're ready to start integrating CE.SDK into your application, check out our [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/) or explore the Essential Guides. ### CreativeEditor Photo UI The CE.SDK photo editor UI is a specific configuration of the CE.SDK which focuses the Editor UI to essential photo editing features. It further includes our powerful background removal plugin that runs entirely on the users device, saving on computing costs. This configuration can be further modified to suit your needs, see the section on customization. Designed for intuitive interaction, enabling users to create and edit designs with ease. Key components include: ![](/docs/cesdk/f6c8e768b60bb12f5dc766f8ca63ca62/CESDK-UI.png) * _Canvas_: The primary workspace where users interact with their photo content. * _Dock_: Provides access to tools and assets that are not directly related to the selected image or block, often used for adding or managing assets. * _Inspector Bar_: Controls properties specific to the selected block, such as size, rotation, and other adjustments. * _Canvas Menu_: Provides block-specific settings and actions such as deletion or duplication. * _Canvas Bar_: For actions affecting the canvas or scene as a whole such as adding pages. This is an alternative place for actions such as zoom or redo/undo. * _Navigation Bar_: Offers global actions such as undo/redo, zoom controls, and access to export options. Learn more about interacting with and customizing the photo editor UI in our design editor UI guide. ### CreativeEngine At the heart of CE.SDK is the CreativeEngine, which powers all rendering and design manipulation tasks. It can be used in headless mode or in combination with the CreativeEditor UI. Key features and APIs provided by CreativeEngine include: * **Scene Management:** Create, load, save, and manipulate design scenes programmatically. * **Block Manipulation:** Create and manage elements such as images, text, and shapes within the scene. * **Asset Management:** Load and manage assets like images and SVGs from URLs or local sources. * **Variable Management:** Define and manipulate variables for dynamic content within scenes. * **Event Handling:** Subscribe to events such as block creation or selection changes for dynamic interaction. ## API Overview CE.SDK’s APIs are organized into several categories, each addressing different aspects of scene and content management. The engine API is relevant if you want to programmatically manipulate images to create or modify them at scale. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/) * **Creating Blocks**: ``` const block = engine.block.create('shapes/rectangle'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source) * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API:**](https://img.ly/docs/cesdk/engine/api/events/) * **Subscribing to Events**: ``` engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ### Basic Automation Example The following automation example shows how to turn an image block into a square format for a platform such as Instagram. ``` // Assuming you have an initialized engine and a selected block (which is an image block) // Example dimensions const newWidth = 1080; // Width in pixels const newHeight = 1080; // Height in pixels // Get the ID of the image block you want to resize const imageBlockId = engine.block.findByType('image')[0]; // Resize the image block engine.block.setWidth(imageBlockId, newWidth); engine.block.setHeight(imageBlockId, newHeight); // Optionally, you can ensure the content fills the new dimensions engine.block.setContentFillMode(imageBlockId, 'Cover'); ``` ## Customizing the CE.SDK Photo Editor CE.SDK provides extensive customization options, allowing you to tailor the UI and functionality to meet your specific needs. This can range from basic configuration settings to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object:** Customize the editor’s appearance and functionality by passing a configuration object during initialization. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization:** Adjust the editor’s language and labels to support different locales. ``` const config = { i18n: { en: { 'libraries.ly.img.insert.text.label': 'Add Caption' } } }; ``` * **Custom Asset Sources:** Serve custom sticker or shape assets from a remote URL. ### UI Customization Options * **Theme:** Choose between 'dark' or 'light' themes. ``` const config = { theme: 'dark' }; ``` * **UI Components:** Enable or disable specific UI components as per your application’s needs. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations For deeper customization, [explore the range of APIs](https://img.ly/docs/cesdk/ui/customization/) available for extending the functionality of the photo editor. You can customize the order of components, add new UI elements, and even develop your own plugins to introduce new features. ## Plugins For cases where encapsulating functionality for reuse is necessary, plugins provide an effective solution. Use our [guide on building plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) to get started, or explore existing plugins like **Background Removal** and **Vectorizer**. ## Framework Support CreativeEditor SDK’s photo editor is compatible with any Javascript including, React, Angular, Vue.js, Svelte, Blazor, Next.js, Typescript. It is also compatible with desktop and server-side technologies such as electron, PHP, Laravel and Rails. [ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/quickstart?framework=react)[ Angular ](/docs/cesdk/ui/quickstart?framework=angular)[ Vue ](/docs/cesdk/ui/quickstart?framework=vue)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,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)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous Vue.js ](/docs/cesdk/ui/solutions/video-editor/vuejs/)[ Next React ](/docs/cesdk/ui/solutions/photo-editor/react/) Placeholder - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-placeholder/?platform=web&language=javascript # Placeholder In this example, we will demonstrate how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to manage placeholder behavior and controls through the block API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Placeholder Behavior and Controls ``` supportsPlaceholderBehavior(id: DesignBlockId): boolean ``` Checks whether the block supports placeholder behavior. * `block`: The block to query. * Returns True, if the block supports placeholder behavior. ``` if (engine.block.supportsPlaceholderBehavior(block)) { ``` ``` setPlaceholderBehaviorEnabled(id: DesignBlockId, enabled: boolean): void ``` Enable or disable the placeholder behavior for a block. * `id`: The block whose placeholder behavior should be enabled or disabled. * `enabled`: Whether the placeholder behavior should be enabled or disabled. ``` engine.block.setPlaceholderBehaviorEnabled(block, true); ``` ``` isPlaceholderBehaviorEnabled(id: DesignBlockId): boolean ``` Query whether the placeholder behavior for a block is enabled. * `id`: The block whose placeholder behavior state should be queried. * Returns the enabled state of the placeholder behavior. ``` const placeholderBehaviorIsEnabled = ``` ``` setPlaceholderEnabled(id: DesignBlockId, enabled: boolean): void ``` Enable or disable the placeholder function for a block. * `id`: The block whose placeholder function should be enabled or disabled. * `enabled`: Whether the function should be enabled or disabled. ``` engine.block.setPlaceholderEnabled(block, true); ``` ``` isPlaceholderEnabled(id: DesignBlockId): boolean ``` Query whether the placeholder function for a block is enabled. * `id`: The block whose placeholder function state should be queried. * Returns the enabled state of the placeholder function. ``` const placeholderIsEnabled = engine.block.isPlaceholderEnabled(block); ``` ``` supportsPlaceholderControls(id: DesignBlockId): boolean ``` Checks whether the block supports placeholder controls. * `block`: The block to query. * Returns True, if the block supports placeholder controls. ``` if (engine.block.supportsPlaceholderControls(block)) { ``` ``` setPlaceholderControlsOverlayEnabled(id: DesignBlockId, enabled: boolean): void ``` Enable or disable the visibility of the placeholder overlay pattern for a block. * `block`: The block whose placeholder overlay should be enabled or disabled. * `enabled`: Whether the placeholder overlay should be shown or not. * Returns An empty result on success, an error otherwise. ``` engine.block.setPlaceholderControlsOverlayEnabled(block, true); ``` ``` isPlaceholderControlsOverlayEnabled(id: DesignBlockId): boolean ``` Query whether the placeholder overlay pattern for a block is shown. * `block`: The block whose placeholder overlay visibility state should be queried. * Returns An error if the block was invalid, otherwise the visibility state of the block's placeholder overlay pattern. ``` const overlayEnabled = ``` ``` setPlaceholderControlsButtonEnabled(id: DesignBlockId, enabled: boolean): void ``` Enable or disable the visibility of the placeholder button for a block. * `block`: The block whose placeholder button should be shown or not. * `enabled`: Whether the placeholder button should be shown or not. * Returns An empty result on success, an error otherwise. ``` engine.block.setPlaceholderControlsButtonEnabled(block, true); ``` ``` isPlaceholderControlsButtonEnabled(id: DesignBlockId): boolean ``` Query whether the placeholder button for a block is shown. * `block`: The block whose placeholder button visibility state should be queried. * Returns An error if the block was invalid, otherwise ``` const buttonEnabled = engine.block.isPlaceholderControlsButtonEnabled(block); ``` Adding Color Libraries - CE.SDK Docs - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/guides/custom-color-libraries/?platform=web&language=js # Adding Color Libraries to the CreativeEditor SDK In this example, we will show you how to configure the [CreativeEditor SDK](https://img.ly/products/creative-sdk). CE.SDK has full support for custom color libraries. These appear in the color picker, and allow users to choose from pre-defined sets of colors to use in their design. They can include RGB colors, CMYK colors, and spot colors. They can be used to support your users with a variety of palettes, brand colors, or color books for printing. Note that many providers of color books (e.g. Pantone) require a license to use their data. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-colorlibraries?title=IMG.LY%27s+CE.SDK%3A+Configure+Colorlibraries&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-colorlibraries). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-colorlibraries?title=IMG.LY%27s+CE.SDK%3A+Configure+Colorlibraries&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-colorlibraries) ## General Concept Color libraries are implemented as asset sources, containing individual colors as assets. Each color library is identified by a source ID, a unique string used when establishing and configuring the source. ## Defining Colors To integrate colors into a library, we need to supply them in the shape of an `AssetDefinition`. For more details on the `AssetDefinition` type, please consult our Guide on [integrating custom asset sources](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/). For colors, we are utilizing the `payload` key, providing an `AssetColor` to the `AssetPayload.color` property. In this example, we define one color for every type; see below for the applicable types: ### `AssetRGBColor` Member Type Description Member colorSpace Type `'sRGB'` Description Member r, g, b, a Type `number` Description Red, green, blue, and alpha color channel components ### `AssetCMYKColor` Member Type Description Member colorSpace Type `'CMYK'` Description Member c, m, y, k Type `number` Description Cyan, magenta, yellow, and black color channel components ### `AssetSpotColor` Member Type Description Member colorSpace Type `'SpotColor'` Description Member name Type `string` Description Unique identifier of this spot color. Make sure this matches the standardized name of the spot color when using color books like those supplied by Pantone. Member externalReference Type `string` Description Reference to the source of this spot color (e.g. name of color book), if applicable. Member representation Type `AssetRGBColor` | `AssetCMYKColor` Description Note that spot colors also require a CMYK or RGB color representation, which is used to display an approximate color on screen during editing. In this example, we are defining one of each color type, and add them to a single library, for demonstration purposes. Although there is no technical requirement to do so, it is recommended to separate different color types (RGB, CMYK, Spot) into separate libraries, to avoid confusing users. ``` const colors = [ { id: 'RGB Murky Magenta', label: { en: 'RGB Murky Magenta' }, tags: { en: ['RGB', 'Murky', 'Magenta'] }, payload: { color: { colorSpace: 'sRGB', r: 0.65, g: 0.3, b: 0.65, a: 1 } } }, { id: 'Spot Goo Green', label: { en: 'Spot Goo Green' }, tags: { en: ['Spot', 'Goo', 'Green'] }, payload: { color: { colorSpace: 'SpotColor', name: 'Spot Goo Green', externalReference: 'My Spot Color Book', representation: { colorSpace: 'sRGB', r: 0.7, g: 0.98, b: 0.13, a: 1 } } } }, { id: 'CMYK Baby Blue', label: { en: 'CMYK Baby Blue' }, tags: { en: ['CMYK', 'Baby', 'Blue'] }, payload: { color: { colorSpace: 'CMYK', c: 0.5, m: 0, y: 0, k: 0 } } } ]; ``` ## Configuration ### Labels To show the correct labels for your color library, provide a matching i18n key in your configuration. Color libraries use keys in the format `libraries..label` to look up the correct label. ``` i18n: { en: { 'libraries.myCustomColors.label': 'Custom Color Library' } }, ``` ### Order Color libraries appear in the color picker in a specific order. To define this order, provide an array of source IDs to the `ui.colorLibraries` configuration key. The special source `'ly.img.colors.defaultPalette'` is used to place the default palette (See: [Configuring the Default Color Palette](/docs/cesdk/ui/configuration/ui/#configuring-the-default-color-palette)). If you want to hide this default palette, just remove the key from this array. ``` colorLibraries: ['ly.img.colors.defaultPalette', 'myCustomColors'], ``` ## Adding the library After initializing the engine, use the Asset API to add a local asset source using `addLocalSource`. Afterwards, add colors to the new source using `addAssetToSource`. Make sure to use the same source ID as is referenced in the configuration. See also [Local Asset Sources](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/#local-asset-sources) in our Guide on [Integrating custom asset sources](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) ``` cesdk.engine.asset.addLocalSource('myCustomColors', ['text/plain']); for (const asset of colors) { cesdk.engine.asset.addAssetToSource('myCustomColors', asset); } ``` Javascript Creative Editor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/design-editor/javascript/ CESDK/CE.SDK/Web Editor/Solutions/Design Editor # Javascript Creative Editor SDK CreativeEditor SDK offers a fully-featured JavaScript library for creating and editing rich visual designs directly within the browser. This CE.SDK configuration is highly customizable and extendable, providing a comprehensive set of design editing features such as templating, layout management, asset integration, and more. All operations are executed directly in the browser, without the need for server dependencies. [Explore Demo](https://img.ly/showcases/cesdk/default-ui/web) ## Key Capabilities of the JavaScript Creative Editor SDK ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transform Perform operations like cropping, rotating, and resizing design elements. ![images](/docs/cesdk/static/Templating-5fc837e60dafb163c824ed6684fc7b0c.png) ### Templating Create and apply design templates with placeholders and text variables for dynamic content. ![images](/docs/cesdk/static/Placeholders-5b1a42097f1b9d363d48c30ca90fbcc2.png) ### Placeholders & Lockable Design Constrain templates to guide your users’ design and ensure brand consistency. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Management Import and manage images, shapes, and other assets to build your designs. ![images](/docs/cesdk/static/VideoCollage-ed31587fdccad3fe7f335c6e70216355.png) ### Design Collage Arrange multiple elements on a single canvas to create complex layouts. ![images](/docs/cesdk/static/TextEditing-2529ca84ae5a97dd9bd3da74d0b86e85.png) ### Text Editing Add and style text blocks with various fonts, colors, and effects. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Processing All design editing operations are executed directly in the browser, with no need for server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit designs within your React application using the engine API. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Hook into the engine API and editor events to implement custom features. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Build and integrate custom UIs tailored to your application’s design needs. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Background Removal This plugin makes it easy to remove the background from images running entirely in the browser. ![images](/docs/cesdk/static/CutoutLines-fae102d8e0218cdf5070f7b344fad81e.png) ### Optimized for Print Perfect for web-to-print use cases, supporting spot colors and cut-outs. ## Browser Support The CE.SDK Design Editor is optimized for use in modern web browsers, ensuring compatibility with the latest versions of Chrome, Firefox, Edge, and Safari. See the full list of [supported browsers here](https://img.ly/docs/cesdk/faq/browser-support/). ## Supported File Types Creative Editor SDK supports loading, editing, and saving various image formats directly in the browser, without server dependencies. The supported file types include: * JPG * PNG * SVG * WEBP Individual assets or entire designs can be exported as PDF, JPG, PNG, or SVG files. ## Understanding CE.SDK Architecture & API The following sections provide an overview of the key components of the CE.SDK design editor UI and its API architecture. If you're ready to start integrating CE.SDK into your JavaScript application, check out our [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/) or dive into the [guides](https://img.ly/docs/cesdk/ui/guides/). ### CreativeEditor Design UI The CE.SDK design UI is built for intuitive design creation and editing. Here are the main components and customizable elements within the UI: ![](/docs/cesdk/f6c8e768b60bb12f5dc766f8ca63ca62/CESDK-UI.png) * **Canvas:** The core interaction area for design content. * **Dock:** Entry point for interactions not directly related to the selected design block, often used for accessing asset libraries. * **Canvas Menu:** Access block-specific settings like duplication or deletion. * **Inspector Bar:** Manage block-specific functionalities, such as adjusting properties of the selected block. * **Navigation Bar:** Handles global scene actions like undo/redo and zoom. * **Canvas Bar:** Provides tools for managing the overall canvas, such as adding pages or controlling zoom. * **Layer Panel:** Manage the stacking order and visibility of design elements. Learn more about interacting with and manipulating design controls in our design editor UI guide. ### CreativeEngine CreativeEngine is the core of CE.SDK, responsible for managing the rendering and manipulation of design scenes. It can be used in headless mode or integrated with the CreativeEditor UI. Below are key features and APIs provided by the CreativeEngine: * **Scene Management:** Create, load, save, and modify design scenes programmatically. * **Block Manipulation:** Create and manage design elements, such as shapes, text, and images. * **Asset Management:** Load assets like images and SVGs from URLs or local sources. * **Variable Management:** Define and manipulate variables within scenes for dynamic content. * **Event Handling:** Subscribe to events like block creation or updates for dynamic interaction. ## API Overview The APIs of CE.SDK are grouped into several categories, reflecting different aspects of scene management and manipulation. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes:** ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control:** ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/) * **Creating Blocks**: ``` const block = engine.block.create('shapes/rectangle'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) Variables allow dynamic content within scenes to programmatically create variations of a design. * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source) * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API:**](https://img.ly/docs/cesdk/engine/api/events/) * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ## Customizing the JavaScript Creative Editor CE.SDK provides extensive customization options to adapt the UI to various use cases. These options range from simple configuration changes to more advanced customizations involving callbacks and custom elements. ### Role-Based Customization Switch between "Creator" and "Adopter" roles to control the editing experience. The "Creator" role allows setting constraints on template elements, while the "Adopter" role is focused on adapting these elements. * **Creator:** Set constraints and manage template settings. * **Adopter:** Edit elements within the bounds set by the Creator. ### Basic Customizations * **Configuration Object:** When initializing the CreativeEditor, you can pass a configuration object that defines basic settings such as the base URL for assets, the language, theme, and license key. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization:** Customize the language and labels used in the editor to support different locales. ``` const config = { i18n: { en: { variables: { my_custom_variable: { label: 'Custom Label' } } } } }; ``` * [Custom Asset Sources](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): Serve custom image or SVG assets from a remote URL. ### UI Customization Options * **Theme:** Choose between predefined themes such as 'dark' or 'light'. ``` const config = { theme: 'dark' }; ``` * **UI Components:** Enable or disable specific UI components based on your requirements. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations Learn more about extending editor functionality and customizing its UI to your use case by consulting our in-depth [customization guide](https://img.ly/docs/cesdk/ui/customization/guides/). Here is an overview of the APIs and components available to you. ### Order APIs Customization of the web editor's components and their order within these locations is managed through specific Order APIs, allowing the addition, removal, or reordering of elements. Each location has its own Order API, e.g., `setDockOrder`, `setCanvasMenuOrder`, `setInspectorBarOrder`, `setNavigationBarOrder`, and `setCanvasBarOrder`. ### Layout Components CE.SDK provides special components for layout control, such as `ly.img.separator` for separating groups of components and `ly.img.spacer` for adding space between components. ### Registration of New Components Custom components can be registered and integrated into the web editor using builder components like buttons, dropdowns, and inputs. These components can replace default ones or introduce new functionalities, deeply integrating custom logic into the editor. ### Feature API The Feature API enables conditional display and functionality of components based on the current context, allowing for dynamic customization. For example, you can hide certain buttons for specific block types. ## Plugins You can customize the CE.SDK web editor during its initialization using the APIs outlined above. For many use cases, this will be adequate. However, there are times when you might want to encapsulate functionality for reuse. This is where plugins become useful. Follow our [guide on building your own plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) to learn more or check out one of the plugins we built using this API: * [**Background Removal**](https://img.ly/docs/cesdk/ui/customization/plugins/backgroundRemoval/): Adds a button to the canvas menu to remove image backgrounds. * [**Vectorizer**](https://img.ly/docs/cesdk/ui/customization/plugins/vectorizer/): Transform your pixel-based images images into scalable vector graphics. ## Framework Support CreativeEditor SDK’s video editing library is compatible with any Javascript including, React, Angular, Vue.js, Svelte, Blazor, Next.js, Typescript. It is also compatible with desktop and server-side technologies such as electron, PHP, Laravel and Rails. [ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/solutions/design-editor/react/)[ Angular ](/docs/cesdk/ui/solutions/design-editor/angular/)[ Vue ](/docs/cesdk/ui/solutions/design-editor/vuejs/)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,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)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous Overview ](/docs/cesdk/ui/solutions/)[ Next React ](/docs/cesdk/ui/solutions/design-editor/react/) Configure the Engine to use Assets served from your own Servers - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/assets-served-from-your-own-servers/?platform=web&language=javascript Platform Web iOS Catalyst macOS Android Language JavaScript Platform: Web Language: JavaScript # Configure the Engine to use Assets served from your own Servers Learn how to serve assets from your own servers in the Engine. In this example, we explain how to configure the Creative Engine to use assets hosted on your own servers. While we serve all assets from our own CDN by default, it is highly recommended to serve the assets from your own servers in a production environment. ## Prerequisites * [Get the latest stable version of **Node.js & NPM**](https://www.npmjs.com/get-npm) * (Optional): [Get the latest stable version of **Yarn**](https://yarnpkg.com/getting-started) ## 1\. Add the CreativeEngine to Your Project ``` npm install --save @cesdk/engine@1.43.0 ``` ## 2\. Decide what Assets you need We offer two sets of assets: * _Core_ Assets - Files required for the engine to function. * _Default_ Assets - Demo assets to quickstart your development. ### ⚠️ Warning Prior to `v1.10.0`, the `CreativeEngine` and `CreativeEditorSDK` would load referenced default assets from the `assets/extensions/*` directories and hardcode them as `/extensions/…` references in serialized scenes. To **maintain compatibility** for such scenes, make sure you're still serving version v1.9.2 the `/extensions` directory from your `baseURL`. You can [download them from our CDN](https://cdn.img.ly/packages/imgly/cesdk-engine/1.9.2/assets/extensions.zip). ## 3\. Register IMG.LY's default assets If you want to use our default asset sources in your integration, call `CreativeEngine.addDefaultAssetSources({ baseURL?: string, excludeAssetSourceIds?: string[] }): void`. Right after initialization: ``` CreativeEngine.init(config).then((engine) => { engine.addDefaultAssetSources(); }); ``` This call adds IMG.LY's default asset sources for stickers, vectorpaths and filters to your engine instance. By default, these include the following source ids: * `'ly.img.sticker'` - Various stickers. * `'ly.img.vectorpath'` - Shapes and arrows. * `'ly.img.filter.lut'` - LUT effects of various kinds. * `'ly.img.filter.duotone'` - Color effects of various kinds. * `'ly.img.colors.defaultPalette'` - Default color palette. * `'ly.img.effect'` - Default effects. * `'ly.img.blur'` - Default blurs. * `'ly.img.typeface'` - Default typefaces. If you don't specify a `baseURL` option, the assets are parsed and served from the IMG.LY CDN. It's it is highly recommended to serve the assets from your own servers in a production environment, if you decide to use them. To do so, follow the steps below and pass a `baseURL` option to `addDefaultAssetSources`. If you only need a subset of the IDs above, use the `excludeAssetSourceIds` option to pass a list of ignored Ids. ## 4\. Copy Assets Copy the CreativeEngine `core`, `demo`, `i18n`, `ui`, etc. asset folders to your application's asset folder. The name of the folder depends on your setup and the used bundler, but it's typically a folder called `assets` or `public` in the project root. ``` cp -r ./node_modules/@cesdk/engine/assets public/ ``` Furthermore, if you are using our IMG.LY default assets, download them from [our CDN](https://cdn.img.ly/assets/v3/IMGLY-Assets.zip) and extract them to your public directory as well. If you deploy the site that embeds the CreativeEngine together with all its assets and static files, this might be all you need to do for this step. In different setups, you might need to upload this folder to your CDN. ## 5\. Configure the CreativeEngine to use your self-hosted assets Next, we need to configure the SDK to use our local assets instead of the ones served via our CDN. There are two configuration options that are relevant for this: * `baseURL` should ideally point to the `assets` folder that was copied in the previous step. This can be either an absolute URL, or a path. A path will be resolved using the `window.location.href` of the page where the CreativeEngine is embedded. By default `baseURL` is set to our CDN. * `core.baseURL` must point to the folder containing the core sources and data file for the CreativeEngine. Defaults to `${baseURL}/core` This can be either an absolute URL, or a relative path. A relative path will be resolved using the the previous `baseURL`. By default, `core.baseURL` is set to `core/`. Normally you would simply serve the assets directory from the previous step. That directory already contains the `core` folder, and this setting does not need to be changed. For highly customized setups that separate hosting of the WASM files from the hosting of other assets that are used inside scenes, you can set this to a different URL. * `CreativeEngine.addDefaultAssetSources` offers a `baseURL` option, that needs to be set to an absolute URL. The given URL will be used to lookup the asset definitions from `{{baseURL}}//content.json` and for all file references, like `{{baseURL}}//images/example.png`. By default, default sources parse and reference assets from `https://cdn.img.ly/assets/v3`. ``` const config = { ... // Specify baseURL for all relative URLs. baseURL: '/assets' // or 'https://cdn.mydomain.com/assets' core: { // Specify location of core assets, required by the engine. baseURL: 'core/' }, ... }; // Setup engine and add default sources served from your own server. CreativeEngine.init(config).then((engine) => { engine.addDefaultAssetSources({ baseURL: 'https://cdn.mydomain.com/assets'}) }) ``` ## Versioning of the WASM assets The files that the CreativeEngine loads from `core.baseURL` (`.wasm` and`.data` files, and the `worker-host.js`) are changing between different versions of the CreativeEngine. You need to ensure that after a version update, you update your copy of the assets. The filenames of these assets will also change between updates. This makes it safe to store different versions of these files in the same folder during migrations, as the CreativeEngine will always locate the correct files using the unique filenames. It also means that if you forget to copy the new assets, the CreativeEngine will fail to load them during initialization and abort with an Error message on the console. Depending on your setup this might only happen in your production or staging environments, but not during development where the assets might be served from a local server. Thus we recommend to ensure that copying of the assets is taken care of by your automated deployments and not performed manually. [ Previous Using the Camera ](/docs/cesdk/engine/guides/using-camera/)[ Next Adding Custom Asset Sources ](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) Creating a Scene From an HTMLCanvas - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/create-scene-from-canvas/?platform=web&language=javascript # Creating a Scene From an HTMLCanvas In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image provided from a canvas. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-canvas?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Image+Canvas&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-canvas). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-canvas?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Image+Canvas&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-canvas) Starting from an existing image allows you to use the editor for customizing individual assets. This is done by using `engine.scene.createFromImage(url: string, dpi = 300, pixelScaleFactor = 1): Promise` and passing a URL as argument. The `dpi` argument sets the dots per inch of the scene. The `pixelScaleFactor` sets the display's pixel scale factor. To use the image drawn by a canvas as the initial image, acquire a `dataURL` containing the canvas contents via `canvas.toDataURL()`. ``` const dataURL = canvas.toDataURL(); ``` Use the created URL as a source for the initial image. ``` let scene = await engine.scene.createFromImage(dataURL); ``` We can retrieve the graphic block id of this initial image using `cesdk.engine.block.findByType(type: ObjectType): number[]`. Note that that function returns an array. Since there's only a single graphic block in the scene, the block is at index `0`. ``` // Find the automatically added graphic block with an image fill in the scene. const block = engine.block.findByType('graphic')[0]; ``` We can then manipulate and modify this block. Here we modify its opacity with `cesdk.engine.block.setOpacity(id: number, opacity: number): void`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. engine.block.setOpacity(block, 0.5); ``` When starting with an initial image, the scenes page dimensions match the given image, and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Groups - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-groups/?platform=web&language=javascript # Groups In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to group blocks through the `block` API. Groups form a cohesive unit. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Grouping Multiple blocks can be grouped together to form a cohesive unit. A group being a block, it can itself be part of a group. #### What cannot be grouped * A scene * A block that already is part of a group ``` isGroupable(ids: DesignBlockId[]): boolean ``` Confirms that a given set of blocks can be grouped together. * `ids`: An array of block ids. * Returns Whether the blocks can be grouped together. ``` const groupable = engine.block.isGroupable([member1, member2]) ``` ``` group(ids: DesignBlockId[]): DesignBlockId ``` Group blocks together. * `ids`: A non-empty array of block ids. * Returns The block id of the created group. ``` const group = engine.block.group([member1, member2]); ``` ``` ungroup(id: DesignBlockId): void ``` Ungroups a group. * `id`: The group id from a previous call to `group`. ``` engine.block.ungroup(group); ``` ``` enterGroup(id: DesignBlockId): void ``` Changes selection from selected group to a block within that group. Nothing happens if `group` is not a group. Required scope: 'editor/select' * `id`: The group id from a previous call to `group`. ``` engine.block.enterGroup(group); ``` ``` enterGroup(id: DesignBlockId): void ``` Changes selection from selected group to a block within that group. Nothing happens if `group` is not a group. Required scope: 'editor/select' * `id`: The group id from a previous call to `group`. ``` engine.block.enterGroup(group); ``` ``` exitGroup(id: DesignBlockId): void ``` Changes selection from a group's selected block to that group. Nothing happens if the `id` is not part of a group. Required scope: 'editor/select' * `id`: A block id. ``` engine.block.exitGroup(member1); ``` Load Scenes from a Remote URL - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/load-scene-from-url/?platform=web&language=javascript # Load Scenes from a Remote URL In this example, we will show you how to load scenes from a remote URL with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-remote?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Load+Scene+From+Remote&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-remote). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-remote?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Load+Scene+From+Remote&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-remote) Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. This can be done by fetching a scene file or an archive file from a URL. #### Warning Saving a scene can be done as a either _scene file_ or as an _archive file_ (c.f. [Saving scenes](/docs/cesdk/engine/guides/save-scene/)). A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an _archive file_ contains within it the scene's assets and references them as relative URIs. Determine a URL that points to a scene binary string. ``` const sceneUrl = 'https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene'; ``` We can then pass that string to the `engine.scene.loadFromURL(url: string): Promise` function. The editor will reset and present the given scene to the user. The function is asynchronous and the returned `Promise` resolves if the scene load succeeded. ``` let scene = await engine.scene .loadFromURL(sceneUrl) .then(() => { console.log('Load succeeded'); let text = engine.block.findByType('text')[0]; engine.block.setDropShadowEnabled(text, true); }) .catch((error) => { console.error('Load failed', error); }); ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` let text = engine.block.findByType('text')[0]; engine.block.setDropShadowEnabled(text, true); ``` Scene loads may be reverted using `cesdk.engine.editor.undo()`. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Scene Lifecycle - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/scene-lifecycle/?platform=web&language=javascript # Scene Lifecycle In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to save scenes through the `scene` API and export them to PNG. At any time, the engine holds only a single scene. Loading or creating a scene will replace the current one. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Scene ``` create(sceneLayout?: SceneLayout): DesignBlockId ``` Create a new design scene, along with its own camera. * Returns The scene's handle. ``` let scene = engine.scene.create(layout); ``` ``` type SceneLayout = 'Free' | 'VerticalStack' | 'HorizontalStack' | 'DepthStack' ``` The scene layout determines how the layout stack should layout its pages. ``` const layout = 'Free'; ``` ``` get(): DesignBlockId | null ``` Return the currently active scene. * Returns The scene or null, if none was created yet. ``` scene = engine.scene.get(); ``` ``` createVideo(): DesignBlockId ``` Create a new scene in video mode, along with its own camera. * Returns The scene's handle. ``` scene = engine.scene.createVideo(); ``` ``` getMode(): SceneMode ``` Get the current scene mode. * Returns The current mode of the scene. ``` const mode = scene.getMode(); ``` ``` type SceneMode = 'Design' | 'Video' ``` The mode of the scene defines how the editor and playback should behave. ``` if (mode == 'Design') { // Working with a static design… } ``` ``` createFromImage(url: string, dpi?: number, pixelScaleFactor?: number, sceneLayout?: SceneLayout, spacing?: number, spacingInScreenSpace?: boolean): Promise ``` Loads the given image and creates a scene with a single page showing the image. Fetching the image may take an arbitrary amount of time, so the scene isn't immediately available. * `url`: The image URL. * `dpi`: The scene's DPI. * `pixelScaleFactor`: The display's pixel scale factor. * Returns A promise that resolves with the scene ID on success or rejected with an error otherwise. ``` scene = await engine.scene.createFromImage( 'https://img.ly/static/ubq_samples/sample_4.jpg' ); ``` ``` createFromVideo(url: string): Promise ``` Loads the given video and creates a scene with a single page showing the video. Fetching the video may take an arbitrary amount of time, so the scene isn't immediately available. * `url`: The video URL. * Returns A promise that resolves with the scene ID on success or rejected with an error otherwise. ``` scene = await engine.scene.createFromVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4' ); ``` ## Loading a Scene ``` loadFromString(sceneContent: string): Promise ``` Load the contents of a scene file. * `sceneContent`: The scene file contents, a base64 string. * Returns A handle to the loaded scene. ``` scene = engine.scene.loadFromString(SCENE_CONTENT); ``` ``` loadFromURL(url: string): Promise ``` Load a scene from the URL to the scene file. The scene file will be fetched asynchronously by the engine. This requires continuous `render` calls on this engines instance. * `url`: The URL of the scene file. * Returns scene A promise that resolves once the scene was loaded or rejects with an error otherwise. ``` scene = await engine.scene.loadFromURL( ``` ``` loadFromArchiveURL(url: string): Promise ``` Load a previously archived scene from the URL to the scene file. The scene file will be fetched asynchronously by the engine. This requires continuous `render` calls on this engines instance. * `url`: The URL of the scene file. * Returns scene A promise that resolves once the scene was loaded or rejects with an error otherwise. ``` scene = await engine.scene.loadFromArchiveURL('https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1_scene.zip'); ``` ## Saving a Scene ``` saveToString(allowedResourceSchemes?: string[]): Promise ``` Serializes the current scene into a string. Selection is discarded. * Returns A promise that resolves with a string on success or an error on failure. ``` scene = await engine.scene.saveToString(); ``` ``` saveToArchive(): Promise ``` Saves the current scene and all of its referenced assets into an archive. The archive contains all assets, that were accessible when this function was called. Blocks in the archived scene reference assets relative from to the location of the scene file. These references are resolved when loading such a scene via `loadSceneFromURL`. * Returns A promise that resolves with a Blob on success or an error on failure. ``` const archive = await engine.scene.saveToArchive(); ``` ## Events ``` onActiveChanged: (callback: () => void) => (() => void) ``` Subscribe to changes to the active scene rendered by the engine. * `callback`: This function is called at the end of the engine update, if the active scene has changed. * Returns A method to unsubscribe. ``` const unsubscribe = engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` The Background Removal Plugin - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/plugins/backgroundRemoval/ CESDK/CE.SDK/Web Editor/Customization/Plugins # The Background Removal Plugin Learn about IMG.LY's Background Removal plugin This plugin introduces background removal for the CE.SDK editor, leveraging the power of the [background-removal-js library](https://github.com/imgly/background-removal-js). It integrates seamlessly with CE.SDK, provides users with an efficient tool to remove backgrounds from images directly in the browser with ease and no additional costs or privacy concerns. ## Installation You can install the plugin via npm or yarn. Use the following commands to install the package: ``` yarn add @imgly/plugin-background-removal-web npm install @imgly/plugin-background-removal-web ``` ## Usage Adding the plugin to CE.SDK will automatically add a background removal canvas menu entry for every block with an image fill. ``` import CreativeEditorSDK from '@cesdk/cesdk-js'; import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web'; const config = { license: '', callbacks: { // Please note that the background removal 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(BackgroundRemovalPlugin()); await cesdk.createDesignScene(); ``` ## Configuration Multiple configuration options are available for the background removal plugin. ### Adding Canvas Menu Component After adding the plugin to CE.SDK, it will register a component that can be used inside the canvas menu. It is not added by default but can be included using the following configuration: ``` // Either pass a location via the configuration object, ... BackgroundRemovalPlugin({ ui: { locations: 'canvasMenu' } }) ``` ### Configuration of `@imgly/background-removal` By default, this plugin uses the `@imgly/background-removal-js` library to remove a background from the image fill. All configuration options from this underlying library can be used in this plugin. [See the documentation](https://github.com/imgly/background-removal-js/tree/main/packages/web#advanced-configuration) for further information. ``` import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web'; [...] await cesdk.unstable_addPlugin(BackgroundRemovalPlugin({ provider: { type: '@imgly/background-removal', configuration: { publicPath: '...', // All other configuration options that are passed to the bg removal // library. See https://github.com/imgly/background-removal-js/tree/main/packages/web#advanced-configuration } } })) ``` ## Performance For optimal performance using the correct CORS headers is important. See the library documentation [here](https://github.com/imgly/background-removal-js/tree/main/packages/web#performance) for more details. ``` 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' ``` ## Custom Background Removal Provider It is possible to declare a different provider for the background removal process. ``` BackgroundRemovalPlugin({ provider: { type: 'custom', // Some images have a source set defined which provides multiple images // in different sizes. processSourceSet: async ( // Source set for the current block sorted by the resolution. // URI with the highest URI is first sourceSet: { uri: string; width: number; height: number; }[], ) => { // You should call the remove background method on every URI in the // source set. Depending on your service or your algorithm, you // have the following options: // - Return a source set with a single image (will contradict the use-case of source sets and degrades the user experience) // - Create a segmented mask and apply it to every image (not always available) // - Create a new source set by resizing the resulting blob. // In this example we will do the last case. // First image has the highest resolution and might be the best // candidate to remove the background. const highestResolution = sourceSet[0]; const highestResolutionBlob = await removeBackground(highestResolution.uri); const highestResolutionURI = await uploadBlob(highestResolutionBlob); const remainingSources = await Promise.all( sourceSet.slice(1).map((source) => { // ... const upload = uploadBlob(/* ... */) return { ...source, uri: upload }; }) ); return [{ ...highestResolution, uri: highestResolutionURI }, remainingSources]; } } }); ``` Depending on your use case or service you might end up with a blob that you want to upload by the configured upload handler of the editor. This might look like the following function: ``` async function uploadBlob( blob: Blob, initialUri: string, cesdk: CreativeEditorSDK ) { const pathname = new URL(initialUri).pathname; const parts = pathname.split('/'); const filename = parts[parts.length - 1]; const uploadedAssets = await cesdk.unstable_upload( new File([blob], filename, { type: blob.type }) ); const url = uploadedAssets.meta?.uri; if (url == null) { throw new Error('Could not upload processed fill'); } return url; } ``` [ Previous Create Plugin ](/docs/cesdk/ui/customization/plugins/createPlugin/)[ Next Vectorizer ](/docs/cesdk/ui/customization/plugins/vectorizer/) Migrating to v1.19 - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/introduction/migration_1_19/?platform=web&language=javascript Platform Web iOS Catalyst macOS Android Language JavaScript Platform: Web Language: JavaScript Version [v1.19](/docs/cesdk/faq/changelog/#v1190) of CreativeEngineSDK and CreativeEditorSDK introduces structural changes to many of the current design blocks, making them more composable and more powerful. Along with this update, there are mandatory license changes that require attention. This comes with a number of breaking changes. This document will explain the changes and describe the steps you need to take to adapt them to your setup. ## **License Changes** The `license` parameter is now required for CreativeEngineSDK and CreativeEditorSDK. This means that you will need to update your license parameter in the `CreativeEngine.init` and `CreativeEditorSDK.create` configuration object properties. There is also a new `userId`, an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. ## **Graphic Design Block** A new generic `Graphic` design block with the type id `//ly.img.ubq/graphic` has been introduced, which forms the basis of the new unified block structure. ## **Shapes** Similar to how the fill of a block is a separate object which can be attached to and replaced on a design block, we have now introduced a similar concept for the shape of a block. You use the new `createShape`, `getShape` and `setShape` APIs in order to define the shape of a design block. Only the new `//ly.img.ubq/graphic` block allows for its shape to be changed with these APIs. The new available shape types are: * `//ly.img.ubq/shape/rect` * `//ly.img.ubq/shape/line` * `//ly.img.ubq/shape/ellipse` * `//ly.img.ubq/shape/polygon` * `//ly.img.ubq/shape/star` * `//ly.img.ubq/shape/vector_path` The following block types are now removed in favor of using a `Graphic` block with one of the above mentioned shape instances: * `//ly.img.ubq/shapes/rect` * `//ly.img.ubq/shapes/line` * `//ly.img.ubq/shapes/ellipse` * `//ly.img.ubq/shapes/polygon` * `//ly.img.ubq/shapes/star` * `//ly.img.ubq/vector_path` (The removed type ids use the plural “shapes” and the new shape types use the singular “shape”) This structural change means that the shape-specific properties (e.g. the number of sides of a polygon) are not available on the design block any more but on the shape instances instead. You will have to add calls to `getShape` to get the instance id of the shape instance and then pass that to the property getter and setter APIs. Also remember to change property key strings in the getter and setter calls from the plural `shapes/…` to the singular `shape/…` to match the new type identifiers. ## **Image and Sticker** Previously, `//ly.img.ubq/image` and `//ly.img.ubq/sticker` were their own high-level design block types. They do not support the fill APIs nor the effects APIs. Both of these blocks are now removed in favor of using a `Graphic` block with an image fill (`//ly.img.ubq/fill/image`) and using the effects APIs instead of the legacy image block’s numerous effects properties. At its core, the sticker block has always just been an image block that is heavily limited in its capabilities. You can not crop it, nor apply any effects to it. In order to replicate this difference as closely as possible in the new unified structure, more fine-grained scopes have been added. You can now limit the adopter’s ability to crop a block and to edit its appearance. Note that since these scopes only apply to a user of the editor with the “Adopter” role, a “Creator” user will now have all of the same editing options for both images and for blocks that used to be stickers. ## **Scopes** The following is the list of changes to the design block scopes: * (Breaking) The permission to crop a block was split from `content/replace` and `design/style` into a separate scope: `layer/crop`. * Deprecated the `design/arrange` scope and renamed `design/arrange/move` → `layer/move``design/arrange/resize` → `layer/resize``design/arrange/rotate` → `layer/rotate``design/arrange/flip` → `layer/flip` * Deprecated the `content/replace` scope. For `//ly.img.ubq/text` blocks, it is replaced with the new `text/edit` scope. For other blocks it is replaced with `fill/change`. * Deprecated the `design/style` scope and replaced it with the following fine-grained scopes: `text/character`, `stroke/change`, `layer/opacity`, `layer/blendMode`, `layer/visibility`, `layer/clipping`, `appearance/adjustments`, `appearance/filter`, `appearance/effect`, `appearance/blur`, `appearance/shadow` * Introduced `fill/change`, `stroke/change`, and `shape/change` scopes that control whether the fill, stroke or shape of a block may be edited by a user with an "Adopter" role. * The deprecated scopes are automatically mapped to their new corresponding scopes by the scope APIs for now until they will be removed completely in a future update. ## **Kind** While the new unified block structure both simplifies a lot of code and makes design blocks more powerful, it also means that many of the design blocks that used to have unique type ids now all have the same generic `//ly.img.ubq/graphic` type, which means that calls to the `findByType` cannot be used to filter blocks based on their legacy type ids any more. Simultaneously, there are many instances in which different blocks in the scene which might have the same type and underlying technical structure have different semantic roles in the document and should therefore be treated differently by the user interface. To solve both of these problems, we have introduced the concept of a block “kind”. This is a mutable string that can be used to tag different blocks with a semantic label. You can get the kind of a block using the `getKind` API and you can query blocks with a specific kind using the `findByKind` API. CreativeEngine provides the following default kind values: * `image` * `video` * `sticker` * `scene` * `camera` * `stack` * `page` * `audio` * `text` * `shape` * `group` Unlike the immutable design block type id, you can change the kind of a block with the new `setKind` API. It is important to remember that the underlying structure and properties of a design block are not strictly defined by its kind, since the kind, shape, fill and effects of a block can be changed independent of each other. Therefore, a user-interface should not make assumptions about available properties of a block purely based on its kind. **Note** Due to legacy reasons, blocks with the kind "sticker" will continue to not allow their contents to be cropped. This special behavior will be addressed and replaced with a more general-purpose implementation in a future update. ​ ## **Asset Definitions** The asset definitions have been updated to reflect the deprecation of legacy block type ids and the introduction of the “kind” property. In addition to the “blockType” meta property, you can now also define the `“shapeType”` ,`“fillType”` and `“kind”` of the block that should be created by the default implementation of the applyAsset function. * `“blockType”` defaults to `“//ly.img.ubq/graphic”` if left unspecified. * `“shapeType”` defaults to `“//ly.img.ubq/shape/rect”` if left unspecified * `“fillType”` defaults to `“//ly.img.ubq/fill/color”` if left unspecified Video block asset definitions used to specify the `“blockType”` as `“//ly.img.ubq/fill/video“`. The `“fillType”` meta asset property should now be used instead for such fill type ids. ## **Automatic Migration** CreativeEngine will always continue to support scene files that contain the now removed legacy block types. Those design blocks will be automatically replaced by the equivalent new unified block structure when the scene is loaded, which means that the types of all legacy blocks will change to `“//ly.img.ubq/graphic”`. Note that this can mean that a block gains new capabilities that it did not have before. For example, the line shape block did not have any stroke properties, so the `hasStroke` API used to return `false`. However, after the automatic migration its `Graphic` design block replacement supports both strokes and fills, so the `hasStroke` API now returns `true` . Similarly, the image block did not support fills or effects, but the `Graphic` block does. ## List of all Removed Block Type IDs * `//ly.img.ubq/image` * `//ly.img.ubq/sticker` * `//ly.img.ubq/shapes/rect` * `//ly.img.ubq/shapes/line` * `//ly.img.ubq/shapes/ellipse` * `//ly.img.ubq/shapes/polygon` * `//ly.img.ubq/shapes/star` * `//ly.img.ubq/vector_path` ## **UI Configuration** The configuration options for the legacy blocks have also been removed under `config.ui.elements.blocks` and a new configuration option for the `ly.img.ubq/graphic` block type have been introduced which will then define which UI controls to enable for graphic blocks (crop, filters, adjustments, effects, blur). This new configuration option follows the same structure as before. Here is a list of the deprecated block configuration options: * `//ly.img.ubq/image` * `//ly.img.ubq/fill/video` * `//ly.img.ubq/shapes/rect` * `//ly.img.ubq/shapes/line` * `//ly.img.ubq/shapes/star` * `//ly.img.ubq/shapes/polygon` * `//ly.img.ubq/shapes/ellipse` * `//ly.img.ubq/vector_path` ## Translations Some of the translation keys related to Scopes and Placeholder-Settings have been also updated: * Removed the following keys: * `scope.content.replace` * `scope.design.arrange` * `scope.design.style` * Renamed the following keys: * `scope.design.arrange.flip` is now `scope.layer.flip` * `scope.design.arrange.move` is now `scope.layer.move` * `scope.design.arrange.resize` is now `scope.layer.resize` * `scope.design.arrange.rotate` is now `scope.layer.rotate` * Added the following keys: * `component.placeholder.appearance.description` * `component.placeholder.appearance` * `component.placeholder.arrange.description` * `component.placeholder.arrange` * `component.placeholder.disableAll` * `component.placeholder.enableAll` * `component.placeholder.fill.description` * `component.placeholder.fill` * `component.placeholder.general.description` * `component.placeholder.general` * `component.placeholder.shape.description` * `component.placeholder.shape` * `component.placeholder.stroke.description` * `component.placeholder.stroke` * `component.placeholder.text.description` * `component.placeholder.text` * `scope.appearance.adjustments` * `scope.appearance.blur` * `scope.appearance.effect` * `scope.appearance.filter` * `scope.appearance.shadow` * `scope.fill.change` * `scope.layer.blendMode` * `scope.layer.opacity` * `scope.shape.change` * `scope.stroke.change` * `scope.text.character` * `scope.text.edit` ## **Types and API Signatures** To improve the type safety of our APIs, we have moved away from using the `DesignBlockType` enum and replaced with a set of types. Those changes have affected the following APIs: * `CESDK.engine.block.create()` * `CESDK.engine.block.createFill()` * `CESDK.engine.block.createEffect()` * `CESDK.engine.block.createBlur()` * `CESDK.engine.block.findByType()` * `CESDK.engine.block.getType()` **Note** The `create`, `findByType`, and `getType` APIs will no longer accept the IDs of the deprecated legacy blocks and will throw an error when those are passed ## **Code Examples** This section will show some code examples of the breaking changes and how it would look like after migrating. ``` /** Block Creation */ // Creating an Image before migration const image = cesdk.engine.block.create('image'); cesdk.engine.block.setString( image, 'image/imageFileURI', 'https://domain.com/link-to-image.jpg' ); // Creating an Image after migration const block = cesdk.engine.block.create('graphic'); const rectShape = cesdk.engine.block.createShape('rect'); const imageFill = cesdk.engine.block.createFill('image'); cesdk.engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://domain.com/link-to-image.jpg' ); cesdk.engine.block.setShape(block, rectShape); cesdk.engine.block.setFill(block, imageFill); cesdk.engine.block.setKind(block, 'image'); // Creating a star shape before migration const star = cesdk.engine.block.create('shapes/star'); cesdk.engine.block.setInt(star, 'shapes/star/points', 8); // Creating a star shape after migration const block = cesdk.engine.block.create('graphic'); const starShape = cesdk.engine.block.createShape('star'); const colorFill = cesdk.engine.block.createFill('color'); cesdk.engine.block.setInt(starShape, 'shape/star/points', 8); cesdk.engine.block.setShape(block, starShape); cesdk.engine.block.setFill(block, colorFill); cesdk.engine.block.setKind(block, 'shape'); // Creating a sticker before migration const sticker = cesdk.engine.block.create('sticker'); cesdk.engine.setString( sticker, 'sticker/imageFileURI', 'https://domain.com/link-to-sticker.png' ); // Creating a sticker after migration const block = cesdk.engine.block.create('graphic'); const rectShape = cesdk.engine.block.createShape('rect'); const imageFill = cesdk.engine.block.createFill('image'); cesdk.engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://domain.com/link-to-sticker.png' ); cesdk.engine.block.setShape(block, rectShape); cesdk.engine.block.setFill(block, imageFill); cesdk.engine.block.setKind(block, 'sticker'); /** Block Creation */ ``` ``` /** Block Exploration */ // Query all images in the scene before migration const images = cesdk.engine.block.findByType('image'); // Query all images in the scene after migration const images = cesdk.engine.block.findByType('graphic').filter((block) => { const fill = cesdk.engine.block.getFill(block); return ( cesdk.engine.block.isValid(fill) && cesdk.engine.block.getType(fill) === '//ly.img.ubq/fill/image' ); }); // Query all stickers in the scene before migration const stickers = cesdk.engine.block.findByType('sticker'); // Query all stickers in the scene after migration const stickers = cesdk.engine.block.findByKind('sticker'); // Query all Polygon shapes in the scene before migration const polygons = cesdk.engine.block.findByType('shapes/polygon'); // Query all Polygon shapes in the scene after migration const polygons = cesdk.engine.block.findByType('graphic').filter((block) => { const shape = cesdk.engine.block.getShape(block); return ( cesdk.engine.block.isValid(shape) && cesdk.engine.block.getType(shape) === '//ly.img.ubq/shape/polygon' ); }); /** Block Exploration */ ``` [ Previous Migrating to v1.13 ](/docs/cesdk/introduction/migration_1_13/)[ Next Migrating to v1.32 ](/docs/cesdk/introduction/migration_1_32/) How to Write Custom Components - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/api/registerComponents/ CESDK/CE.SDK/Web Editor/Customization/API Reference # How to Write Custom Components Learn how to write or overwrite and register custom components The CE.SDK web editor provides a set of built-in components that you can reference and render in a specific order. You can see a list of all available components in the respective documentation, including [Dock](/docs/cesdk/ui/customization/api/dock/), [Canvas Menu](/docs/cesdk/ui/customization/api/canvasMenu/), [Inspector Bar](/docs/cesdk/ui/customization/api/inspectorBar/), [Navigation Bar](/docs/cesdk/ui/customization/api/navigationBar/) and [Canvas Bar](/docs/cesdk/ui/customization/api/canvasBar/). Additionally, you can write your own custom components, register them, and use them just like the built-in components. This allows you to extend the functionality of the CE.SDK web editor with custom logic and create a more personalized experience for your users. ## Registering a Custom Component The entry point for writing a custom component is the method `registerComponent`. For a given identifier, you add a function that will be called repeatedly to render this component. Several arguments are passed to this function that can be used to access the current state of the `engine` and render an UI with the help of the `builder`. ``` cesdk.ui.registerComponent('myCustomComponent', ({ builder: { Button }, engine }) => { const selectedIds = engine.block.findAllSelected(); if (selectedIds.length !== 1) return; Button('button-id', { label: 'My Button', onClick: () => { console.log('Button clicked'); } }); }); ``` ### When is a Component Rendered? It is important concept to understand when a registered component re-renders after its initial rendering. The component assumes that all its state is stored in the `engine` or the local state. Whenever we detect a change that is relevant for this component it will re-render. ### Using the Engine In the above example, we query for all selected blocks and based on the result we render a button or not. The engine detects the call to `findAllSelected` and now knows that if the selection changes, it needs to re-render this component. This works with all methods provided by the engine. ### Using Local State Besides the `engine`, the render function also receives a `state` argument to manage local state. This can be used to control input components or store state in general. When the state changes, the component will be re-rendered as well. The `state` argument is a function that is called with a unique ID for the state. Any subsequent call to the state within this registered component will return the same state. The second optional argument is the default value for this state. If the state has not been set yet, it will return this value. Without this argument, you'll need to handle `undefined` values manually. The return value of the state call is an object with `value` and `setValue` properties, which can be used to get and set the state. Since these property names match those used by input components, the object can be directly spread into the component options. ``` cesdk.ui.registerComponent('counter', ({ builder, state }) => { const { value, setValue } = state('counter', 0); builder.Button('counter-button', { label: `${value} clicks`, onClick: () => { const pressed = value + 1; setValue(pressed); } }); }); ``` ## Passing Data to a Custom Component If you add a component to an order, you can either pass a string representing the component or an object with the id and additional data. This data can be accessed in the component function with inside the `payload` property. ``` cesdk.ui.registerComponent( 'myDockEnry.dock', ({ builder: { Button }, engine, payload }) => { const { label } = payload; console.log("Label", label); } ); cesdk.ui.setDockOrder([ { id: 'myDockEnry.dock', label: "Custom Label" } ]); ``` ## Using the Builder The `builder` object passed to the render function provides a set of methods to create UI elements. Calling this method will add a builder component to the user interface. This includes buttons, dropdowns, text, etc. ### Available Builder components Not every location supports every builder component yet. It will be extended over time. If you are unsure, you can always check the documentation of the respective component. Builder Component Description Properties Available For Builder Component `builder.Button` Description A simple button to react on a user click. Properties **label**: The label inside the button. If it is a i18n key, it will be used for translation. **onClick**: Executed when the button is clicked. **variant**: The variant of the button. `regular` is the default variant, `plain` is a variant without any background or border. **color**: The color of the button. `accent` is the accent color to stand out, `danger` is a red color. **icon**: The icon of the button. **isActive**: A boolean to indicate that the button is active. **isSelected**: A boolean to indicate that the button is selected. **isDisabled**: A boolean to indicate that the button is disabled. **isLoading**: A boolean to indicate that the button is in a loading state. **loadingProgress**: A number between 0 and 1 to indicate the progress of a loading button. **tooltip**: A tooltip displayed upon hover. If it is a i18n key, it will be used for translation. Available For [Dock](/docs/cesdk/ui/customization/api/dock/), [Canvas Menu](/docs/cesdk/ui/customization/api/canvasMenu/), [Inspector Bar](/docs/cesdk/ui/customization/api/inspectorBar/), [Navigation Bar](/docs/cesdk/ui/customization/api/navigationBar/) and [Canvas Bar](/docs/cesdk/ui/customization/api/canvasBar/) Builder Component `builder.ButtonGroup` Description Grouping of multiple buttons in a single segmented control. Properties **children**: A function executed to render grouped buttons. Only the Button and Dropdown builder components are allowed to be rendered inside this function. Available For [Dock](/docs/cesdk/ui/customization/api/dock/), [Canvas Menu](/docs/cesdk/ui/customization/api/canvasMenu/), [Inspector Bar](/docs/cesdk/ui/customization/api/inspectorBar/), [Navigation Bar](/docs/cesdk/ui/customization/api/navigationBar/) and [Canvas Bar](/docs/cesdk/ui/customization/api/canvasBar/) Builder Component `builder.Dropdown` Description A button that opens a dropdown with additional content when the user clicks on it. Properties The same properties as for `builder.Button`, but instead of `onClick` it provides: **children**: A function executed to render the content of the dropdown. Every builder component called in this children function, will be rendered in the dropdown Available For [Canvas Menu](/docs/cesdk/ui/customization/api/canvasMenu/), [Inspector Bar](/docs/cesdk/ui/customization/api/inspectorBar/) and [Navigation Bar](/docs/cesdk/ui/customization/api/navigationBar/) Builder Component `builder.Heading` Description Renders its content as heading to the navigation bar. Properties **content**: The content of the header as a string Available For [Navigation Bar](/docs/cesdk/ui/customization/api/navigationBar/) Builder Component `builder.Separator` Description Adds a small space (invisible `
` element) in the canvas menu to help the visual separation of entries. Separators follow some special layouting rules: \- If 2 or more separators end up next to each other (e.g. due to other components not rendering), **only 1** separator will be rendered. \- Separators that end up being the first or last element in the canvas menu will **not** be rendered. \- Separators directly adjacent _to the top side_ of a spacer (see below) will **not** be rendered. Properties \- Available For [Dock](/docs/cesdk/ui/customization/api/dock/), [Canvas Menu](/docs/cesdk/ui/customization/api/canvasMenu/), [Inspector Bar](/docs/cesdk/ui/customization/api/inspectorBar/), [Navigation Bar](/docs/cesdk/ui/customization/api/navigationBar/) and [Canvas Bar](/docs/cesdk/ui/customization/api/canvasBar/) [ Previous Canvas Bar ](/docs/cesdk/ui/customization/api/canvasBar/)[ Next Custom Panels ](/docs/cesdk/ui/customization/api/customPanel/) One-Click Quick Action Plugins - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/guides/oneClickQuickActionPlugins/ CESDK/CE.SDK/Web Editor/Customization/Guides # One-Click Quick Action Plugins Learn how to add one-click quick actions from plugins to the web editor Adding new functionality to the web editor is easy with plugins provided by external packages. Some of these are simple to integrate and just require a single button to be added to the editor. We call them "quick actions." You just need to install the NPM package and add the plugin to the editor. The business logic and button components are automatically registered, and all that’s left to do is to add the one-click quick actions to the editor. ## Quick Action: Background Removal The background removal plugin is a great example of a one-click quick action. It allows you to remove the background of an image with a single click. After adding the plugin, you won't see any immediate change to the editor. You will need to define where to place this feature. ``` import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web'; [...] // Adding this plugin will automatically register user interface components cesdk.addPlugin(BackgroundRemovalPlugin()); // Prepend it to the canvas menu ... cesdk.ui.setCanvasMenuOrder([ 'ly.img.background-removal.canvasMenu' ...cesdk.ui.getCanvasMenuOrder(), ]); // ... or the inspector bar cesdk.ui.setInspectorBar([ 'ly.img.background-removal.inspectorBar' ...cesdk.ui.getInspectorBar(), ]); ``` ## Quick Action: Vectorizer For the vectorizer plugin, the process is exactly the same. Simply install and add the plugin, then use the component Ids to extend the user interface. ``` import VectorizerPlugin from '@imgly/plugin-vectorizer-web'; [...] // Adding this plugin will automatically register user interface components cesdk.addPlugin(VectorizerPlugin()); // Prepend it to the canvas menu ... cesdk.ui.setCanvasMenuOrder([ 'ly.img.vectorizer.canvasMenu' ...cesdk.ui.getCanvasMenuOrder(), ]); // ... or the inspector bar cesdk.ui.setInspectorBar([ 'ly.img.vectorizer.inspectorBar' ...cesdk.ui.getInspectorBar(), ]); ``` [ Previous Adding New Buttons ](/docs/cesdk/ui/customization/guides/addingNewButtons/)[ Next Creating A Custom Panel ](/docs/cesdk/ui/customization/guides/creatingCustomPanel/) Shapes - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-shapes/?platform=web&language=javascript # Shapes In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a `graphic` block's shape through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Shape To create a shape simply use `createShape(type: string): number`. ``` const star = engine.block.createShape('star'); ``` ``` createShape(type: ShapeType): DesignBlockId ``` Create a new shape, fails if type is unknown. * `type`: The type of the shape object that shall be created. * Returns The created shape's handle. ``` const star = engine.block.createShape('star'); ``` We currently support the following shape types: * `'//ly.img.ubq/shape/rect'` * `'//ly.img.ubq/shape/line'` * `'//ly.img.ubq/shape/ellipse'` * `'//ly.img.ubq/shape/polygon'` * `'//ly.img.ubq/shape/star'` * `'//ly.img.ubq/shape/vector_path'` Note: short types are also accepted, e.g. `'rect'` instead of `'//ly.img.ubq/shape/rect'`. ``` const star = engine.block.createShape('star'); ``` ## Functions You can configure shapes just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` engine.block.setInt(star, 'shape/star/points', 6); ``` ``` getShape(id: DesignBlockId): DesignBlockId ``` Returns the block containing the shape properties of the given block. * `id`: The block whose shape block should be returned. * Returns The block that currently defines the given block's shape. ``` const previousShape = engine.block.getShape(block); ``` ## Destroying and Replacing Shapes Remember to first destroy the previous shape if you don't need it any more before changing the shape of a design block. ``` engine.block.destroy(previousShape); ``` ``` setShape(id: DesignBlockId, shape: DesignBlockId): void ``` Sets the block containing the shape properties of the given block. Note that the previous shape block is not destroyed automatically. The new shape is disconnected from its previously attached block. Required scope: 'shape/change' * `id`: The block whose shape should be changed. * `fill`: The new shape. ``` engine.block.setShape(block, star); ``` ``` supportsShape(id: DesignBlockId): boolean ``` Query if the given block has a shape property. * `id`: The block to query. * Returns true, if the block has a shape property, an error otherwise. ``` engine.block.supportsShape(block); ``` Hierarchies - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-hierarchies/?platform=web&language=javascript # Hierarchies In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify the hierarchy of blocks through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Manipulate the hierarchy of blocks Only blocks that are direct or indirect children of a `page` block are rendered. Scenes without any `page` child may not be properly displayed by the CE.SDK editor. ``` getParent(id: DesignBlockId): DesignBlockId | null ``` Query a block's parent. * `id`: The block to query. * Returns The parent's handle or null if the block has no parent. ``` const parent = engine.block.getParent(block); ``` ``` getChildren(id: DesignBlockId): DesignBlockId[] ``` Get all children of the given block. Children are sorted in their rendering order: Last child is rendered in front of other children. * `id`: The block to query. * Returns A list of block ids. ``` const childIds = engine.block.getChildren(block); ``` ``` insertChild(parent: DesignBlockId, child: DesignBlockId, index: number): void ``` Insert a new or existing child at a certain position in the parent's children. Required scope: 'editor/add' * `parent`: The block whose children should be updated. * `child`: The child to insert. Can be an existing child of `parent`. * `index`: The index to insert or move to. ``` engine.block.insertChild(page, block, 0); ``` ``` appendChild(parent: DesignBlockId, child: DesignBlockId): void ``` Appends a new or existing child to a block's children. Required scope: 'editor/add' * `parent`: The block whose children should be updated. * `child`: The child to insert. Can be an existing child of `parent`. ``` engine.block.appendChild(parent, block); ``` When adding a block to a new parent, it is automatically removed from its previous parent. Web Editor Solutions - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/ CESDK/CE.SDK/Web Editor/Solutions # Web Editor Solutions Get to know all the available web editor solutions. In this section, you will find all the available web editor solutions. Check out all the solutions to find what fits your use case best. * [**Design Editor**](/docs/cesdk/ui/solutions/design-editor/javascript/) Built to support versatile editing capabilities for a broad range of design applications. * [**Video Editor**](/docs/cesdk/ui/solutions/video-editor/javascript/) Built to edit videos. * [**Photo Editor**](/docs/cesdk/ui/solutions/photo-editor/javascript/) Built to edit photos. [ Previous Editing Video ](/docs/cesdk/ui/guides/edit/)[ Next Javascript ](/docs/cesdk/ui/solutions/design-editor/javascript/) Vue.js Creative Editor - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/design-editor/vuejs/ CESDK/CE.SDK/Web Editor/Solutions/Design Editor # Vue.js Creative Editor The CreativeEditor SDK delivers a powerful Vue.js library designed for crafting and editing rich visual designs directly within the browser. This CE.SDK configuration is fully customizable and extendable, offering a comprehensive range of design editing capabilities, including templating, layout management, asset integration, and more, as well as advanced features like background removal. [Explore Demo](https://img.ly/showcases/cesdk/default-ui/web) ## Key Features of the Vue.js Creative Editor SDK ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transformations Execute operations like cropping, rotating, and resizing design components. ![images](/docs/cesdk/static/Templating-5fc837e60dafb163c824ed6684fc7b0c.png) ### Templating Build and apply design templates with placeholders and text variables for dynamic content. ![images](/docs/cesdk/static/Placeholders-5b1a42097f1b9d363d48c30ca90fbcc2.png) ### Placeholders & Lockable Design Constrain templates to guide your users’ design and ensure brand consistency. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Handling Import and manage images, shapes, and other assets for your design projects. ![images](/docs/cesdk/static/VideoCollage-ed31587fdccad3fe7f335c6e70216355.png) ### Design Collages Arrange multiple elements on a single canvas to create intricate layouts. ![images](/docs/cesdk/static/TextEditing-2529ca84ae5a97dd9bd3da74d0b86e85.png) ### Text Editing Incorporate and style text blocks using various fonts, colors, and effects. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Operations All design editing tasks are performed directly in the browser, eliminating the need for server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit designs within your React application using the engine API. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Easily enhance functionality with plugins and custom scripts. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Develop and integrate custom UIs tailored to your application’s design requirements. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Background Removal This plugin simplifies the process of removing backgrounds from images entirely within the browser. ![images](/docs/cesdk/static/CutoutLines-fae102d8e0218cdf5070f7b344fad81e.png) ### Print Optimization Ideal for web-to-print scenarios, supporting spot colors and cut-outs. ## Browser Support The CE.SDK Design Editor is optimized for use in modern web browsers, ensuring compatibility with the latest versions of Chrome, Firefox, Edge, and Safari. See the full list of [supported browsers here](https://img.ly/docs/cesdk/faq/browser-support/). ## Prerequisites [Install the latest stable version of **Node.js & NPM**](https://www.npmjs.com/get-npm) ## Supported Formats Creative Editor SDK supports loading, editing, and saving a variety of image formats directly in the browser, with no need for server-side dependencies. Supported formats include: * JPG * PNG * SVG * WEBP You can export individual assets or entire designs as PDF, JPG, PNG, or SVG files. ## Understanding CE.SDK Architecture & API The following sections provide an overview of the key components of the CE.SDK design editor UI and its API architecture. If you're ready to start integrating CE.SDK into your Vue.js application, refer to our [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/) or explore the Essential Guides. ### CreativeEditor Design UI The CE.SDK design UI is designed for intuitive design creation and editing. Key components and customizable elements within the UI include: ![](/docs/cesdk/f6c8e768b60bb12f5dc766f8ca63ca62/CESDK-UI.png) * **Canvas:** The main interaction area for design content. * **Dock:** An entry point for interactions not directly related to the selected design block, often used for accessing asset libraries. * **Canvas Menu:** Access block-specific settings like duplication or deletion. * **Inspector Bar:** Manage block-specific functionalities, such as adjusting properties of the selected block. * **Navigation Bar:** Handles global scene actions like undo/redo and zoom. * **Canvas Bar:** Provides tools for managing the overall canvas, such as adding pages or controlling zoom. * **Layer Panel:** Manage the stacking order and visibility of design elements. Learn more about interacting with and manipulating design controls in our design editor UI guide. ### CreativeEngine The CreativeEngine is the core of CE.SDK, responsible for rendering and manipulating design scenes. It can operate in headless mode or be integrated with the CreativeEditor UI. Key features and APIs provided by CreativeEngine include: * **Scene Management:** Create, load, save, and modify design scenes programmatically. * **Block Manipulation:** Create and manage design elements, such as shapes, text, and images. * **Asset Management:** Load assets like images and SVGs from URLs or local sources. * **Variable Management:** Define and manipulate variables within scenes for dynamic content. * **Event Handling:** Subscribe to events like block creation or updates for dynamic interaction. ## API Overview CE.SDK’s APIs are categorized into several groups, reflecting different aspects of scene management and manipulation. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/): * **Creating Blocks**: ``` const block = engine.block.create('shapes/rectangle'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) Variables allow dynamic content within scenes, enabling programmatic creation of design variations. * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API:**](https://img.ly/docs/cesdk/engine/api/events/): * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ## Customizing the Vue.js Creative Editor CE.SDK provides extensive customization options to tailor the UI to various use cases. These options range from simple configuration changes to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object:** When initializing the CreativeEditor, pass a configuration object that defines basic settings such as the base URL for assets, language, theme, and license key. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization:** Customize the language and labels used in the editor to support different locales. ``` const config = { i18n: { en: { variables: { my_custom_variable: { label: 'Custom Label' } } } } }; ``` * [Custom Asset Sources](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): Serve custom image or SVG assets from a remote URL. ### UI Customization Options * **Theme:** Select from predefined themes like 'dark' or 'light'. ``` const config = { theme: 'dark' }; ``` * **UI Components:** Enable or disable specific UI components as needed. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations Explore more ways to extend editor functionality and customize its UI to your specific needs by consulting our detailed [customization guide](https://img.ly/docs/cesdk/ui/customization/guides/). Below is an overview of the available APIs and components. ### Order APIs The Order APIs manage the customization of the web editor's components and their order within these locations, allowing the addition, removal, or reordering of elements. Each location has its own Order API, such as `setDockOrder`, `setCanvasMenuOrder`, `setInspectorBarOrder`, `setNavigationBarOrder`, and `setCanvasBarOrder`. ### Layout Components CE.SDK offers special components for layout control, such as `ly.img.separator` for separating groups of components and `ly.img.spacer` for adding space between components. ### Registration of New Components Custom components can be registered and integrated into the web editor using builder components like buttons, dropdowns, and inputs. These components can replace default ones or introduce new functionalities, seamlessly integrating custom logic into the editor. ### Feature API The Feature API allows for the conditional display and functionality of components based on the current context, enabling dynamic customization. For instance, certain buttons can be hidden for specific block types. ## Plugins Customizing the CE.SDK web editor during its initialization is possible through the APIs outlined above. For many use cases, this will be sufficient. However, there are situations where encapsulating functionality for reuse is necessary. Plugins come in handy here. Follow our [guide on building your own plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) to learn more or explore some of the plugins we've created using this API: * **Background Removal**: Adds a button to the canvas menu to remove image backgrounds. * **Vectorizer**: Adds a button to the canvas menu to quickly vectorize a graphic. ## Framework Support CreativeEditor SDK’s video editing library is compatible with any Javascript including, React, Angular, Vue.js, Svelte, Blazor, Next.js, Typescript. It is also compatible with desktop and server-side technologies such as electron, PHP, Laravel and Rails. [ Headless ](/docs/cesdk/engine/quickstart/)[ Javascript ](/docs/cesdk/ui/solutions/design-editor/react/)[ React ](/docs/cesdk/ui/solutions/design-editor/vuejs/)[ Angular ](/docs/cesdk/ui/solutions/design-editor/angular/)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,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)![Reuters logo](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAaQAAAB4CAMAAACKGXbnAAABO1BMVEUAAABAQEBAQEBAQEBAQEDsZRhBQUHoWybtZRlAQEBAQEBAQEDtZhj1ayHsaxpAQEBAQEBAQEDsZRlAQEBBQUHsZRnsZRnsZxdAQEBAQEDtZRlAQEDsZRnsZRlAQEDvZh3sZRnrZRnvZxtFRUXsZRnsZRnsZRnsZRlAQEDsZRlAQEDsZRlAQEDsZRlAQEDsZRlKSkpAQEBAQEDrZRrsZRntZRnsZRnsZRnsZRnsZRnsZhnqZhjsZRnsZhlAQEDtZRntZRnsZRnsZRntZRntZRnsZRnsZRjsZRnsZRnrZRntZRnsZRnuZhlBQUHsZRpAQEBAQEBBQUHuYx5AQEBAQEDtZRrtZRpERERAQEBBQUFAQEBBQUFAQEBAQEDsZRlAQEBAQEDsZRlAQEBAQEBAQEBAQEA/Pz9AQEDsZRm03tQYAAAAZ3RSTlMA5yNA3/wIBvqviXAWCAvPpi/39RTk7Bv6GNPw2k47D7A/EhB/8ET0xb2YzHlehIQEn1kl6LduY1lJVSCUK+zfxsGpjok2mGg7Mp54HiooUUk0DeTbLyIcZv3WYLu1pHR+c2uTwKn9oIzn+gAAE9lJREFUeNrs2Utr6lAUBeA1CcmgcRBIAlLxgVFEigPrM0p8P6rcgaBDobP1/3/BPSde09vqLeW2sR3sb3TgDBcr7J0DIYQQQgghAHvrQ/xIw+4eJ4uqN8NJ8WEL8WN0HbIBrUflAMVckEEX4ofIBCS9NZQ+lSKUuUEygvhe807Phral1ody55IjE0qOioNYKbcsQnyDgUGjsINiRiSDJrTm5vkOWuiSrEErjUjjCHFzfYtKG9qqHkQdvJGru5MQ2pRaDuLWBtQKiGXWPi7lETPr1MoQt1amNsEHFKgtIW4tjEhW5/iAlUfSzUPcxnA6Web/HGvuuIIPqbSyCx+xzMPsWXbcVLUdksEc/23ukvQ6EKlZO9QKJv6h2TvXrHEs53HBrFHzmhBpKTNW3eO6nEWvAmUekBzZlyFajLUh0jJlzFpdVGwLrX7eXwfUutBWD6ukeXNSxvGUdRm7P+AVs0Cnc/7zUIfSSqIo6eM4xMldQM3oQ6Ql36K2wWsrklEJQJu0lknlrCKAI7VaBicNaqMMRGr8oxdEMxuJDJS1Qdb1yexOczsohzppPOnqPFIz9ueYB57jFYYQafKbNhLmLDuDUn6MKm8615n1oIQGYy/X/v4AkQLb3+5wRcWg0YRSKuG6kkstaF67C+Wl/ev8mrhOdmnjwsqh4eNdDWoTE8C+3a7YLxGV7y1vIQvTFyl61AY2LiwnXbzP3lTplHXRnqqkMc4ka61BJZJv39cY83JuHk43Ia4pFSt3b2IKd38twjUTsTZPxjbE5/kGTyZI5LNkC1fsI4Pu/BwYzpIxj9YasSxPrBDi84q/2bnTtbSBKAzAhyVA2awiSwGVpYoLCCLuSqmoIBZBpZWCVlxqzv1fQWEmkyEhgFLbPvbx/dU2abbPE85kgijJgWzHguixQ4/TILa5vZ1USjHMfHTK6U0gtUFbdzdKjuHN7/siIBVSVlIOem3IQZyXsCMvxzeL1LaykibeKulFrCEhTAFnPynYQbYacn8+hbYTZJOwCSmDY9Unm4eEQpazvu/NC/gaJBnlB8TInnnPWdjzoAOkEiA5pKV0C5SDRv/p7eHDC5kpuT1Lm0CYp/Z3I+rlKE8FnghSnEn57sbshTyzQf5XcyLoce++DWdfzrkdJFsWjVuUGxHddJXtoxKJs2jhjxq+zBWdpN1QDooc9rfnrC+PzRR59kBpyoOZdVBKCogYSwBE8gLiLC+h8G5uP/EWz5/CxqTBHVD5Ev4GKubN/c+lOQBHiHYQBSDMeVJia3Z4ttSk/sxQTTfrd3c3N3fN6lkU+ps8qzTv7mzxeNx2d9dMV6f1URhN9MMQUeXq45V0vXvH5UnoZbosT1craXKItnraoE/BIJeGpq21vNyK31UMehMoeAtruVAhoohjIeZJsBwKod0d6M8pPd8j3A56O5xQjoxXP67l9gvnMETV+m7MqMsGXD6fKPEFLuatlSho0NuuLwK+R1HmcwWyF42r+zo7wUrN2FGzQa/phpGwkgtnM+qGuIjzhNLtHbtE7nt7x7rGw+INP87Jm5WHxgU5F3aEj65s471t3ARaotX7MZ2LbzBrfFi0GS7ZyntB7Fg6hS6Or19AUkDEnGJR0QtqJ8pRqx8pgW50ivZ8IS8MNO0S+7hYLINaRSf2UwHqvUiNmaDHskjpSAlci8PwjZz133EdmJ9iH775ZrQ3onhW1OIaG6d1FETKfw6ajrDNC8xqzoKZBK2hw8NzzZAiSLC2L5xBagEGson9BZZVp9YMiH2l/2RIVZ3YF68246AtVUHBVKk9in3YpI8fJgGabhFx1qF8EBsLd+JdE4QFKb05pCxO0q0LipBCyHwdISTmQQ9d9BfivwkpVRNHDInz3aSgSyswbIsLyOwDsVlKzkCXmSMhOAfMXgw78uwZxRoQziUk8jxJYqdTV5+QuR0lJMZYBm5R/EchpcWRQ+IeW10p1b8P26J5CZkFef5uyQ6aeEi78nSsE4jVzwLiRN4BRDGGRAna7B5kkiOExM1HgTE1/lFIqYFr3gwOifvO1zS4hse+hswPaPNm+n03onhwcgrgCGKbZQrASf4UMwNlnvu4fgrMBglmn9783MisPz0kl8uVzaqOfxmYso+vGNCpzF8+O6RFcaif0KGvdTWTOhVjWRESX7F9jAFlvQQmQaJI3XVRqxl1LnVIBeWH/LGnz2uNuxZEzxZAMYgoJDsVsz5L3u1SmYnQfuT2KBkGqoQSwfu0kCpRE6sYfWWxxk9BDxS/6VgvQcOzQ4JLPdeUK1LPSWMcA/vpeK+H/nhI95PyvicNrQf+s7UIVFkOz3edZttMXY5X662Vq7HGGXRE/Ej5zfzDpQhqqwK2BSMApyfJbVo7Gwtr6+qIkkv+H/be9/dZrT4tpGnoVn7/2NPixllsJvj9kNQMosQAPdJsx+PwpJCs0C1alyss+4GfCZFNp0Atyg7uS45+ItlZiz3rTgCVn/i0DVSClsIO9LIfLLCsIiTiT+qUihlssxzNjBCSYsEVSO5FqgF/OaQbNnKLPickrhxQDeauWR0ZYBD7ZilU2nSAxPyNXcuCgOiOdPfYmYj2QMqyCcRmn/7Au74fyocBRgzJVOOXRnX5/25I/L8ZYbSQoOlTLpnnZzKSo+536UiNHIAGP2+9DxSzu5GkfykZAW7UkKAlUoFxoN6xhu9vh2T93ZCiOtaIKEN6gNGELeTXNVA7SX9u3dHnkblljg18iSNaoTlsy52/QEgVkXJVX31IMKZM5Yr9/F3CaLZDpT2QzZyDppmT0iZQe6Q3nNjq7hsLLxCS4T8K6Up56Ctyi5+CJ7IXT+F3bGUQYxvsbkmU3kIaFFJdHt/+1ENf3mL41AFUwiPMlpyg5pjKJ8zwJN6tqR3ll9EOpHo73N5aNY8WEm98p199SCl2u7sGYjIgMrWbD6DJe+RBtITCwB83JEBtaoL86yB2jaqKYZtAN729hIix/OooIfFLk9W/+pAuWSiL8kFyjVYZeh1nkBBueQe9q9nmrcEA5lBmF3p8FBCFDWnEhURmb6QWXMeudkoV0th4r/LfCOlCa8epQSHx/VJ3QI2T1BjXVTMFSt/8KBG2aDvHRznOrcQhUCfCkC/AHmsPob6ub9BQNiZQ4rc/P6RJeQbtHnhIfX2v//mQtK0MD6nCIvGdKUdOnHF5WpFTEmWfHdK8RWYViJAFY5vy18hKDhjg/BOf0Ju7DYNKxI+y5HNDMhnmH/monIfUn/VfhTQ/LCT9vUvsPSareq7C91DpiimIMsu3TvWchA5Ou94ldp/D0ziPHSxPjWd0h8jlnhDS9cr9vdXaisdb1pWHgCh7Z3p1ITXYqcSXF69rouzRAJyBLeB09+MsJwtyW6CQZC/UPRl/hdVyCArbyLlHn0/KnsGrC6mfa+hW1piczVo/APGrvTNtSxsI4viAhaQWUhsLYuSUS6RUUQ6pHEIBsfUAL+pRWnvu9/8EbZbuLjm4UrHHk9+LPm2JAvvP7M7Mzk7iw0Uq4OoUAabER3o7YAKXTvKfhIphkRZP4L8R6bYLSuztBYu60GHlHTamJKJEd0EB17OhTIh6BqswGUu4rdchYFJSJXHMWqRgskZFKm/BhCK9/FMifZlUpLVl0LB59O6LR1MLoXQcEhyocBwIzAePOmAEzqJU5ADTcomZnYFZrhIAcFYRZcegSO+7oCOS5bGWtPUBRPI81rIwN5lIK3js9eh+uvZods9XaRmK2MK61KvZnABqOBdCMRjBW3m3lmh7QayuRn37UoYakmBEJM/tiR0w/1owq+XssxWGY59LLw5O8R15RCWEcfe7FGNvr+cENfuZJAcj6OnOYzW21vn6KolVJxgRaXEL4H8R6YsdRmNdX1RVVjj290TeWzwe6LgqpkCDQ62Rxn0T87pbHUgKgMxuJOmqZHcEmESkm5OfteBPflZaYy1IquFeRWKpg/BMRfrSJkXjZbrQwDg6t2xq7IDM6dKhQxk2FQGz9PbN6xBMBOd7vaNjI/mkCzsOGMHhECbMgltpruGaVjcYESlNMpfhEa/NVqS0YuMSc9aFcTxfUJSwKpCISDRlFM/BdJwGYBCVctOKBDdX5JtZDYg0z4o+NNxSK3sgkcK3LEQYr9KaoqpImyWK+wYKsTKHgHFuVJsO0GXV4aQSbbhUrcN/UyQ7/WYLBkSiezUdUNO9plHlA4kEHY8ybTfZR/8KKhwJ2R2rA6aCMD4WpBY53WYoXletNLAKxQ/uUSTo0ISkf2qR2Ghrl4E2rQd5MJHgqyK9NZq2Z+hNdJpqkmWIisSMrCqAlg0SDNHCL999isRqS9fsU4sUviJzWniYT3HVfjiRuhZWBz2OG9UM0qplbK5mCRg6010rPqRI2D1YyHCZQSi5CxiukL+gyaKsm/duHBoRKXyu2IGYqqQL1oZ5VH4P88CNlXRtTiESK9pjLuUY1pULWKw/zO4YUFixD0+FCTXzAujAK9pMNnqkJZezZqPOfC6OZLwlAyLBu2/ED7NOe0Mzj8qjNJijMzrzABgqjrSEDYhkv2Y+22i6jxRGdyxqk3dC/89cM6JeX5yR5N4+h/+WatYbpOCf1w7/jk0OuQTFlp/kNCDSJvUd0tpLr6wTflfPiwFv5Kas2DEwVGY8Z0Ak8C9qE/rhm3anq5Lc/5Ta3BHe2FYfHjqMJJJB34jGdjbZurgiPm3eP8bH10FDnZ5P4hKIUDAgEvivSGB3BIQTGr9vdaxqwnpJjKvr+XbXDvbluQ+3VywjuDmdSGyevL7pHFlVLI8TyU59h4XBj7h4/vR2Yf7Fu632nN/vP3nydeW7stCrighBkDlwkR7FVLPEXQAA2CkWScCdHn7idQAI+VwJtMTo5pEjgwhvjYjEStPW6JBaPaMSmB1tbY4+5SOYUqTw9aiN+/YYkcBqob97ory655neITJhD2HE/GBHLmkbgJ0HkxwkgxSPAaW0DYNwTR65CuyHMBFDIlkfkUE4obfkyMHfAsozy6gh2IJpRYL16Q6RpYetkk/tk4j0mWVBB6oYd3gqGdOMdRXM4gtZ3NuimhQ1jyE7jAl98/MiQsqQSMwfvX5OF/AJRYJOebhGT2B6keamO46ZHmqJT8aKxAKqV4gQomEP2+Yu2BCGFHtfVm22PQcAbEeVfddiPCmx08DOTscDxkSyrxFTmmd5kwlFAuuwK8ttMCAS3BoSSXv68uz5OJGu0nYgrjLC2GrKs/5e2p8TkyeXtxpCX76KGK8eA8HHDw9jD6sIE42AMZHgZFHjO/gtE4oEz3VPd6+kj8CQSEdPDYqkLoZcH7NBc/7JDoTVphzFiEXWLxfTAzb76Y7/aekSMPT4mPeUliXnFYHrboKX7Sg/ab6qCyo2P7NJmmB9P8x5+KY2keWP16pry6x7xwgPzg96dB97xt8d50QJrchl5Q1n30qvWa60Er1QuOXcRS6SL7GDLBjXgaIVh7Qk/2P3FIaye7dPRONqCEkKlSD0NvJqCcYQnl/A6Owudz+v9zkZUM7/8f21pbyysiizslK2nP10ZdMft56BBnvnyfrauaVcLlserX39OPccRrD5bh3zIQz6HL14rHrjRz/feP5mzs5sP70go2euJ+t95pcH+im1P80v3D49PzuzWB6d36Y/dcIwgtVgXNYoRh/TTHocC/UK770TNNm+uwNQccyTqGv2LFufdfwyz55Zu+FxvaXkWMYO90KYvHFHfuNNuB/s4eXnE33AVi7iWwVCrIpQIgTAZVk3DYaQQLTOlYlkwyKZzADhInQMDGfsrp8hFQTiGZCWt9u1ivRWoN7fHijh3gxMd9uhhtkU/N5oSCKK9qiTHEiICMUjdICbzIPYlXC0RB14CVQ4CzvkLHsvauMl82nbxtGeJmIda06zqkTOa5Z+e8seJxfwkk6EugQqJMNuch/sKTvLFHiE8XKAIe1WM0sAbwa88uNiMO8EynYvWNJpg5c8BZPfZwmR8VRmCaI0GNrAsc4+fY0/0PktLpx0JXA0IXQMJr/PhTLTAHeaXSYhVZWCBZwb8pK6ZDWluOIwn+Bije5Mfh8uriyovxBHNHtcSrgzRQ50qNlQzalp249cZmfw34d5bzYS9OT6hhST7eMu0lCHvALoEvC9ciofYoZ5DSb3gaOKNapzdNH3Rt2JA1kufoKnTXAboqq3AFvIbEGzQfg9EdgP4hbfFOEYT1INnqTBR9ESkfgKtDTqwZ75vNl7hBO44btBwWE/tHuMXXEXcjfA5OFYfZULOVlFCXMoHMW9nspRS/F8Dy8/zQIwQrlXDjCZIT65g+feEt3Oo+WPjqTsqW1juV7v1XEWVr72EpRsV0WEpAKYzIwLt8IjC9Rs5IhznjULypIrkjoiBZFMxrSlGcEWITfNEuxkqzkHfoWtTjyJe31usQ5KLqOk7MRkVryhhSlqYtih3ifn1oNDziA1aAGXCWMmluQqgYamDaEe9+sxJMkS6LMkmpY0U1hXqDrocJBq0bhqXPZCMlPfMyTklVvgOUk3h1WYkMAlB5jDoA0hqQUmM4RrxMju952I+A2YBGddRNE8mfFiZmT7YMT4iReXFC6BMB/N/PAUidM9nsSv7XaTh6Y+KNL2jo6dhFKHgKmaIj08rEcr7wOZgguJEVDC1aOo4mNdDEVzKfoD+CS3N8UeBuwOgUwrm431RbTRHXeuGEXuPJj8ATjHKmBOWSc0h5tk5nzYfIhLd2Hukv9pqjQFHqNy7bpxHYTJ38Jl1VYpgMyhDSEeywW+CgrugsnfAy1VyO0lUyw9ZPKXYhbhm5iYmJiYmJiYmEzPD/42lUm7sBpAAAAAAElFTkSuQmCC)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous Angular ](/docs/cesdk/ui/solutions/design-editor/angular/)[ Next Javascript ](/docs/cesdk/ui/solutions/video-editor/javascript/) Adding Translations to the CreativeEditor SDK - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/guides/i18n/?platform=web&language=js # Adding Translations to the CreativeEditor SDK In this example, we will show you how to configure the [CreativeEditor SDK](https://img.ly/products/creative-sdk). CE.SDK has full i18n support and allows overwriting and extending all strings to any valid language. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-i18n?title=IMG.LY%27s+CE.SDK%3A+Configure+I18n&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-i18n). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-i18n?title=IMG.LY%27s+CE.SDK%3A+Configure+I18n&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-i18n) ## I18N Configuration The CE.SDK comes with a set of configuration parameters which let you translate any label in the user interface. ``` const config = { license: 'mtLT-_GJwMhE7LDnO8KKEma7qSuzWuDxiKuQcxHKmz3fjaXWY2lT3o3Z2VdL5twm', userId: 'guides-user', locale: 'fr', i18n: { fr: { 'common.back': 'Retour', 'meta.currentLanguage': 'Français' }, it: { 'common.back': 'Indietro', 'meta.currentLanguage': 'Italiano' } }, ui: { elements: { navigation: { action: { back: true // Enable 'Back' button to show translation label. } }, panels: { settings: true // Enable Settings panel for switching languages. } } }, callbacks: { onUpload: 'local' } // Enable local uploads in Asset Library. }; ``` * `locale: string` defines the default language of the user interface. This can later be changed in the editor customization panel. The CE.SDK currently ships with locales for English `en` and German `de`. Additional languages will be available in the future. Until then, or if you want to provide custom wording and translations, you can provide your own translations under the `i18n` configuration option, as described in the next paragraph. ``` locale: 'fr', ``` * `i18n: Object` is a map of strings that define the translations for multiple locales. Since the CE.SDK supports maintaining multiple locales at the same time, the first key in the map must always be the locale the translation is targeting. In this example, we change the back-button label for the locales `'fr'` and `'it'`. Please note that the fallback locale is `'en'`. So, with this configuration, the complete user interface would be English except for the back-button. Also, if you want to change a single label for one of our shipped translations, you do not have to add all keys, but only the one you want to override. A detailed overview of all available keys can be found in form of predefined language files inside `assets/i18n`. You can download the English version from our CDN here: [en.json](https://cdn.img.ly/packages/imgly/cesdk-js/1.43.0/assets/i18n/en.json) ``` i18n: { fr: { 'common.back': 'Retour', 'meta.currentLanguage': 'Français' }, it: { 'common.back': 'Indietro', 'meta.currentLanguage': 'Italiano' } ``` * Under the special key `meta.currentLanguage`, you should provide a short label for your provided language. This will be used to display the language in the language select dropdown of the editor customization panel. ``` 'meta.currentLanguage': 'Français' ``` Adding Templates - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/guides/custom-template-source/?platform=web&language=js # Adding Templates You can use the asset source system to offer a series of templates, that act as a quick start for your users. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-custom-template-source?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Custom+Template+Source&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-custom-template-source). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-custom-template-source?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Custom+Template+Source&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-custom-template-source) ## Demo templates By default, the CE.SDK ships with a predefined set of default templates, that can be added to your installation using the `addDemoAssetSources()` method: ![](/docs/cesdk/6e6dabafbadf8c2855654a8022e685c4/templates-light.png) ``` instance.addDemoAssetSources({ sceneMode: 'Design' }); ``` ## Custom template asset source Templates consist of predefined scenes created using CE.SDK. To create your own template, just design your desired file using CE.SDK and save the scene to disk using the 'Download' button. The scene file can then act as a template for your users. To make the templates you created available to users, you need to create an asset source. This example shows the minimal amount of code you need to write to have your templates appear in the Asset Library. For a more complete description of the different ways you can configure the asset library, take a look at the [Asset API documentation](/docs/cesdk/engine/api/assets/) and the [customize asset library guide](/docs/cesdk/ui/guides/customize-asset-library/). ### Add local asset source Add a local asset source, using an arbitrary id that identifies the asset source. In this case the asset source id is `my-templates`. ``` instance.engine.asset.addLocalSource( 'my-templates', undefined, function applyAsset(asset) { instance.engine.scene.applyTemplateFromURL(asset.meta.uri); } ); ``` ### applyAsset() Assets that the user has chosen from the asset library are passed to the `applyAsset` function. This function determines what to do with the selected asset. In our case we want to apply it to the scene using the [Scene API](/docs/cesdk/engine/api/scene-apply-template/). Note that you can access the entire asset object here. This means you can use it to store and process arbitrary information in the `meta` property. In this example, we are passing a URI to a template file, but you could also pass an internal identifier, a serialized scene string, or additional data to further modify or process the template using the available API methods. ``` function applyAsset(asset) { instance.engine.scene.applyTemplateFromURL(asset.meta.uri); } ``` ### Source Id Every asset source needs a source id. Which one you chose does not really matter, as long as it is unique. ``` 'my-templates', undefined, ``` ### Add template assets to the source Every template asset should have four different properties: * `id: string` needs to be a uniqe identifier. It is mainly used internally and can be chosen arbitrarily. * `label: string` describes the template and is being rendered inside the template library via tooltip and as ARIA label. * `meta.thumbUri: string` pointing to a thumbnail source. Thumbnails are used in the template library shown in the inspector and should ideally have a width of 400px for landscape images. Keep in mind, that a large number of large thumbnails may slow down the library. * `meta.uri: string` provides the scene to load, when selecting the template. This can either be a raw scene string starting with `UBQ1`, an absolute or relative URL pointing at a scene file, or an async function that returns a scene string upon resolve. ``` instance.engine.asset.addAssetToSource('my-templates', { id: 'template1', label: 'Template 1', meta: { uri: `${window.location.protocol}//${window.location.host}/template.scene`, thumbUri: `${window.location.protocol}//${window.location.host}/template_thumb.png` } }); ``` ### UI Config It is not enough to add the asset source to the Creative Engine. You also need to configure how the asset source will appear in the asset library. For more information, see the [asset library guide](/docs/cesdk/ui/guides/customize-asset-library/). In this example we provide the minimal configuration to have the template library we just created appear. ``` instance.ui.addAssetLibraryEntry({ id: 'my-templates-entry', sourceIds: ['my-templates'], sceneMode: 'Design', previewLength: 5, previewBackgroundType: 'cover', gridBackgroundType: 'cover', gridColumns: 3 }); instance.ui.setDockOrder([ { id: 'ly.img.assetLibrary.dock', key: 'my-templates-dock-entry', label: 'My Templates', icon: '@imgly/Template', entries: ['my-templates-entry'] } ]); ``` Javascript Video Editor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/video-editor/javascript/ CESDK/CE.SDK/Web Editor/Solutions/Video Editor # Javascript Video Editor SDK CreativeEditor SDK is a Javascript library for creating and editing videos directly in a browser. This CE.SDK configuration is highly customizable and extendible and offers a well-rounded suite of video editing features such as splitting, cropping and composing clips on a timeline. [Explore Demo](https://img.ly/showcases/cesdk/video-ui/web) ## Key Capabilities ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transform Crop, flip, and rotate video operations. ![images](/docs/cesdk/static/TrimSplit-9f679312338d9c81b5863f540fb13f39.png) ### Trim & Split Set start and end time and split videos. ![images](/docs/cesdk/static/MergeVideos-9f08894a0142a48861b0bf595804d314.png) ### Merge Videos Pick, edit and merge several video clips into a single sequence. ![images](/docs/cesdk/static/VideoCollage-ed31587fdccad3fe7f335c6e70216355.png) ### Video Collage Arrange multiple clips on a single page. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Processing All video editing operations are performed by our engine in the browser. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit videos inside the browser. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Easily add functionality using the plugins & engine API. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Build Custom UIs. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Libraries Add custom assets for filters, stickers, images and videos. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Green Screen Utilize chroma keying for background removal. ![images](/docs/cesdk/static/Templating-5fc837e60dafb163c824ed6684fc7b0c.png) ### Templating Create role based design templates with placeholders and text variables. ## Browser Support Video editing mode relies on modern web codecs, which are currently only available in the latest versions of Google Chrome, Microsoft Edge, or other Chromium-based browsers. ## Supported File Types [IMG.LY](http://img.ly/)'s Creative Editor SDK enables you to load, edit, and save **MP4 files** directly in the browser without server dependencies. The following file types can also be imported for use as assets during video editing: * MP3 * MP4 (with MP3 audio) * M4A * AAC * JPG * PNG * WEBP Individual scenes or assets from the video can be exported as JPG, PNG, Binary, or PDF files. ## Getting Started If you're ready to start integrating CE.SDK into your Vue.js application, check out the CE.SDK [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/). In order to configure the editor for a video editing use case consult our [video editor UI showcase](https://img.ly/showcases/cesdk/video-ui/web) and its [reference implementation](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-video-ui/src/components/case/CaseComponent.jsx). ## Understanding CE.SDK Architecture & API The following sections outline the fundamentals of CE.SDK’s video editor user interface and its technical architecture and APIs. CE.SDK architecture is designed to facilitate the creation, manipulation, and rendering of complex designs. At a high level, it consists of two main components: the CreativeEngine and the CreativeEditor UI. The following is an overview of these components and how they are reflected at the API level. If you are already familiar with CE.SDK and want to get started integrating CE.SDK video editor check out our “Getting Started” guide or jump ahead to the “Essential Guides” section. ### CreativeEditor Video UI CE.SDK’s video UI is designed to facilitate intuitive manipulation and creation of a range of video-based designs. The following are the key locations in the editor UI and extension points for your Ui customizations: ![](/docs/cesdk/7a1d859e523b71cad4257ca45524e689/Simple-Timeline-Mono.png) * _Canvas_: The core interaction area for design content, controlled by the Creative Engine. * _Dock_: The primary entry point for user interactions not directly related to the selected block. It is primarily, though not exclusively, used to open panels with asset libraries or add elements to the canvas. [Learn how to add demo videos in the dock.](https://img.ly/docs/cesdk/ui/guides/video/#add-demo-asset-sources) * _Canvas Menu_: Provides block-specific settings and actions such as deletion or duplication. * _Inspector Bar_: Main location for block-specific functionality. Any action or setting available for the currently selected block that does not appear in the canvas menu will be shown here. * _Navigation Bar_: For actions affecting browser navigation and global scene effects such as zoom or undo/redo. * _Canvas Bar_: For actions affecting the canvas or scene as a whole such as adding pages. This is an alternative place for actions such as zoom or redo/undo. * _Timeline_: The timeline is the main control for video editing. It is here that clips and audio strips can be positioned in time. Learn more about interacting with and manioukating video controls in our [video editor UI guide.](https://img.ly/docs/cesdk/ui/guides/video) ### CreativeEngine The CreativeEngine is the core of CE.SDK, responsible for handling the rendering and manipulation of designs. It can be used independently (headless mode) or integrated with the CreativeEditor UI. The CreativeEngine provides several APIs to interact with and manipulate scenes. A scene is the visual context or environment within the CreativeEditor SDK where blocks (elements) are created, manipulated, and rendered. ### Key Features: 1. **Scene Management**: * Create, load, save, and [m](https://img.ly/docs/cesdk/engine/guides/blocks/)odify scenes. * Control the zoom and camera position within scenes. 2. **Block Manipulation**: * Blocks are the basic building units of a scene, representing elements like shapes, images, and text. * APIs to create, modify, and manage blocks. * Properties of blocks (e.g., color, position, size) can be queried and set using specific methods. 3. **Asset Management**: * Manage assets like images, videos, and fonts. * Load assets from URLs or local sources. 4. **Variable Management**: * Define and manipulate variables within scenes. * Useful for template-based designs where dynamic content is required. 5. **Event Handling**: * Subscribe to events related to block creation, updates, and destruction. * Manage user interactions and state changes. ## API Overview The APIs of CE.SDK are grouped into several categories, reflecting different aspects of scene management and manipulation. [**Scene API :**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API :**](https://img.ly/docs/cesdk/engine/api/block/) * **Creating Blocks**: ``` const block = engine.block.create('shapes/star'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API :**](https://img.ly/docs/cesdk/engine/api/variables/) Variables allow dynamic content within scenes to programmatically create variations of a design. * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API :**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source) * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API :**](https://img.ly/docs/cesdk/engine/api/events/) * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ### Example Usage Here is a simple example of initializing the CreativeEngine and creating a video scene with a text block: ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/index.js'; const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets' }; CreativeEngine.init(config).then(async (engine) => { // Create a new scene await engine.scene.createVideo(); // Add a text block const textBlock = engine.block.create('text'); engine.block.setString(textBlock, 'text/content', 'Hello, CE.SDK!'); // Set the color of the text engine.block.setColor(textBlock, 'fill/color', { r: 0, g: 0, b: 0, a: 1 }); // Attach the engine canvas to the DOM document.getElementById('cesdk_container').append(engine.element); }); ``` This example demonstrates how to initialize the CreativeEngine, create a scene, add a text block, and set its properties. The flexibility of the APIs allows for extensive customization and automation of design workflows. To learn more about how to power your own UI or creative workflows with the CreativeEngine have a look at the [engine guides](https://img.ly/docs/cesdk/engine/). ### Customization Options CE.SDK provides extensive customization options to adapt the UI to various use cases. These options range from simple configuration changes to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object**: When initializing the CreativeEditor, you can pass a configuration object that defines basic settings such as the base URL for assets, the language, theme, and license key. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization**: Customize the language and labels used in the editor to support different locales. ``` const config = { i18n: { en: { variables: { my_custom_variable: { label: 'Custom Label' } } } } }; ``` * [Custom Asset Sources](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): Serve custom video or image assets from a remote URL. ### UI Customization Options * **Theme**: Choose between predefined themes such as 'dark' or 'light'. ``` const config = { theme: 'dark' }; ``` * **UI Components**: Enable or disable specific UI components based on your requirements. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ### Advanced Customizations Learn more about extending editor functionality and customizing its UI to your use case by consulting our in-depth [customization guide](https://img.ly/docs/cesdk/ui/customization/guides/). Here is an overview of the APIs and components available to you. ### Order APIs Customization of the web editor's components and their order within these locations is managed through specific Order APIs, allowing the addition, removal, or reordering of elements. Each location has its own Order API, e.g., `setDockOrder`, `setCanvasMenuOrder`, `setInspectorBarOrder`, `setNavigationBarOrder`, and `setCanvasBarOrder`. ### Layout Components CE.SDK provides special components for layout control, such as `ly.img.separator` for separating groups of components and `ly.img.spacer` for adding space between components. ### Registration of New Components Custom components can be registered and integrated into the web editor using builder components like buttons, dropdowns, and inputs. These components can replace default ones or introduce new functionalities, deeply integrating custom logic into the editor. ### Feature API The Feature API enables conditional display and functionality of components based on the current context, allowing for dynamic customization. For example, you can hide certain buttons for specific block types. ## Plugins You can customize the CE.SDK web editor during its initialization using the APIs outlined above. For many use cases, this will be adequate. However, there are times when you might want to encapsulate functionality for reuse. This is where plugins become useful. Follow our [guide on building your own plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) to learn more or check out one of the plugins we built using this api: [**Background Removal**](https://img.ly/docs/cesdk/ui/customization/plugins/backgroundRemoval/): Adds a button to the canvas menu to remove image backgrounds. [**Vectorizer**](https://img.ly/docs/cesdk/ui/customization/plugins/vectorizer/): Adds a button to the canvas menu to quickly vectorize a graphic. ## Framework Support CreativeEditor SDK’s video editing library is compatible with any Javascript including, React, Angular, Vue.js, Svelte, Blazor, Next.js, Typescript. It is also compatible with desktop and server-side technologies such as electron, PHP, Laravel and Rails. [ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/solutions/video-editor/react/)[ Angular ](/docs/cesdk/ui/solutions/video-editor/angular/)[ Vue ](/docs/cesdk/ui/solutions/video-editor/vuejs/)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,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)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous Vue.js ](/docs/cesdk/ui/solutions/design-editor/vuejs/)[ Next React ](/docs/cesdk/ui/solutions/video-editor/react/) Create and Edit Video using the CreativeEditor SDK - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/guides/edit/?platform=web&language=js # Create and Edit Video using the CreativeEditor SDK CE.SDK Video Mode allows your users to edit videos in a manner familiar from popular platforms such as TikTok or Instagram. The CE.SDK editor contains a full-featured video editor, complete with Video and Audio trimming, and composition capabilities. Get to know the core concepts in this guide. To learn about the **available API calls** related to video editing, see the [engine guide on video editing](/docs/cesdk/engine/guides/video/). To learn about the **Video Editor UI**, skip to the [UI overview](#ui-overview). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/guides-video-mode?title=IMG.LY%27s+CE.SDK%3A+Guides+Video+Mode&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/guides-video-mode). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/guides-video-mode?title=IMG.LY%27s+CE.SDK%3A+Guides+Video+Mode&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/guides-video-mode) ## A Note on Browser Support Video mode heavily relies on modern features like web codecs. A detailed list of supported browser versions can be found in our ["Supported Browsers" documentation](/docs/cesdk/faq/browser-support/). Please also take note of [possible restrictions based on the host platform](/docs/cesdk/faq/editor-limitations/) browsers are running on. Video mode is currently not supported on any mobile web platform due to technical limitations by all mobile browsers. [Our native mobile SDKs are a great solution for implementing video features on mobile devices today.](/docs/cesdk/mobile-editor/solutions/video-editor/) ## Editor Setup ### Configuring which library entries to use for background clips You can use the `cesdk.ui.setBackgroundTrackAssetLibraryEntries` API to specify which library entries should be shown in insert panel for background clips. ``` cesdk.ui.setBackgroundTrackAssetLibraryEntries(['ly.img.image', 'ly.img.video']); ``` ### Add demo asset sources When adding demo asset sources using `cesdk.addDemoAssetSources()`, make sure to specify the correct scene mode. The installed demo asset sources vary between Design and Video modes. If you don't specify a scene mode, `addDemoAssetSources()` will try to add the correct sources based on the current scene, and default to `'Design'`. If you call `addDemoAssetSources()` _without_ a scene mode, and _before_ loading or creating a video scene, the audio and video asset sources will not be added. ``` cesdk.addDemoAssetSources({ sceneMode: 'Video' }); ``` ### Create or load a video scene To switch the editor to video mode, create an empty video scene using `cesdk.createVideoScene()`. If you already have a video scene, loading it with `loadFromUrl()` or `loadFromString()` will switch the editor to video mode as well. ``` const scene = await cesdk.createVideoScene(); ``` And that's it! ## UI Overview ### Timeline ![The editor timeline control.](/docs/cesdk/d4b0c103dabf50e4880b8c26ae957dc3/video_mode_timeline.png) The timeline is the main control for video editing. It is here that clips and audio strips can be positioned in time. The timeline is divided into two main areas: #### Foreground Clips run along the top of the foreground timeline, while audio strips run along the bottom. Click anywhere inside the timeline to set the playback time, or drag the seeker directly. Use the **Split Clip** button to split the clips into two at the position of the seeker. The **zoom controls** allow you to zoom the timeline in and out for better control. Finally, Use the **Play** and **Loop** buttons to view the final result. * Drag either of the strip handles to **adjust start and end time** of a clip. * Drag the center of the strip to **position** the clip. * Drag the center of the strip to **rearrange** the clips. * Use the context menu on the strip to **perform specific actions** on the clip. #### Background Background clips go at the bottom of the timeline and define the length of the video. Background clips are typically videos and images, but can be configured to support other types of elements as well. They are packed against each other and can't have time gaps in between, and they define the length of the exported video. * Drag either of the strip handles to **adjust start and end time** of a clip. * Drag the center of the strip to **rearrange** the clips. * Use the context menu on the strip to **perform specific actions** on the clip. ### Video Videos are handled in a similar fashion to regular elements: You can add them via the Asset Library and position them anywhere on the page. You can select a specific section of video to play by trimming the clip on the timeline and/or using the trim controls (pictured below), accessible by pressing the trim button. #### Trimming videos ![The trim controls for video.](/docs/cesdk/99df9a33df4d9e847b319866b2f48685/video_mode_trim_controls.png) Trim controls will appear near the top of the editor window. * While these controls are open, **only the selected video is played** during seeking or playback. * You can **adjust the start and end time** by dragging the handles on either side of the strip. * The **grayed-out area** indicates the parts of the video that won't be shown. * The **blue overlay** indicates the end of the page duration - to show these parts of the video, extend the duration of the containing page. ### Audio ![A timeline containing audio strips.](/docs/cesdk/707296d610a4bc233831b26946b46afe/video_mode_audio_strips.png) Unlike regular design elements, audio is not visible on the canvas. It is only shown in the timeline, as audio strips. Use the timeline to edit audio: * Drag either of the strip handles to **adjust start and end time** of the audio strip. * Drag the center of the strip to **position** the strip. * Drag the center of the strip to **rearrange** the audio stipes. * Use the context menu on the strip to **perform specific actions** on the audio strip. ### Trimming audio ![The trim controls for audio.](/docs/cesdk/9357ee451101c417f59018f0f93bba1f/video_mode_audio_trim.png) Trimming audio works just like [trimming video](#trimming-videos). Configuring the CreativeEditor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/configuration/ CESDK/CE.SDK/Web Editor/Configuration # Configuring the CreativeEditor SDK Get to know the various configuration options of the CreativeEditor SDK. The CreativeEditor SDK (CE.SDK) works out of the box with almost zero configuration effort. However, almost every part of CE.SDK can be adapted and its behaviour and look & feel changed. Here is a list of all available configuration options: Key Type Description Key [baseURL](/docs/cesdk/ui/configuration/basics/) Type `string` Description Definition of the the base URL of all assets required by the SDK. Key [callbacks](/docs/cesdk/ui/configuration/callbacks/) Type `object` Description Definition of callbacks the SDK triggers. Key [i18n](/docs/cesdk/ui/guides/i18n/) Type `object` Description Options to add custom translations to the SDK. Key [license](/docs/cesdk/engine/quickstart/#licensing) Type `string` Description A license key that is unique to your product. Key [userID](/docs/cesdk/engine/quickstart/#licensing) Type `string` Description An unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. Key [locale](/docs/cesdk/ui/configuration/basics/) Type `string` Description Chosen language the editor is . Possible values are 'en', 'de', ... Key [role](/docs/cesdk/ui/configuration/basics/) Type `string` Description Chosen role. Possible values are 'Creator' or 'Adopter'.defines Key [theme](/docs/cesdk/ui/configuration/basics/) Type `string` Description The theme the SDK is launched in. Possible values are 'dark', 'light' Key [ui](/docs/cesdk/ui/guides/elements/) Type `object` Description Options to adapt the user interface elements. [ Previous Notification ](/docs/cesdk/ui/customization/api/notification/)[ Next Basics ](/docs/cesdk/ui/configuration/basics/) Adjusting the Content of the Canvas Menu - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/api/canvasMenu/ CESDK/CE.SDK/Web Editor/Customization/API Reference # Adjusting the Content of the Canvas Menu Learn how to adjust the content of the canvas menu in the editor There are 2 APIs for getting and setting the order of components in the Canvas Menu. The content of the Canvas Menu changes based on the current [edit mode](/docs/cesdk/engine/api/editor-state/#state) (`'Transform'` (the default), `'Text'`, `'Crop'`, `'Trim'`, or a custom value), so both APIs accept an `orderContext` argument to specify the mode. For example usage of these APIs, see also [Moving Existing Buttons](/docs/cesdk/ui/customization/guides/movingExistingButtons/#reducing-the-canvas-menu) or [Adding New Buttons](/docs/cesdk/ui/customization/guides/addingNewButtons/#add-a-button-to-the-canvas-menu-to-mark-blocks) in the Guides section. ### Get the Current Order ``` cesdk.ui.getCanvasMenuOrder( orderContext: OrderContext = { editMode: 'Transform' } ) ``` When omitting the `orderContext` parameter, the order for the `'Transform'` edit mode is returned, e.g. ``` cesdk.ui.getCanvasMenuOrder(); // => [ // {id: 'ly.img.group.enter.canvasMenu'}, // {id: 'ly.img.group.select.canvasMenu'}, // ... // ] ``` ### Set a new Order ``` setCanvasMenuOrder( canvasMenuOrder: (CanvasMenuComponentId | OrderComponent)[], orderContext: OrderContext = { editMode: 'Transform' } ) ``` When omitting the `orderContext` parameter, the order is set for the default edit mode (`'Transform'`), e.g.: ``` // Sets the order for transform mode by default cesdk.ui.setCanvasMenuOrder([ 'my.component.for.transform.mode']); ``` ## Canvas Menu Components The following lists the default Canvas Menu components available within CE.SDK. Take special note of the "Feature ID" column. Most components can be hidden/disabled by disabling the corresponding feature using the [Feature API](/docs/cesdk/ui/customization/api/features/). Also note that many components are only rendered for the block types listed in the "Renders for" column, because their associated controls (e.g. font size) are only meaningful for specific kinds of blocks (e.g. text). ### Layout Helpers Component ID Description Component ID `ly.img.separator` Description Adds a vertical separator (`
` element) in the Canvas Menu. Separators follow some special layouting rules: \- If 2 or more separators end up next to each other (e.g. due to other components not rendering), **only 1** separator will be rendered. \- Separators that end up being the first or last element in the Canvas Menu will **not** be rendered. \- Separators directly adjacent _to the left side_ of a spacer (see below) will **not** be rendered. ### Common Controls These components are useful for editing various different block types. Component ID Description Feature ID Renders for Component ID `ly.img.replace.canvasMenu` Description Replace button: Opens the "Replace" Panel offering to replace the current image or video fill. Feature ID `ly.img.replace` Renders for Images, Videos Component ID `ly.img.placeholder.canvasMenu` Description Placeholder button: Opens the "Placeholder" panel, allowing the user to toggle specific [constraints](/docs/cesdk/ui/guides/placeholders/) on the selected block. Feature ID `ly.img.placeholder` Renders for Every block Component ID `ly.img.duplicate.canvasMenu` Description Duplicate button: Duplicates the selected elements when pressed. Feature ID `ly.img.duplicate` Renders for Every block Component ID `ly.img.delete.canvasMenu` Description Delete button: Deletes the selected elements when pressed. Feature ID `ly.img.delete` Renders for Every block ### Text These components are relevant for editing text blocks, and will only render when a text block is selected. Component ID Description Feature ID Component ID `ly.img.text.edit.canvasMenu` Description Edit button: Switches to text edit mode when pressed. Feature ID `ly.img.text.edit` Component ID `ly.img.text.color.canvasMenu` Description Color swatch button: Opens the Color Panel, where the user can set the color for the currently selected text run. Feature ID `ly.img.fill` Component ID `ly.img.text.bold.canvasMenu` Description Bold toggle: Toggles the bold cut (if available) for the currently selected text run. Feature ID `ly.img.text.edit` Component ID `ly.img.text.italic.canvasMenu` Description Italic toggle: Toggles the italic cut (if available) for the currently selected text run. Feature ID `ly.img.text.edit` Component ID `ly.img.text.variables.canvasMenu` Description Insert Variable button: Opens a dropdown containing the selection of available text variables. Feature ID ### Groups These controls will only render when a group or an element of a group is selected. Component ID Description Feature ID Renders for Component ID `ly.img.group.enter.canvasMenu` Description Enter group button: Moves selection to the first element of the group when pressed. Feature ID `ly.img.group` Renders for Groups Component ID `ly.img.group.select.canvasMenu` Description Select group button: Moves selection to the parent group of the currently selected element when pressed. Feature ID `ly.img.group` Renders for Elements within groups ### Page These controls will only render when a page is selected. Component ID Description Feature ID Component ID `ly.img.page.moveUp.canvasMenu` Description Move Up/Left button: Moves the current page closer to the **start** of a multi-page document. Feature ID `ly.img.page.move` Component ID `ly.img.page.moveDown.canvasMenu` Description Move Down/Right button: Moves the current page closer to the **end** of a multi-page document. Feature ID `ly.img.page.move` ## Default Order The default order of the Canvas Menu is the following: ### Transform Mode ``` [ 'ly.img.group.enter.canvasMenu', 'ly.img.group.select.canvasMenu', 'ly.img.page.moveUp.canvasMenu', 'ly.img.page.moveDown.canvasMenu', 'ly.img.separator', 'ly.img.text.edit.canvasMenu', 'ly.img.replace.canvasMenu', 'ly.img.separator', 'ly.img.placeholder.canvasMenu', 'ly.img.separator', 'ly.img.duplicate.canvasMenu', 'ly.img.delete.canvasMenu' ] ``` ### Text Mode ``` [ 'ly.img.text.color.canvasMenu', 'ly.img.separator', 'ly.img.text.bold.canvasMenu', 'ly.img.text.italic.canvasMenu', 'ly.img.separator', 'ly.img.text.variables.canvasMenu' ] ``` [ Previous Dock ](/docs/cesdk/ui/customization/api/dock/)[ Next Inspector Bar ](/docs/cesdk/ui/customization/api/inspectorBar/) Angular Image Editor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/photo-editor/angular/ CESDK/CE.SDK/Web Editor/Solutions/Photo Editor # Angular Image Editor SDK The CreativeEditor SDK provides a powerful and intuitive solution designed for seamless photo editing directly in the browser. This CE.SDK configuration is fully customizable and offers a range of features that cater to various use cases, from simple photo adjustments and image compositions with background removal to programmatic editing at scale. Whether you are building a photo editing application for social media, e-commerce, or any other platform, the CE.SDK Angular Image Editor provides the tools you need to deliver a best-in-class user experience. [Explore Demo](https://img.ly/showcases/cesdk/photo-editor-ui/web) ## Key Capabilities of the CE.SDK Photo Editor ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transform Easily perform operations like cropping, rotating, and resizing your design elements to achieve the perfect composition. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Management Import and manage stickers, images, shapes, and other assets to build intricate and visually appealing designs. ![images](/docs/cesdk/static/TextEditing-2529ca84ae5a97dd9bd3da74d0b86e85.png) ### Text Editing Add and style text blocks with a variety of fonts, colors, and effects, giving users the creative freedom to express themselves. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Processing All editing operations are performed directly in the browser, ensuring fast performance without the need for server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit designs using the engine API, allowing for automated workflows and advanced integrations within your application. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Enhance functionality with plugins and custom scripts, making it easy to tailor the editor to specific needs and use cases. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Design and integrate custom user interfaces that align with your application’s branding and user experience requirements. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Background Removal Utilize the powerful background removal plugin to allow users to effortlessly remove backgrounds from images, entirely on the Client-Side. ![images](/docs/cesdk/static/Filters-348c44197a3cde0e9982fd7560139502.png) ### Filters & Effects Choose from a wide range of filters and effects to add professional-grade finishing touches to photos, enhancing their visual appeal. ![images](/docs/cesdk/static/SizePresets-2fe404893ff6b91bb1695eb39ee9a658.png) ### Size Presets Access a variety of size presets tailored for different use cases, including social media formats and print-ready dimensions. ## Browser Compatibility The CE.SDK Design Editor is optimized for use in modern web browsers, ensuring compatibility with the latest versions of Chrome, Firefox, Edge, and Safari. See the full list of [supported browsers here](https://img.ly/docs/cesdk/faq/browser-support/). ## Prerequisites To get started with the CE.SDK Photo Editor, ensure you have the latest versions of **Node.js** and **NPM** installed. ## Supported File Types The CE.SDK Photo Editor supports loading, editing, and saving various image formats directly in the browser. Supported formats include: * JPG * PNG * SVG * WEBP Exports can be made in formats such as JPG, PNG, and SVG, depending on your requirements. ## Getting Started If you're ready to start integrating CE.SDK into your Angular application, check out the CE.SDK [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/). In order to configure the editor for an image editing use case consult our [photo editor UI showcase](https://img.ly/showcases/cesdk/photo-editor-ui/web#c) and its [reference implementation](https://github.com/imgly/cesdk-web-examples/tree/main/showcase-photo-editor-ui/src/components/case/CaseComponent.jsx). ## Understanding CE.SDK Architecture & API The following sections provide an overview of the key components of the CE.SDK photo editor UI and its API architecture. ### CreativeEditor Photo UI The CE.SDK photo editor UI is a specific configuration of the CE.SDK that focuses the Editor UI on essential photo editing features. It also includes our powerful background removal plugin that runs entirely on the user's device, saving on computing costs. This configuration can be further modified to suit your needs. Key components include: ![](/docs/cesdk/f6c8e768b60bb12f5dc766f8ca63ca62/CESDK-UI.png) * **Canvas:** The primary workspace where users interact with their photo content. * **Dock:** Provides access to tools and assets that are not directly related to the selected image or block, often used for adding or managing assets. * **Inspector Bar:** Controls properties specific to the selected block, such as size, rotation, and other adjustments. * **Canvas Menu:** Provides block-specific settings and actions such as deletion or duplication. * **Navigation Bar:** Offers global actions such as undo/redo, zoom controls, and access to export options. * **Canvas Bar:** For actions affecting the canvas or scene as a whole, such as adding pages or controlling zoom. This is an alternative place for actions like zoom or undo/redo. Learn more about interacting with and customizing the photo editor UI in our design editor UI guide. ### CreativeEngine At the heart of CE.SDK is the CreativeEngine, which powers all rendering and design manipulation tasks. It can be used in headless mode or integrated with the CreativeEditor UI. Key features and APIs provided by CreativeEngine include: * **Scene Management:** Create, load, save, and manipulate design scenes programmatically. * **Block Manipulation:** Create and manage elements such as images, text, and shapes within the scene. * **Asset Management:** Load and manage assets like images and SVGs from URLs or local sources. * **Variable Management:** Define and manipulate variables for dynamic content within scenes. * **Event Handling:** Subscribe to events such as block creation or selection changes for dynamic interaction. ## API Overview CE.SDK’s APIs are organized into several categories, each addressing different aspects of scene and content management. The engine API is relevant if you want to programmatically manipulate images to create or modify them at scale. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/): * **Creating Blocks**: ``` const block = engine.block.create('shapes/rectangle'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API:**](https://img.ly/docs/cesdk/engine/api/events/): * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ## Basic Automation Example The following automation example shows how to turn an image block into a square format for a platform such as Instagram: ``` // Assuming you have an initialized engine and a selected block (which is an image block) const newWidth = 1080; // Width in pixels const newHeight = 1080; // Height in pixels const imageBlockId = engine.block.findByType('image')[0]; engine.block.setWidth(imageBlockId, newWidth); engine.block.setHeight(imageBlockId, newHeight); engine.block.setContentFillMode(imageBlockId, 'Cover'); ``` ## Customizing the CE.SDK Photo Editor CE.SDK provides extensive customization options, allowing you to tailor the UI and functionality to meet your specific needs. This can range from basic configuration settings to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object:** Customize the editor’s appearance and functionality by passing a configuration object during initialization. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization:** Adjust the editor’s language and labels to support different locales. ``` const config = { i18n: { en: { 'libraries.ly.img.insert.text.label': 'Add Caption' } } }; ``` * **Custom Asset Sources:** Serve custom sticker or shape assets from a remote URL. ### UI Customization Options * **Theme:** Choose between 'dark' or 'light' themes. ``` const config = { theme: 'dark' }; ``` * **UI Components:** Enable or disable specific UI components as per your application’s needs. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations For deeper customization, [explore the range of APIs](https://img.ly/docs/cesdk/ui/customization/) available for extending the functionality of the photo editor. You can customize the order of components, add new UI elements, and even develop your own plugins to introduce new features. ## Plugins For cases where encapsulating functionality for reuse is necessary, plugins provide an effective solution. Use our [guide on building plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) to get started, or explore existing plugins like **Background Removal** and **Vectorizer**. ## Framework Support CreativeEditor SDK’s photo editor is compatible with Angular and other JavaScript frameworks like React, Vue.js, Svelte, Blazor, Next.js, and TypeScript. It also integrates well with desktop and server-side technologies such as Electron, PHP, Laravel, and Rails. [ Vanilla JS ](/docs/cesdk/ui/quickstart/)[ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/quickstart?framework=react)[ Angular ](/docs/cesdk/ui/quickstart?framework=angular)[ Vue ](/docs/cesdk/ui/quickstart?framework=vue)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node)[ Apple ](/docs/cesdk/mobile-editor/quickstart/)[ Android ](/docs/cesdk/mobile-editor/quickstart?framework=composable&language=kotlin&platform=android) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,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)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous React ](/docs/cesdk/ui/solutions/photo-editor/react/)[ Next Vue.js ](/docs/cesdk/ui/solutions/photo-editor/vuejs/) Showing Notifications - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/customization/api/notification/?platform=web&language=js # Showing Notifications The CE.SDK editor brings a notification API designed to deliver non-intrusive messages to the user, appearing in the lower right corner of the user interface. Use them to convey information without disrupting the user's current workflow. It provides a range of customization options to fit your specific needs. * `cesdk.ui.showNotification(notification: string | Notification): string` displays a new notification to the user, accepting either a simple string message or a more complex notification object that allows for additional configuration options (detailed below). It returns an id of the notification. * `cesdk.ui.updateNotification(notificationId: string, notification: Notification): updates a notification given by the id. All configuration options can be changed. If the` notificationId\` doesn't exist or the notification has already been dismissed, this action will have no effect. * `cesdk.ui.dismissNotification(notificationId: string)` removes a notification identified by the provided notification `notificationId`. If the `notificationId` doesn't exist or the notification has already been dismissed, this action will have no effect. ### Notification Options All options apart from `message` are optional Option Description Option `message` Description The message displayed on the notification. This can either be a string or an internationalization (i18n) key from the CE.SDK translations, allowing for localized messages. Option `type` Description Notifications can be displayed in various styles, each differing in appearance to convey the appropriate context to the user. The available types include `info` (which is the default setting), `warning`, `error`, `success`, and `loading` (which displays a loading spinner). Option `duration` Description Notifications typically disappear after a set period, but the duration can vary based on the message's importance. Less critical updates may vanish swiftly, whereas warnings or significant alerts stay open for a longer time. You can specify this duration using either a numerical value representing milliseconds or predefined strings from CE.SDK, such as `short`, `medium` (which is the default setting), or `long`, each corresponding to different default durations. For notifications that should remain visible indefinitely, the `infinite` value can be used to prevent automatic dismissal. Option `onDismiss` Description A callback function that is triggered upon the dismissal of the notification, whether it's done automatically, programmatically, or manually by the user. Option `action: { label: "Retry", onClick: () => void }` Description Adds a single action button within the notification for user interaction. Asset Library Entry - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/api/assetLibraryEntry/ CESDK/CE.SDK/Web Editor/Customization/API Reference # Asset Library Entry Learn how to configure what is shown in the asset library An [Asset Source](/docs/cesdk/engine/api/assets/) is defined within the engine and can be seen as some sort of database of assets, it does not define how these assets are presented to the user. The web editor provides a versatile asset library that will display the assets in a user-friendly way inside a panel. However since an asset source does not provide any presentational information, the web editor needs to be told how to display the assets. This connection is made with an `Asset Library Entry`. When you open the asset library, either by calling `openPanel` or adding a `ly.img.assetLibrary.dock` component to the dock, you must provide asset library entries. Only with these entries, the asset library will know what and how to display assets. ## Managing Asset Library Entries The CE.SDK web editor has an internal store of asset library entries that are referenced by asset libraries. You can find, add, remove, and update entries in this store. ``` CreativeEditorSDK.create('#cesdk', config).then(async (cesdk) => { await cesdk.addDefaultAssetSources(); await cesdk.addDemoAssetSources(); await cesdk.createDesignScene(); // Find all asset library entries const entryIds = cesdk.ui.findAllAssetLibraryEntries(); if (entryIds.includes('ly.img.image')) { // Get the default image entry of the web editor const entry = cesdk.ui.getAssetLibraryEntry('ly.img.image'); // ... to add a new source id. cesdk.ui.updateAssetLibraryEntry('ly.img.image', { sourceIds: [...entry.sourceIds, 'my.own.image.source'], }); } // Add a new custom asset library entry cesdk.ui.addAssetLibraryEntry({ id: 'my.own.image.entry', sourceIds: ['my.own.image.source'], sceneMode: 'Design', previewLength: 5, previewBackgroundType: 'cover', gridBackgroundType: 'cover', gridColumns: 2 }); // Remove a default entry for stickers. cesdk.ui.removeAssetLibraryEntry('ly.img.sticker'); ``` Method Description Method `findAllAssetLibraryEntries()` Description Returns all available asset library entries by their id. Method `addAssetLibraryEntry(entry)` Description Adds a new asset library entry to the store. Method `getAssetLibraryEntry(id)` Description Returns the asset library entry for the given id if present and `undefined` otherwise. Method `updateAssetLibraryEntry(id, entry)` Description Updates the asset library entry for the given id with the provided entry object. It does not replace the entry but is merged with a present entry. Method `removeAssetLibraryEntry(id)` Description Removes the asset library entry for the given id. ## Using Asset Library Entries Asset library entries are used by an asset library. How the library gets the entries depends on the context. The main use case for a library is the dock. This is described in the [Dock API](/docs/cesdk/ui/customization/api/dock/) documentation. We will describe further use cases here. ### Replace Asset Library The fill of a block can be replaced with another asset. What assets are applicable is different for each block. The web editor provides a function `setReplaceAssetLibraryEntries`. It gets a function that is used to define what block should be replaced with what asset. It will get the current context (most importantly the selected block) and will return ids to asset library entries that shall be used by the replace asset library. ``` cesdk.ui.setReplaceAssetLibraryEntries((context) => { const { selectedBlocks, defaultEntryIds } = context; // Most of the time replace makes only sense if one block is selected. if (selectedBlocks.length !== 1) { // If no entry ids are returned, a replace button will not be shown. return []; } // id, block and fill type are provided for the selected block. const { id, blockType, fillType } = selectedBlocks[0]; if (fillType === '//ly.img.ubq/fill/image') { return ['my.own.image.entry']; } return []; }); ``` ### Background Tracks Asset Library In the video timeline, the background track allows only certain type of assets. With `setBackgroundTrackAssetLibraryEntries` you can define what asset library entries are allowed for the background track when the user clicks on the "Add to Background Track" button. ``` // Only the image and video entries are allowed for the background track. cesdk.ui.setBackgroundTrackAssetLibraryEntries(['ly.img.video', 'ly.img.video']); ``` ## Asset Library Entry Definition ### Define What to Display The following properties tell the asset library what data should be queried and displayed. Property Type Description Property `id` Type `string` Description The unique id of this entry that is referenced by other APIs and passed to the asset library. Property `sourceIds` Type `string[]` Description Defines what asset sources should be queried by the asset library. Property `excludeGroups` Type `string[]` Description If the asset sources support groups, these will be excluded and not displayed. Property `includeGroups` Type `string[]` Description If the asset sources support groups, only these will be displayed and all other groups will be ignored. Property `sceneMode` Type `'Design'` or `'Video'` Description If set, the asset library will use this entry exclusively in the respective scene mode. ### Define How to Display Besides properties that define what data should be displayed, there are also properties that specify how the data should be displayed to meet specific use case requirements. #### Preview and Grid Some settings are for the preview and some for the grid view. If multiple asset sources or groups within a source are used, the asset library will show short sections with only a few assets shown. This is what we call the preview. The grid view is the main view where all assets are shown. Property Type Description Property `title` Type `string` or `((options: { group?: string; sourceId?: string }) => string` Description Use for custom translation in the overviews for a group or a source. If undefined is returned by the function it will be handled like there wasn't a title function set at all. Property `canAdd` Type `boolean'` or `'(sourceId: string) => boolean'` Description If `true` an upload button will be shown and the uploaded file will be added to the source. Please note that the asset source needs to support `addAsset` as well. Property `canRemove` Type `boolean'` or `'(sourceId: string) => boolean'` Description If `true` a remove button will be shown which will remove the asset from the source. Please note that the asset source needs to support `removeAsset` as well. Property `previewLength` Type `number` Description Defines how many assets will be shown in a preview section of a source or group. Property `previewBackgroundType` Type `cover` or `contain` Description If the thumbUri is set as a background that will be contained or covered by the card in the preview view. Property `gridBackgroundType` Type `cover` or `contain` Description If the thumbUri is set as a background that will be contained or covered by the card in the grid view. Property `gridColumns` Type `number` Description Number of columns in the grid view. Property `gridItemHeight` Type `auto` or `square` Description The height of an item in the grid view: \- `auto` automatically determines height yielding a masonry-like grid view. \- `square` every card will have the same square size. Property `cardBorder` Type `boolean` Description Draws a border around the card if set to true. Property `cardLabel` Type `(assetResult: AssetResult) => string` Description Overwrites the label of a card for a specific asset result Property `cardLabelPosition` Type `(assetResult: AssetResult) => 'inside' or 'below'` Description Position of the label `inside` or `below` the card. Property `cardLabelTruncateLines` Type `(assetResult: AssetResult) => 'single' or 'multi'` Description Controls label truncation to occur at end of first line (`single`), or at end of second line (`multi`). Property `cardBackground` Type `CardBackground[]` Description Determines what will be used as the card background from the asset and in which priorities. The first preference for which the `path` returns a value will be used to decide what and how the background will be rendered. E.g. a path of `meta.thumbUri` will look inside the asset for a value `asset.meta.thumbUri`. This non-null value will be used. The type of preference decides how the card will render the background. `svgVectorPath` creates a `` element with the given vector path. Adapts the color depending on the theme. `image` uses a CSS background image Property `sortBy` Type The value can be one of the following: * `"None" |"Ascending" |"Descending"` * `{ sortingOrder?: "None" | "Ascending" | "Descending", sortKey?: string }` * `{ sourceId: string, sortingOrder?: "None" |"Ascending" |"Descending", sortKey?: string }[]` Description Controls the sorting of asset source entries inside the asset library. This can be one of three possible values: 1. `"None" |"Ascending" |"Descending"` applies the sorting direction for all sources. 2. An object which defines the sort order via its `sortingOrder` key and an additional `sortKey` controlling the context of the sorting. This `sortKey` can be either any key which has been saved to the [asset source meta object](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) or the special `id` value which uses the asset ids as a sorting context. 3. An array which contains multiple of the above mentioned objects including a `sourceId` key defining to with asset source this configuration applies to. [ Previous Feature ](/docs/cesdk/ui/customization/api/features/)[ Next Icons ](/docs/cesdk/ui/customization/api/icons/) Buffers - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/editor-buffers/?platform=web&language=javascript # Buffers In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to create buffers through the `editor` API. Buffers can hold arbitrary data. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. #### Limitations Buffers are intended for temporary data only. * Buffer data is not part of the [scene serialization](/docs/cesdk/engine/api/scene-lifecycle/) * Changes to buffers can't be undone using the [history system](/docs/cesdk/engine/api/editor-history/) ``` createBuffer(): string ``` Create a resizable buffer that can hold arbitrary data. * Returns A URI to identify the buffer. ``` const audioBuffer = engine.editor.createBuffer(); ``` ``` destroyBuffer(uri: string): void ``` Destroy a buffer and free its resources. * `uri`: The URI of the buffer to destroy. ``` engine.editor.destroyBuffer(audioBuffer); ``` ``` setBufferData(uri: string, offset: number, data: Uint8Array): void ``` Set the data of a buffer at a given offset. * `uri`: The URI of the buffer to update. * `offset`: The offset in bytes at which to start writing. * `data`: The data to write. ``` engine.editor.setBufferData(audioBuffer, 0, new Uint8Array(samples.buffer)); ``` ``` getBufferData(uri: string, offset: number, length: number): Uint8Array ``` Get the data of a buffer at a given offset. * `uri`: The URI of the buffer to query. * `offset`: The offset in bytes at which to start reading. * `length`: The number of bytes to read. * Returns The data at the given offset. ``` const chunk = engine.editor.getBufferData(audioBuffer, 0, 4096); ``` ``` setBufferLength(uri: string, length: number): void ``` Set the length of a buffer. * `uri`: The URI of the buffer to update. * `length`: The new length of the buffer in bytes. ``` engine.editor.setBufferLength(audioBuffer, length / 2); ``` ``` getBufferLength(uri: string): number ``` Get the length of a buffer. * `uri`: The URI of the buffer to query. * Returns The length of the buffer in bytes. ``` const length = engine.editor.getBufferLength(audioBuffer); ``` Configure the SDK to Use Assets Served From Your Own Servers - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/guides/assets-served-from-your-own-servers/ CESDK/CE.SDK/Web Editor/Guides # Configure the SDK to Use Assets Served From Your Own Servers Learn how to serve assets from your own servers in the CreativeEditor SDK. In this example, we explain how to configure the Creative Engine to use assets hosted on your own servers. While we serve all assets from our own CDN by default, it is highly recommended to serve the assets from your own servers in a production environment. ## Prerequisites * [Get the latest stable version of **Node.js & NPM**](https://www.npmjs.com/get-npm) * (Optional): [Get the latest stable version of **Yarn**](https://yarnpkg.com/getting-started) ## 1\. Add the CreativeEditorSDK to Your Project ``` npm install --save @cesdk/engine@1.43.0 ``` ## 2\. Decide what Assets you need We offer two sets of assets: * _Core_ Assets - Files required for the engine to function. * _Default_ Assets - Demo assets to quickstart your development. ### ⚠️ Warning Prior to `v1.10.0`, the `CreativeEditorSDK` and `CreativeEditorSDK` would load referenced default assets from the `assets/extensions/*` directories and hardcode them as `/extensions/…` references in serialized scenes. To **maintain compatibility** for such scenes, make sure you're still serving the `/extensions` directory (included in version v1.9.2) from your `baseURL`. You can [download them from our CDN](https://cdn.img.ly/packages/imgly/cesdk-engine/1.9.2/assets/extensions.zip). ## 3\. Register IMG.LY's default assets If you want to use our default asset sources in your integration, call `CreativeEditorSDK.addDefaultAssetSources({ baseURL?: string, excludeAssetSourceIds?: string[] }` during initialization: ``` CreativeEditorSDK.create(config).then(async (instance) => { await instance.addDefaultAssetSources(); await instance.createDesignScene(); }); ``` This call adds IMG.LY's default asset sources for images, stickers, vectorpaths and filters to your engine instance. By default, these include the following source ids: * `'ly.img.image'` - Sample images * `'ly.img.sticker'` - Various stickers * `'ly.img.vectorpath'` - Shapes and arrows * `'ly.img.filter.lut'` - LUT effects of various kinds. * `'ly.img.filter.duotone'` - Color effects of various kinds. If you don't specify a `baseURL` option, the assets are parsed and served from the IMG.LY CDN. It's it is highly recommended to serve the assets from your own servers in a production environment, if you decide to use them. To do so, follow the steps below and pass a `baseURL` option to `addDefaultAssetSources`. If you only need a subset of the IDs above, use the `excludeAssetSourceIds` option to pass a list of ignored Ids. ## 4\. Copy Assets Copy the CreativeEditorSDK `core`, etc. asset folders to your application's asset folder. The name of the folder depends on your setup and the used bundler, but it's typically a folder called `assets` or `public` in the project root. ``` cp -r ./node_modules/@cesdk/cesdk-js/assets public/ ``` Furthermore, if you are using our IMG.LY default assets, download them from [our CDN](https://cdn.img.ly/assets/v3/IMGLY-Assets.zip) and extract them to your public directory as well. If you deploy the site that embeds the CreativeEditorSDK together with all its assets and static files, this might be all you need to do for this step. In different setups, you might need to upload this folder to your CDN. ## 5\. Configure the CreativeEditorSDK to use your self-hosted assets Next, we need to configure the SDK to use our local assets instead of the ones served via our CDN. There are two configuration options that are relevant for this: * `baseURL` should ideally point to the `assets` folder that was copied in the previous step. This can be either an absolute URL, or a path. A path will be resolved using the `window.location.href` of the page where the CreativeEditorSDK is embedded. By default `baseURL` is set to our CDN. * `core.baseURL` must point to the folder containing the core sources and data file for the CreativeEditorSDK. Defaults to `${baseURL}/core` This can be either an absolute URL, or a relative path. A relative path will be resolved using the the previous `baseURL`. By default, `core.baseURL` is set to `core/`. Normally you would simply serve the assets directory from the previous step. That directory already contains the `core` folder, and this setting does not need to be changed. For highly customized setups that separate hosting of the WASM files from the hosting of other assets that are used inside scenes, you can set this to a different URL. * `CreativeEditorSDK.addDefaultAssetSources` offers a `baseURL` option, that needs to be set to an absolute URL or a URL relative to the **global** `baseURL` option described above. This can be either an absolute URL, or a relative path. A relative path will be resolved using the the previous `baseURL`. By default, default sources parse and reference assets from `https://cdn.img.ly/assets/v3`. ``` const config = { ... // Specify baseURL for all relative URLs. baseURL: '/assets' // or 'https://cdn.mydomain.com/assets' core: { // Specify location of core assets, required by the engine. baseURL: 'core/' }, ... }; // Setup SDK and add default sources served from your own server. CreativeEditorSDK.create(config).then(async (instance) => { await instance.addDefaultAssetSources({ baseURL: 'https://cdn.mydomain.com/assets'}) await instance.createDesignScene() }) ``` ## Versioning of the WASM assets The `.wasm` and `.data` files that the CreativeEditorSDK loads from `core.baseURL` are changing between different versions of the CreativeEditorSDK. You need to ensure that after a version update, you update your copy of the assets. The filenames of these assets will also change between updates. This makes it safe to store different versions of these files in the same folder during migrations, as the CreativeEditorSDK will always locate the correct files using the unique filenames. It also means that if you forget to copy the new assets, the CreativeEditorSDK will fail to load them during initialization and abort with an Error message on the console. Depending on your setup this might only happen in your production or staging environments, but not during development where the assets might be served from a local server. Thus we recommend to ensure that copying of the assets is taken care of by your automated deployments and not performed manually. [ Previous I18n ](/docs/cesdk/ui/guides/i18n/)[ Next Custom Templates ](/docs/cesdk/ui/guides/custom-template-source/) Creating a Scene From an Initial Image URL - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/create-scene-from-image-url/?platform=web&language=javascript # Creating a Scene From an Initial Image URL In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-url?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Image+Url&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-url). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-url?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Image+Url&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-url) Starting from an existing image allows you to use the editor for customizing individual assets. This is done by using `engine.scene.createFromImage(url: string, dpi = 300, pixelScaleFactor = 1): Promise` and passing a URL as argument. The `dpi` argument sets the dots per inch of the scene. The `pixelScaleFactor` sets the display's pixel scale factor. Specify the source to use for the initial image. This can be a relative path or a remote URL. ``` await engine.scene.createFromImage( ``` We can retrieve the graphic block id of this initial image using `cesdk.engine.block.findByType(type: ObjectType): number[]`. Note that that function returns an array. Since there's only a single graphic block in the scene, the block is at index `0`. ``` // Find the automatically added graphic block in the scene that contains the image fill. const block = engine.block.findByType('graphic')[0]; ``` We can then manipulate and modify this block. Here we modify its opacity with `cesdk.engine.block.setOpacity(id: number, opacity: number): void`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. engine.block.setOpacity(block, 0.5); ``` When starting with an initial image, the scene's page dimensions match the given resource and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Integrate a Custom Asset Source - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/integrate-a-custom-asset-source/?platform=web&language=javascript # Integrate a Custom Asset Source In this example, we will show you how to integrate your custom asset sources into [CE.SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-custom-asset-source?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Custom+Asset+Source&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-custom-asset-source). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-custom-asset-source?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Custom+Asset+Source&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-custom-asset-source) With CE.SDK you can directly add external image providers like Unsplash or your own backend. A third option we will explore in this guide is using the engine's [Asset API](/docs/cesdk/engine/api/assets/) directly. Follow along with this example while we are going to add the Unsplash library. Adding an asset source is done creating an asset source definition and adding it using `asset.addSource(source: AssetSource)`. The asset source needs a unique identifier as part of an object implementing the interface of the source. All Asset API methods require the asset source's unique identifier. ``` const customSource = { id: 'unsplash', findAssets: findUnsplashAssets, credits: { name: 'Unsplash', url: 'https://unsplash.com/' }, license: { name: 'Unsplash license (free)', url: 'https://unsplash.com/license' } }; ``` The most important function to implement is `findAssets(query: AssetQueryData): AssetQueryResult`. With this function alone you can define the complete asset source. It receives the asset query as an argument and returns a promise with the results. * The argument is the `queryData` and describes the slice of data the engine wants to use. This includes a query string and pagination information. * The result of this query is a promise that, besides the actual asset data, returns information like the current page, the next page and the total number of assets available for this specific query. Returning a promise gives us great flexibility since we are completely agnostic of how we want to get the assets. We can use `fetch`, `XMLHttpRequest` or import a library to return a result. ``` const findUnsplashAssets = async (queryData) => { ``` Let us implement an Unsplash asset source. Please note that this is just for demonstration purposes only and may not be ideal if you want to integrate Unsplash in your production environment. We will use the official `unsplash-js` library to query the Unsplash API ([Link to Github page](https://github.com/unsplash/unsplash-js)). According to their documentation and guidelines, we have to create an access key and use a proxy to query the API, but this is out of scope for this example. Take a look at Unsplash's documentation for further details. ``` const unsplashApi = unsplash.createApi({ apiUrl: '...' }); ``` Unsplash has different API endpoints for different use cases. If we want to search we need to call a different endpoint as if we just want to display images without any search term. Therefore we need to check if the query data contains a `query` string. If `findAssets` was called with a non-empty `query` we can call the asynchronous method `search.getPhotos`. As we can see in the example, we are passing the query arguments to this method. * `queryData.query`: The current search string from the search bar in the asset library. * `queryData.page`: For Unsplash specifically the requested page number starts with 1. We do not query all assets at once but by pages. As the user scrolls down more pages will be requested by calls to the `findAssets` method. * `queryData.perPage`: Determines how many assets we want to have included per page. This might change between calls. For instance, `perPage` can be called with a small number to display a small preview, but with a higher number e.g. if we want to show more assets in a grid view. ``` const response = await unsplashApi.search.getPhotos({ query: queryData.query, page: unsplashPage, perPage: queryData.perPage }); ``` Once we receive the response and check for success we need to map Unsplash's result to what the asset source API needs as a result. The CE.SDK expects an object with the following properties: * `assets`: An array of assets for the current query. We will take a look at what these have to look like in the next paragraph. * `total`: The total number of assets available for the current query. If we search for "Cat" with `perPage` set to 30, we will get 30 assets, but `total` likely will be a much higher number. * `currentPage`: Return the current page that was requested. * `nextPage`: This is the next page that can be requested after the current one. Should be `undefined` if there is no other page (no more assets). In this case we stop querying for more even if the user has scrolled to the bottom. ``` const { results, total, total_pages } = response.response; return { assets: await Promise.all(results.map(translateToAssetResult)), total, currentPage: queryData.page, nextPage: queryData.page + 1 < total_pages ? queryData.page + 1 : undefined }; ``` Every image we get as a result of Unsplash needs to be translated into an object that is expected by the asset source API. We will describe every mandatory and optional property in the following paragraphs. ``` async function translateToAssetResult(image) { ``` `id`: The id of the asset (mandatory). This has to be unique for this source configuration. ``` id: image.id, ``` `locale` (optional): The language locale for this asset is used in `label` and `tags`. ``` locale: 'en', ``` `label` (optional): The label of this asset. It could be displayed in the tooltip as well as in the credits of the asset. ``` label: image.description ?? image.alt_description ?? undefined, ``` `tags` (optional): The tags of this asset. It could be displayed in the credits of the asset. ``` tags: image.tags ? image.tags.map((tag) => tag.title) : undefined, ``` `meta`: The meta object stores asset properties that depend on the specific asset type. ``` meta: { ``` `uri`: For an image asset this is the URL to the image file that will be used to add the image to the scene. Note that we have to use the Unsplash API to obtain a usable URL at first. ``` uri: await getUnsplashUrl(image), ``` `thumbUri`: The URI of the asset's thumbnail. It could be used in an asset library. ``` thumbUri: image.urls.thumb, ``` `blockType`: The type id of the design block that should be created when this asset is applied to the scene. If omitted, CE.SDK will try to infer the block type from an optionally provided `mimeType` property (e.g. `image/jpeg`) or by loading the asset data behind `uri` and parsing the mime type from that. However, this will cause a delay before the asset can be added to the scene, which is why it is always recommended to specify the `blockType` upfront. ``` blockType: '//ly.img.ubq/graphic', ``` `fillType`: The type id of the fill that should be attached to the block when this asset is applied to the scene. If omitted, CE.SDK will default to a solid color fill `//ly.img.ubq/fill/color`. ``` fillType: '//ly.img.ubq/fill/image', ``` `shapeType`: The type id of the shape that should be attached to the block when this asset is applied to the scene. If omitted, CE.SDK will default to a rect shape `//ly.img.ubq/shape/rect`. ``` shapeType: '//ly.img.ubq/shape/rect', ``` `kind`: The kind that should be set to the block when this asset is applied to the scene. If omitted, CE.SDK will default to an empty string. ``` kind: 'image', ``` `width`: The original width of the image. `height`: The original height of the image. ``` width: image.width, height: image.height ``` `context`: Adds contextual information to the asset. Right now, this only includes the source id of the source configuration. ``` context: { sourceId: 'unsplash' }, ``` `credits` (optional): Some image providers require to display credits to the asset's artist. If set, it has to be an object with the artist's `name` and a `url` to the artist page. ``` credits: artistName ? { name: artistName, url: artistUrl } : undefined, ``` `utm` (optional): Some image providers require to add UTM parameters to all links to the source or the artist. If set, it contains a string to the `source` (added as `utm_source`) and the `medium` (added as `utm_medium`) ``` utm: { source: 'CE.SDK Demo', medium: 'referral' } ``` After translating the asset to match the interface from the asset source API, the array of assets for the current page can be returned. In case, we've encountered an error, we need to reject the promise. Since we use the async/await language feature this means throwing a new error. ``` throw new Error(response.errors.join('. ')); ``` An empty result can be returned instead of the result object if for some reason it does not make sense to throw an error but just show an empty result. ``` return Promise.resolve(EMPTY_RESULT); ``` Going further with our Unsplash integration we need to handle the case when no query was provided. Unsplash requires us to call a different API endpoint (`photos.list`) with slightly different parameters but the basics are the same. We need to check for success, calculate `total` and `nextPage` and translate the assets. ``` const response = await unsplashApi.photos.list({ orderBy: 'popular', page: unsplashPage, perPage: queryData.perPage }); if (response.type === 'success') { const { results, total } = response.response; const totalFetched = queryData.page * queryData.perPage + results.length; const nextPage = totalFetched < total ? queryData.page + 1 : undefined; return { assets: await Promise.all(results.map(translateToAssetResult)), total, currentPage: queryData.page, nextPage }; } else if (response.type === 'error') { throw new Error(response.errors.join('. ')); } else { return Promise.resolve(EMPTY_RESULT); } ``` We have already seen that an asset can define credits for the artist. Depending on the image provider you might need to add credits and the license for the source. In case of Unsplash, this includes a link as well as the license of all assets from this source. ``` credits: { name: 'Unsplash', url: 'https://unsplash.com/' }, license: { name: 'Unsplash license (free)', url: 'https://unsplash.com/license' } ``` ## Local Asset Sources In many cases, you already have various finite sets of assets that you want to make available via asset sources. In order to save you the effort of having to implement custom asset query callbacks for each of these asset sources, CE.SDK also allows you to create "local" asset sources, which are managed by the engine and provide search and pagination functionalities. In order to add such a local asset source, simply call the `addLocalSource` API and choose a unique id with which you can later access the asset source. ``` const localSourceId = 'background-videos'; engine.asset.addLocalSource(localSourceId); ``` The `addAssetToSource(sourceId: string, asset: AssetDefinition): void` API allows you to add new asset instances to your local asset source. The local asset source then keeps track of these assets and returns matching items as the result of asset queries. Asset queries return the assets in the same order in which they were inserted into the local asset source. Note that the `AssetDefinition` type that we pass to the `addAssetToSource` API is slightly different than the `AssetResult` type which is returned by asset queries. The `AssetDefinition` for example contains all localizations of the labels and tags of the same asset whereas the `AssetResult` is specific to the locale property of the query. ``` engine.asset.addAssetToSource(localSourceId, { id: 'ocean-waves-1', label: { en: 'relaxing ocean waves', es: 'olas del mar relajantes' }, tags: { en: ['ocean', 'waves', 'soothing', 'slow'], es: ['mar', 'olas', 'calmante', 'lento'] }, meta: { uri: `https://example.com/ocean-waves-1.mp4`, thumbUri: `https://example.com/thumbnails/ocean-waves-1.jpg`, mimeType: 'video/mp4', width: 1920, height: 1080 }, credits: { name: 'John Doe', url: 'https://example.com/johndoe' } }); ``` Load Scenes from a String - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/load-scene-from-string/?platform=web&language=javascript # Load Scenes from a String In this example, we will show you how to load scenes from a string with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-string?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Load+Scene+From+String&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-string). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-string?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Load+Scene+From+String&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-load-scene-from-string) Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. This can be done by passing a string that describes a scene to the `engine.scene.loadFromString()` function. #### Warning Saving a scene can be done as a either _scene file_ or as an _archive file_ (c.f. [Saving scenes](/docs/cesdk/engine/guides/save-scene/)). A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an _archive file_ contains within it the scene's assets and references them as relative URIs. In this example, we fetch a scene from a remote URL and load it as a string. This string could also come from the result of `engine.scene.saveToString()`. ``` const sceneUrl = 'https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene'; const sceneString = await fetch(sceneUrl).then((response) => { return response.text(); }); ``` We can pass that string to the `engine.scene.loadFromString(sceneContent: string): Promise)` function. The editor will then reset and present the given scene to the user. The function is asynchronous and the returned `Promise` resolves, if the scene load succeeded. ``` let scene = await engine.scene .loadFromString(sceneString) .then(() => { console.log('Load succeeded'); let text = engine.block.findByType('text')[0]; engine.block.setDropShadowEnabled(text, true); }) .catch((error) => { console.error('Load failed', error); }); ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` let text = engine.block.findByType('text')[0]; engine.block.setDropShadowEnabled(text, true); ``` Scene loads may be reverted using `cesdk.engine.editor.undo()`. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). React Image Editor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/photo-editor/react/ CESDK/CE.SDK/Web Editor/Solutions/Photo Editor # React Image Editor SDK The CreativeEditor SDK provides a powerful and intuitive solution designed for seamless photo editing directly in the browser. This CE.SDK configuration is fully customizable and offers a range of features that cater to various use cases, from simple photo adjustments and image compositions with background removal to programmatic editing at scale. Whether you are building a photo editing application for social media, e-commerce, or any other platform, the CE.SDK React Image Editor provides the tools you need to deliver a best-in-class user experience. [Explore Demo](https://img.ly/showcases/cesdk/photo-editor-ui/web) ## Key Capabilities of the CE.SDK Photo Editor ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transform Easily perform operations like cropping, rotating, and resizing your design elements to achieve the perfect composition. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Management Import and manage stickers, images, shapes, and other assets to build intricate and visually appealing designs. ![images](/docs/cesdk/static/TextEditing-2529ca84ae5a97dd9bd3da74d0b86e85.png) ### Text Editing Add and style text blocks with a variety of fonts, colors, and effects, giving users the creative freedom to express themselves. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Processing All editing operations are performed directly in the browser, ensuring fast performance without the need for server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit designs using the engine API, allowing for automated workflows and advanced integrations within your application. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Enhance functionality with plugins and custom scripts, making it easy to tailor the editor to specific needs and use cases. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Design and integrate custom user interfaces that align with your application’s branding and user experience requirements. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Background Removal Utilize the powerful background removal plugin to allow users to effortlessly remove backgrounds from images, entirely on the Client-Side. ![images](/docs/cesdk/static/Filters-348c44197a3cde0e9982fd7560139502.png) ### Filters & Effects Choose from a wide range of filters and effects to add professional-grade finishing touches to photos, enhancing their visual appeal. ![images](/docs/cesdk/static/SizePresets-2fe404893ff6b91bb1695eb39ee9a658.png) ### Size Presets Access a variety of size presets tailored for different use cases, including social media formats and print-ready dimensions. ## Browser Support The CE.SDK Photo Editor is optimized for use in modern web browsers, ensuring compatibility with the latest versions of Chrome, Firefox, Edge, and Safari. ## Prerequisites To get started with the CE.SDK Photo Editor, ensure you have the latest versions of **Node.js** and **NPM** installed. ## Supported File Types The CE.SDK Photo Editor supports loading, editing, and saving various image formats directly in the browser. Supported formats include: * JPG * PNG * SVG * WEBP Exports can be made in formats such as JPG, PNG, and SVG, depending on your requirements. ## Getting Started If you're ready to start integrating CE.SDK into your React application, check out the CE.SDK [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/). In order to configure the editor for an image editing use case consult our [photo editor UI showcase](https://img.ly/showcases/cesdk/photo-editor-ui/web#c) and its [reference implementation](https://github.com/imgly/cesdk-web-examples/tree/main/showcase-photo-editor-ui/src/components/case/CaseComponent.jsx). ## Understanding CE.SDK Architecture & API The following sections provide an overview of the key components of the CE.SDK photo editor UI and its API architecture. If you're ready to start integrating CE.SDK into your React application, check out our [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/) or explore the Essential Guides. ### CreativeEditor Photo UI The CE.SDK photo editor UI is a specific configuration of the CE.SDK that focuses the Editor UI on essential photo editing features. It also includes our powerful background removal plugin that runs entirely on the user's device, saving on computing costs. This configuration can be further modified to suit your needs. Key components include: ![](/docs/cesdk/f6c8e768b60bb12f5dc766f8ca63ca62/CESDK-UI.png) * **Canvas:** The primary workspace where users interact with their photo content. * **Dock:** Provides access to tools and assets that are not directly related to the selected image or block, often used for adding or managing assets. * **Inspector Bar:** Controls properties specific to the selected block, such as size, rotation, and other adjustments. * **Canvas Menu:** Provides block-specific settings and actions such as deletion or duplication. * **Navigation Bar:** Offers global actions such as undo/redo, zoom controls, and access to export options. * **Canvas Bar:** For actions affecting the canvas or scene as a whole, such as adding pages or controlling zoom. This is an alternative place for actions like zoom or undo/redo. Learn more about interacting with and customizing the photo editor UI in our design editor UI guide. ### CreativeEngine At the heart of CE.SDK is the CreativeEngine, which powers all rendering and design manipulation tasks. It can be used in headless mode or integrated with the CreativeEditor UI. Key features and APIs provided by CreativeEngine include: * **Scene Management:** Create, load, save, and manipulate design scenes programmatically. * **Block Manipulation:** Create and manage elements such as images, text, and shapes within the scene. * **Asset Management:** Load and manage assets like images and SVGs from URLs or local sources. * **Variable Management:** Define and manipulate variables for dynamic content within scenes. * **Event Handling:** Subscribe to events such as block creation or selection changes for dynamic interaction. ## API Overview CE.SDK’s APIs are organized into several categories, each addressing different aspects of scene and content management. The engine API is relevant if you want to programmatically manipulate images to create or modify them at scale. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/) * **Creating Blocks**: ``` const block = engine.block.create('shapes/rectangle'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source) * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API:**](https://img.ly/docs/cesdk/engine/api/events/) * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ## Basic Automation Example The following automation example shows how to turn an image block into a square format for a platform such as Instagram: ``` // Assuming you have an initialized engine and a selected block (which is an image block) const newWidth = 1080; // Width in pixels const newHeight = 1080; // Height in pixels const imageBlockId = engine.block.findByType('image')[0]; engine.block.setWidth(imageBlockId, newWidth); engine.block.setHeight(imageBlockId, newHeight); engine.block.setContentFillMode(imageBlockId, 'Cover'); ``` ## Customizing the CE.SDK Photo Editor CE.SDK provides extensive customization options, allowing you to tailor the UI and functionality to meet your specific needs. This can range from basic configuration settings to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object:** Customize the editor’s appearance and functionality by passing a configuration object during initialization. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization:** Adjust the editor’s language and labels to support different locales. ``` const config = { i18n: { en: { 'libraries.ly.img.insert.text.label': 'Add Caption' } } }; ``` * **Custom Asset Sources:** Serve custom sticker or shape assets from a remote URL. ### UI Customization Options * **Theme:** Choose between 'dark' or 'light' themes. ``` const config = { theme: 'dark' }; ``` * **UI Components:** Enable or disable specific UI components as per your application’s needs. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations For deeper customization, [explore the range of APIs](https://img.ly/docs/cesdk/ui/customization/) available for extending the functionality of the photo editor. You can customize the order of components, add new UI elements, and even develop your own plugins to introduce new features. ## Plugins For cases where encapsulating functionality for reuse is necessary, plugins provide an effective solution. Use our [guide on building plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) to get started, or explore existing plugins like **Background Removal** and **Vectorizer**. ## Framework Support CreativeEditor SDK’s photo editor is compatible with React and other JavaScript frameworks like Angular, Vue.js, Svelte, Blazor, Next.js, and TypeScript. It also integrates well with desktop and server-side technologies such as Electron, PHP, Laravel, and Rails. [ Vanilla JS ](/docs/cesdk/ui/quickstart/)[ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/quickstart?framework=react)[ Angular ](/docs/cesdk/ui/quickstart?framework=angular)[ Vue ](/docs/cesdk/ui/quickstart?framework=vue)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node)[ Apple ](/docs/cesdk/mobile-editor/quickstart/)[ Android ](/docs/cesdk/mobile-editor/quickstart?framework=composable&language=kotlin&platform=android) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVAAAAB4CAMAAACTvloEAAACK1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVv0cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVv0cAAAAAAAAAAAAAAACVv0cAAACZwkiVv0cAAACVv0cAAAAAAAAAAACEsEEAAAAAAAAAAAAAAACWwUYAAAAAAACYwUyVwVIAAAAAAAAAAACVv0eWv0eXwEmWv0eXv0eVv0eWwEeWwEeVv0eWwEeYwEhejj6VwEeVwEcAAABfjj6Vv0eVv0Zejj5ejz6Vv0eVwEeVv0Zejj6UvkeWwEeVwEeWwEeXwEdikkNejz4AAACWwEgAAABfjz6XwUVijT2Vv0dejj5ejj+WwEdfjz6WwEeVv0Zfjz9ikEFejz9ejj5ejj6Xv0eVwEZejj6WwEdejz6VwEdgkD+Vv0dgjj9fjj5fjz5hjz5ejj5fjz5fjz5djz9ekD9smT8AAACVv0dejj7///+ItEX9/vqXwUuszXCZwk7v9uP6/Pb4+/Lg7cq61Yeoy2n0+ezs9N3R467D25edxFTo8dbK4KS/2I+204CkyGChx12fxVnl79Da6b/V5rbH3Z2wz3alyWTY57rc314sAAAAl3RSTlMA+/cJ8sd46+QSBPAPB88w5jQbDBjWgO3acJ+/VUhDm5KIOxb9r444UCneYCDo06M/4bosI8OFTCbqs6uLaO6nEvf0z8x9XgiWdWtSJGIdDQW3e1rXxxawMeSRinVtG/r58ryAfFP04UpCOuzBtquXKQ3OyaRlLh4W27umno1oWUwfeG5nYzXFu6+CP183dV8l2JpWlpUqU/DWcAAAE4hJREFUeNrs1s1q20AUhuFzMXMT2giJWXhjkBb6/+sIy8Y2cqWCwQiMTdMmtCaELj+Tm+2MQ6AQSmWnq3Ce1dH2ZY5miDHGGGOMMcYYYx+Ms2y6ff4YEfsP/D4fbCUg6uHgEHuv6D6A5roCUJskmU6IvUN1SgEls6LIJYC2jU9rTno7b+sCVmmOZSJxoYKMf6a3imKBeuuYsqUFQ9UCCHfEbuEUNeyuIk33FOmPGlYXu8C+InaDQwq3i0ibW4BcVi3UocpsuLlH7Gp9CvHZIW016KknKoG95zQK6ZLY9RcSEExJc2IhZK+HNRBG+hPo+El6Lf9owT74ZiptpHMzeYBV6a4Kli7t895fYxoL9bLwzgDkEzJsCHNSJVD2xaa94yfpaH4DyAcyTkAQ0YUE1kSTAnABPC+euOhYOwnkZHgDVEkvWmCZHJsQF8/nxdMXYqPMXdg7Mh5f7yZtA4TSFngNel7MuOg4GyCjiy3Q+GRM7y38wQQ9f5rx1o/xYCNNyOgDWCua7FZFKIA3QXXRr8T+qXGxdcg4WmhXx6w1i/42qCn6c6bdffv+i9jfRPFv7uyzR4koCgPwGYo0aQKidEFw0UVW1sWCvWLv3Wg0GnvsXRN7Lx80nomxu9bY+89TlHvvgRkkMySY+HzZZPeG2bz3PXBnkCdUP4iuDJbHLJ0gCzRQYf36HTfgf2Xzzhk2bFjEawO9ti6Vl62sHN3nnp8qC8pAqdvQmtS8/sz0JLRZ35Q/ZVT9Q3Z8aUDO4nA4grkRhRjos3bD4BML5m5eu2KDzDQP9PIRaIl7KDK5edBWtkjcHNifmKOI1NhVtiBxEHRZsFsevHP1pTH0bbN5oBcPQUtiBmRcMWinzoQVKzz9oVa/6S6sMR90WbdLHjy5ci+kKdB9V6El45HbOBrayJhhWxnsAspYNmGNAaDdpO3nT7AstQW65Sa0JIRcyAlt1IFcgQ69P4114pqruXXTCjbomgNdfwZaIqbLMB3aieTm6ibzHjdgnRnaurlt7YqlrJwaAuVOLoRWOJAxzYA2MgaQCySBcQ4MYh1LEjRYuXMMS1NnoMf3QguyyOW80EYpK3LDxZWTQ7Fez2jQYI3M6A302GlowTzkhvqgjfqS5MJ+YCYiIbl6ely5hA80WNZyoPvuQAsSyIWhrTYiVxQDYyJxBgbF3O7YsC7QovG8f/zW+/WX3gcP/x7ojuvQgh7kEtBWB5EJioJmSJ6LhjhBs+WymofP3n5+cpe59+a+IlDqFOg30oNcBNrKVnZU51pUcH4OuWl9QYcLanE+ePPo3l3ii7Kh1FnQr8siGjEE2qtz0HCHyTE8FBVFXCIm3jAD9NgsK7xncXK9fw/0JOg33oSMtR+0mbG7I9LhJUW0JyRkzCnQY5Nc5/6DFyxO5t6Hvwd6DPQrS8gUbPCvzd8o5mWWHfTYXZ/nu0d3672Q/x7oFtDNWEIu5IN/bcgAZHIdoMuKujx7WZ7EyyaBrl8Ieo3sQY5V4h+a4xAT7wU9Ji2re/9keVK9aoFSp6G5VGxg//LEiZlBsyIjnWr3JcE5UGFMzgqVxuaLUWPDVg85WMyXxpbG9Ykp19j8VTb2i1HFfCGfGZ/VdpLCfD/QY+7U2oI+vav04pkiUI3nJmPn7DAS1nHdTkUlhncB2PsV+dB5lqhFaovlrcjl4kPsDe4nh7orF3bH2QWkQpQu9WfM3GIfgN1s/SWInMP624jxdm+PmZvtAyEWNjPhjuqLbz8qU72P7yp9+dgs0HPwN86uhAfrBIZAhX0WcumR0G+2CwVHUXEQ7LckbcAalkQWiO4cVu0fDTB6uoMuHUR2KFv3kMudQ1WmCLjJDsZTZG9DyE3rZqemVTLxoaag9548/v3jleJOSdO5KTXLhQrmPwPliyOX8I1Om5Ay9TdCjWjaggo9EbIqxhcU5kN0Y236hoGiX0NEGa0zAKAjiKoGJKGTTFdpPtk8coQex9p/bYJMvBV3R89fPvjw8cOz3pcvnn+TmwV6GRrrm5mCSpnqJodFerP7hSWsZYgCNcwjoQrrEjHL8/iW5G3eofXLTUW+cgZpVxIAZtVmT6tnm4hceDRwg5DzuPkxdLAsvP6hMub3379uGujFI43zDKGaIdURtpCBDKNCAQR/XEJ1nhgw0/mactSCCoGYShpjK9MSR3VjbQBFUlgvML7hyKWhauZqmfj+guX5qSbEpoHuOwQN+IqqOz/CCb95kZuiVj/PSGBSEw3YiMvP7nTE9i1yoZKUMSoeNUllO4B/EaoyJGrv5hxJYJaQJ+N8nyYdkIkH/CPprawp0C23oIGuHKopsyHGvwtGgElL2Fgf9jmRFhukun4EG1mLyGwWALhdqCo4EABGWUX6UagyxtVuSQ7vkoneu8w3bYHuOAvqOumYOjwBs2tEziGhtAT+iCsqVBvDlIH8iwkD7U3QYzEh4ao2ORvGJiLV5MV1HB0AEPWgqgEdlbQHIMf+c7oFuS5g9kyViVfiKK8t0PUnQd0ok4hq//isE8DZHRkXXsSOOgGsYSkkMnmrhOTTszrKESsKPYtjWe8wWllDNfio4gXzabMBiVD1H0POUyltMh4KhTaiUAr9Nmh+pYxkmxazgo7nLyvFbcCsXFUbqJb3UOr4zKaP44cn7by33Ub4zS/V1mFJPwBfxKEMdDQ9AITdUOGfiMJEG1TMyyEhFeZ0On3ZBC1zwAkV48hVjaB8AzL0BSKvfAg+siR2bZ4dmO0b1AN9/OW1hkAbf61kRq5sBwUvUpZRv5cYx9JAlQNvZgOWdJGcuqFisUTzrA5CZ4n81jofKkrILQKmSF4PqP7I5ZWz5+ps9DT07c/2zrs5iSAM43uAdAIJXSEaUCAJwURibLFF7L333nXsvfeuY5lxzrH3Fnv/eAqRu2d37/DUm8Fx+P0nk+Pk4d23X4L15pt76oJqDvOQLU8mPBMUU5rJIjupdwfB0t1SWAjLQhkKFzspnzy8aGW1IZHxd07wIAGpouuHngEZAMeD9/6NRCL/GALw5ibw4MU77YJeVRkriTgH4+mO9jnBCBsdEIHppLHDbJQ/pwV2EfLWXdMHA7qUMnr74TEoHA2LwuBlSk6UCBMkDe9KChg98ivgHaYuuIE8eUpVno++aBZ049FfCup3ExY7RoHpcYKC4upDawO8TQ14DEitRxvpAl3sGK/4xQULYXqM7KcNWWiGSgwgiD0o//jPJFR5M2/EZnrMyfSaHj6/pyKo1vaIBxWrZr1oGkxCmEgUqsJg4SSbDcqmg8ljnTcvSROczXrF2OgofEUB+R39PikmWWSrSxIKMPF4QWE5IGZqcOTJrNW+eHiT5uMTjYKqPAs2RASCjWla0mawvCoiEYaUJu/vbRClgtlSgkbAWQRcRGk2bIkyYXuwE8pWbg2QD1f1hbMhGbMwAz/TnmFMe7nLRPHYP9Em6LVtyiM4QQQMfVJeAjQ6wPKUcy173u780D5zEjzyKCgdjUNJoihoUyzva8Hi2o3FGAf3HRdnOvmiRP7MuMZ3QDcKmMdq9eU+q+iHt5oE3a/cHqlfISJCQ3cbZiOy3Fb4fw0G8ynoDt9KSs3CxxupfTqhjxEPCpPGu+GbGKq0xRh2EYqBBqpUqhkuJ/Wu0jPkN1yH+dEdLYKuBUGRiR62ERqTPqkNDl532RN5W+i6xIZJuJsAGOUb8x9MFrgjgB2a/rg/RV9olcKPO4SpAI0vQ7UNJkrXe6oJspgT9M7jTkbQB6+1CLpGJbM3hk1sB62xqGirXNEJYUlmLqXB+ryPj3IZAt20cOLJVnEN3cAT0bO4Zgd2lWmcsvW2E+JNyC6ImyHzY3nWj8KhB0FZ1PbCne1s160hyptEMAVGzaQ01Q1Q+BnR8GYwm5DVIiaHMmOsdOlorIPsv9iCtzdChpBm7aIdS6W45NQdTC64WXGv6dttOnl6o0VQ1b1w5wQHO7Ow/TQJAUdMEtA7z/joHxNmECCdd40wQSJDVRb5ehnobpFziEJMciZAZSNX02HPOypI/2AyQcya8NjTuyOvXmoQdCVRwxXtwYx2uoZlLig8+0Ey1xMKZyeTlo4hQBIMrxDsxmHDVWZKf7h3Xnif7KUdUloOyZmQICwpi+QjbHLSZWJ87VTsNSF3O6mw9FaDoAeIOq0Bv4hUddXTvRRNwjcdQzeTlqYoq8Ga306Is4dy5wB7nT3ywg+E7CIqDVQ9ckQbSgDGDVelfX506cikQTfUFH2qZS6PbCcl8CaLDR9oecS7ySYRUM7Wo0ye31JLgMGM0lm4EoKvqx3uHbDTtW1GyuBjcBAgJnEB1O+OmKRKhdCsh7ye5t4rEPTpXQ2Cri29aO9LCCJzlFuD8sGbqWhPnmQpQVs7mKn0RDnImdLgav0iY5AJsFhohmKLj8Urud2WAX2li+OE5hTVDVXtkzzUIujG3aQ03QVwmVPonpglppgLdRvInOymiWB4baJMXaFOkKNfDj6qWYR03wdL03TaMw5TKx5p2OhJ5IpmECYMi+h18K/8AFS7hW68TEpT3cCkJePBJJyKudDwOPRKWOfmzsEZzStv7ylgK7RIEu5rLVxvVKx4uylGNH7gb8oUT0bORhgW9qaO+UdYEXkCNeh9LT50zUrNe3b8JLw/LNvAYCPhYp4FFepcUmY4wwDKM2FaTEgn2UudjHShjhRlJB/ipOoznloLN0YcTRjGbqYD0f2b9188udOVOX3+3Si/9MCvBO0GB80LT9DRJuHOsCmNG3xgrl5quwShVMgy0UyYKe+tGNhFFDR5wSYZsgVUVn0IBwlxBjpnE9e7e9D56s3XO3e+vsI+3nPVPBTZzzXwshOw0E056CjvszJJDhaAWGbXdMcZk1cab8LbuTCaYS3uGm3Bjiy3NNIiu26T7ECmqD2tghjMLm6VcTaVzn8qZPO373/4+PED6nn79R0tgm5nq3n7+GAuMMBZNNAVIr2dFbPQizl8sM1ku95ngoAbeXlFjakcVl6tXVcaYN+jq1DwtaGeLQMg3WIHdOBBcj7Cgx4KH75DTs9Sbdcj9zEmqQu6lh0r2fqLohAM9TVHmmORnri4NSSeD94d8uGpgTIRhHLymaloqEoEeu0I4q7Yz0XdOvypUPfJqcmJDOZqTaOdBPJf2g0acVLsVHniD3G0uwjLukGMC1XkNs5BeEHVx0rVBlEZS21BOQEXsiR6YKjpwl7HvxNGftzCUcfQ9jNQVcveQojA0woSjXb1Z1LxWHAcGVmiE4pJkyZB17BjpRmiCn2MdENMTHgVp1CjIeNSxVDs8MZzYkmmFxWINInYzQSVYSLN4zIx21Q8hykXirUR8lxTg5lvj7jwoCIhNwR91iRqRJkoTJnU9awpXmkQS9GtXnozg8JjxlGLCLvkiuREIGgjHCMW0tOPD7dv8jzAA19a0Ot08Rl1KCvQv5md2TqScBVEdNmzOqEIomgIu+BKdYJdUQqWIZjR0VCmjFNeIsBdKp4lB5lq8/ltXs9P7zQN6fjfjuNqg4CAxV/SzmZR/iz4CTqlwXSSR8hFsPekjiUgGxQuQ0hzAhyz4NgLMYvUIjPPpLNsR+R1JyPpw8/vb2gW9MwVStChVqXjHmgtjkaYuol/Wn0IAeJjgvyuZ3vWjgtdahgG4/Mk2Qw+kPCTNIz32lxEEdDcM9ROeObN4qR6+ebj04c/Rb394Omnu+qLDjxrdxPEVR/YYQG7Ejo8O8Kt3qJJ1JkkIE1xTpdfHs1E2bqQCXr3lqrEQKOKy87I9xUMnsEpG378ZJN0h0xMUjknvWgdQxSpb5Bvjs+CENy847nz9fHzR52dzzo7P77WsNuErOHGSt5Yot8Of4vVY20IVfVra7bDGY6YJWKQu06WX84ShnS4T1XI2mTxtPhz3Sf6mM/rgTqheVw3v9Xi8FhDPdrzd0WmmCUabZIfCEsvTmDzIT5R9SiHrXU31Lj3/t3Le9rWGRHFvfC4u3ZAKhqrrnGRv8eVjqUikVSt21ZiRiEavPnbRiNjUtFqL9EH1y4Tt6/HZ02a0C7oSlJGZnTAbqf+1Lawj0jwLNRb0AOkjMCMpS/RG3uzlSt1eTbrLegZUj5s3bBprDNG3PB3BJxEmdl6C7qRlA9smg4g+hI3h0R+4s0zTBdBkW2kbESgGZokupKd3oG9GDdRY/3iBWdHzRq0XD9BL5FygQV6Vb2ersQdMIiAw0xKsuT06sULNs+e31sXQU+ScmHshd1W/eSc2Dck0D1AG/klY+dMmrf+8IJNoOofCnqIlAtfH2q0pxcxpmMmDNb8ZU2bOmfJvCMLN80aNGwkLaz2SmnjNVIuBspB3hTWNXdAhOlp8ttMWn9q4c6zswaN/C1Bl649c+D40b2kXCShnRkh+hGgzvvwNPkzpi05ve7I+c2zB/XWJOia7YdOXrgylpSRCMwm3EQ/mlHPIX8X7abu2bph3aqDs4eVFnTN/uNHd++dRsrLeKoZqB/eICSgWaIL0+atXrVz9qh9w5ahoIVjvv3cid3/xp8EoXY79QKHsILVTPRk6qT1FxdtmTtqWO+fgq7df+jEhWPTyD9CCw5B9SQidB33vrV2ojsjJm34kbXOHXXr6oETR3f/U3//p62uSEBHFyo97FgFTVK9GTtizp69/5SYeYwyRFe8bYam/pPT5f8lcv8NSXPMSCpUqFChQoUKFSpUqFChQoUK/yHfAYTLavSW/S0uAAAAAElFTkSuQmCC)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous Javascript ](/docs/cesdk/ui/solutions/photo-editor/javascript/)[ Next Angular ](/docs/cesdk/ui/solutions/photo-editor/angular/) Use CreativeEditor SDK in small viewports - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/guides/smallviewport/?platform=web&language=js # Use CreativeEditor SDK in small viewports CreativeEditor SDK is a tool that can be utilized in a multitude of ways. One such way is by embedding it into small container elements or employing it on devices with limited screen real estate, such as smartphones. The UI of the CreativeEditor SDK seamlessly adjusts to the available space, much like any other modern web application, without sacrificing functionality. Moreover, it provides a certain degree of customizability, allowing you to tailor the behavior of the UI to your specific needs. By default, CreativeEditor SDK is configured to use an optimized layout for small viewports. This layout changes the UI to: * Render the dock on the bottom * Open all panels from the bottom, taking up the bottom half of the screen * Use the [large UI scaling](/docs/cesdk/ui/guides/theming/) All configuration options related to the panel layout are ignored in favor of the layout optimization for small viewports as described above. Drop Shadow - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-drop-shadow/?platform=web&language=javascript # Drop Shadow In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify an block's drop shadow through the `block` API. Drop shadows can be added to any shape, text or image. One can adjust its offset relative to its block on the X and Y axes, its blur factor on the X and Y axes and whether it is visible behind a transparent block. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` supportsDropShadow(id: DesignBlockId): boolean ``` Query if the given block has a drop shadow property. * `id`: The block to query. * Returns True if the block has a drop shadow property. ``` if (engine.block.supportsDropShadow(block)) { ``` ``` setDropShadowEnabled(id: DesignBlockId, enabled: boolean): void ``` Enable or disable the drop shadow of the given design block. Required scope: 'appearance/shadow' * `id`: The block whose drop shadow should be enabled or disabled. * `enabled`: If true, the drop shadow will be enabled. ``` engine.block.setDropShadowEnabled(block, true); ``` ``` isDropShadowEnabled(id: DesignBlockId): boolean ``` Query if the drop shadow of the given design block is enabled. * `id`: The block whose drop shadow state should be queried. * Returns True if the block's drop shadow is enabled. ``` const dropShadowIsEnabled = engine.block.isDropShadowEnabled(block); ``` ``` setDropShadowColor(id: DesignBlockId, color: Color): void ``` Set the drop shadow color of the given design block. Required scope: 'appearance/shadow' * `id`: The block whose drop shadow color should be set. * `color`: The color to set. ``` engine.block.setDropShadowColor(block, { r: 1.0, g: 0.75, b: 0.8, a: 1.0 }); ``` ``` getDropShadowColor(id: DesignBlockId): Color ``` Get the drop shadow color of the given design block. * `id`: The block whose drop shadow color should be queried. * Returns The drop shadow color. ``` const dropShadowColor = engine.block.getDropShadowColor(block); ``` ``` setDropShadowOffsetX(id: DesignBlockId, offsetX: number): void ``` Set the drop shadow's X offset of the given design block. Required scope: 'appearance/shadow' * `id`: The block whose drop shadow's X offset should be set. * `offsetX`: The X offset to be set. ``` engine.block.setDropShadowOffsetX(block, -10); ``` ``` setDropShadowOffsetY(id: DesignBlockId, offsetY: number): void ``` Set the drop shadow's Y offset of the given design block. Required scope: 'appearance/shadow' * `id`: The block whose drop shadow's Y offset should be set. * `offsetY`: The X offset to be set. ``` engine.block.setDropShadowOffsetY(block, 5); ``` ``` getDropShadowOffsetX(id: DesignBlockId): number ``` Get the drop shadow's X offset of the given design block. * `id`: The block whose drop shadow's X offset should be queried. * Returns The offset. ``` const dropShadowOffsetX = engine.block.getDropShadowOffsetX(block); ``` ``` getDropShadowOffsetY(id: DesignBlockId): number ``` Get the drop shadow's Y offset of the given design block. * `id`: The block whose drop shadow's Y offset should be queried. * Returns The offset. ``` const dropShadowOffsetY = engine.block.getDropShadowOffsetY(block); ``` ``` setDropShadowBlurRadiusX(id: DesignBlockId, blurRadiusX: number): void ``` Set the drop shadow's blur radius on the X axis of the given design block. Required scope: 'appearance/shadow' * `id`: The block whose drop shadow's blur radius should be set. * `blurRadiusX`: The blur radius to be set. ``` engine.block.setDropShadowBlurRadiusX(block, -10); ``` ``` setDropShadowBlurRadiusY(id: DesignBlockId, blurRadiusY: number): void ``` Set the drop shadow's blur radius on the Y axis of the given design block. Required scope: 'appearance/shadow' * `id`: The block whose drop shadow's blur radius should be set. * `blurRadiusY`: The blur radius to be set. ``` engine.block.setDropShadowBlurRadiusY(block, 5); ``` ``` getDropShadowBlurRadiusX(id: DesignBlockId): number ``` Get the drop shadow's blur radius on the X axis of the given design block. * `id`: The block whose drop shadow's blur radius should be queried. * Returns The blur radius. ``` const dropShadowBlurRadiusX = engine.block.getDropShadowBlurRadiusX(block); ``` ``` getDropShadowBlurRadiusY(id: DesignBlockId): number ``` Get the drop shadow's blur radius on the Y axis of the given design block. * `id`: The block whose drop shadow's blur radius should be queried. * Returns The blur radius. ``` const dropShadowBlurRadiusY = engine.block.getDropShadowBlurRadiusY(block); ``` ``` setDropShadowClip(id: DesignBlockId, clip: boolean): void ``` Set the drop shadow's clipping of the given design block. (Only applies to shapes.) * `id`: The block whose drop shadow's clip should be set. * `clip`: The drop shadow's clip to be set. ``` engine.block.setDropShadowClip(block, false); ``` ``` getDropShadowClip(id: DesignBlockId): boolean ``` Get the drop shadow's clipping of the given design block. * `id`: The block whose drop shadow's clipping should be queried. * Returns The drop shadow's clipping. ``` const dropShadowClip = engine.block.getDropShadowClip(block); ``` Configure the UI Elements in CreativeEditor SDK - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/guides/elements/?platform=web&language=js # Configure the UI Elements in CreativeEditor SDK In this example, we will show you how to theme [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-ui-elements?title=IMG.LY%27s+CE.SDK%3A+Configure+Ui+Elements&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-ui-elements). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-ui-elements?title=IMG.LY%27s+CE.SDK%3A+Configure+Ui+Elements&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-ui-elements) ## Element Configurations The CE.SDK User Interface is build from the ground up to be highly adaptable to multiple use-cases. This includes hiding or showing individual ui elements, repositioning panels or definining the content of the inspector. ``` elements: { view: 'default', // 'default' or 'advanced' navigation: { show: true, // 'false' to hide the navigation completely position: 'top', // 'top' or 'bottom' action: { close: true, // true or false back: true, // true or false load: true, // true or false save: true, // true or false export: { show: true, format: ['application/pdf'] }, download: true, // true or false custom: [ { label: 'common.custom', // string or i18n key iconName: '@imgly/icons/Essentials/Download', // icon id from our 'Essentials' set, or a custom icon id callback: () => { // callback signature is `() => void | Promise` // place custom functionality here } } ] } }, panels: { inspector: { show: true, // true or false position: 'left', // 'left' or 'right' floating: false // true or false }, assetLibrary: { show: true, // true or false position: 'left' // 'left' or 'right' }, settings: { show: true // true or false } }, dock: { iconSize: 'large', // 'large' or 'normal' hideLabels: false // false or true }, libraries: { insert: { floating: true, // true or false autoClose: false // true or false }, replace: { floating: true, // true or false autoClose: false // true or false } }, blocks: { opacity: false, // true or false transform: false, // true or false '//ly.img.ubq/graphic': { adjustments: true, // true or false filters: false, // true or false effects: false, // true or false blur: true, // true or false crop: false // true or false }, '//ly.img.ubq/page': { manage: true, format: true, maxDuration: 30 * 60 } } } ``` ### View Two different views of the editor are available: The 'advanced' view always shows an inspector panel to the side of the canvas. The 'default' view hides the inspector panel until it is needed, and uses a minimal inspector bar on top of the canvas instead. * `view: string` is used to toggle between `'advanced'` and `'default'` view. ``` view: 'default', // 'default' or 'advanced' ``` ### Navigation * `show: boolean` is used to show or hide the complete navigation * `position: string` is used to set the location of the navigation bar to either `top` or `bottom` . ``` navigation: { show: true, // 'false' to hide the navigation completely position: 'top', // 'top' or 'bottom' action: { close: true, // true or false back: true, // true or false load: true, // true or false save: true, // true or false export: { show: true, format: ['application/pdf'] }, download: true, // true or false custom: [ { label: 'common.custom', // string or i18n key iconName: '@imgly/icons/Essentials/Download', // icon id from our 'Essentials' set, or a custom icon id callback: () => { // callback signature is `() => void | Promise` // place custom functionality here } } ] } }, ``` #### Actions (Buttons) `action` configures the available buttons in the navigation and call-to-action menu. * `close: boolean` shows or hides all `close` buttons. * `back: boolean` shows or hides all `back` buttons. * `load: boolean` shows or hides all `load` buttons. * `save: boolean` shows or hides all `save` buttons. * `export.show: boolean` shows or hides all `export` buttons. * `export.format: 'application/pdf' | 'image/png'` an array of mime types available for export. Supported are `application/pdf` and `image/png`. If not set it will add all supported mime types. * `download: boolean` shows or hides all `download` buttons. ``` action: { close: true, // true or false back: true, // true or false load: true, // true or false save: true, // true or false export: { show: true, format: ['application/pdf'] }, download: true, // true or false ``` #### Custom Call-To-Action Buttons `action.custom` receives an array of custom actions. These will appear as buttons in the call-to-action menu (that also contains the 'save', 'export' and 'download' actions). The first item in the array will be shown in the navigation bar (in place of the 'save' action); all other items will be listed in the dropdown menu. A custom actions has the following properties: * `label` a string, which can be an i18n key that will be looked up in the translation table. * `iconName` defines the icon shown on the button. You can use any of the icon ids found within our IMG.LY Icon Set 'Essentials' ([see full list here](/docs/cesdk/ui/customization/api/icons/#set-essentials)), or any icon id that you've added yourself using the `addIconSet` API ([see details here](/docs/cesdk/ui/customization/api/icons/#add-new-icon-set)). * `callback` a custom callback function with the signature `() => void | Promise`. This function is called when the button is clicked. If the function returns a promise, the button is disabled and a spinner is shown on the button until the promise resolves. ``` custom: [ { label: 'common.custom', // string or i18n key iconName: '@imgly/icons/Essentials/Download', // icon id from our 'Essentials' set, or a custom icon id callback: () => { // callback signature is `() => void | Promise` // place custom functionality here } } ] ``` ### Panels * `inspector.position: string` is used to set the location of the inspector to either `left` or `right`. * `inspector.show: boolean` shows or hides the inspector. * `inspector.floating: boolean` configures if the inspector panel is floating over the canvas or set aside of it. * `assetLibrary.position: string` is used to set the location of the asset library to either `left` or `right`. * `assetLibrary.show: boolean` shows or hides the asset library. * `settings: boolean` shows or hides the settings panel. ``` panels: { inspector: { show: true, // true or false position: 'left', // 'left' or 'right' floating: false // true or false }, assetLibrary: { show: true, // true or false position: 'left' // 'left' or 'right' }, settings: { show: true // true or false } }, ``` ### Dock * `iconSize: string` is used to set the dock button icon size to `large` (24px) or `normal` (16px). * `hideLabels: boolean` shows or hides the labels inside the docks buttons. ``` dock: { iconSize: 'large', // 'large' or 'normal' hideLabels: false // false or true }, ``` ### Libraries * `insert.floating: boolean` configures if the asset library panel is floating over the canvas or set aside of it. * `insert.autoClose: boolean | () => boolean` configures if the asset library panel is closed after an asset was inserted (default is `false`). * `replace.floating: boolean` configures if the image replacement library panel is floating over the canvas or set aside of it. * `replace.autoClose: boolean | () => boolean` configures if the asset library panel is closed after an asset was replaced (default is `true`). ``` libraries: { insert: { floating: true, // true or false autoClose: false // true or false }, replace: { floating: true, // true or false autoClose: false // true or false } }, ``` ### Blocks * `opacity: boolean` shows or hides the `opacity` section in the inspector ui for every block. * `transform: boolean` shows or hides the `transform` section in the inspector ui for every block. * `adjustments: boolean` shows or hides the `adjustments` section in the inspector ui for the image block. * `filters: boolean` shows or hides the `filters` section in the inspector ui for the image block. * `effects: boolean` shows or hides the `effects` section in the inspector ui for the image block. * `blur: boolean` shows or hides the `blur` section in the inspector ui for the image block. * `crop: boolean` shows or hides the `crop` section in the inspector ui for the image block. ``` blocks: { opacity: false, // true or false transform: false, // true or false '//ly.img.ubq/graphic': { adjustments: true, // true or false filters: false, // true or false effects: false, // true or false blur: true, // true or false crop: false // true or false }, '//ly.img.ubq/page': { manage: true, format: true, maxDuration: 30 * 60 } } ``` #### Pages * `manage: boolean` if `false` removes all UI elements to add/duplicate/delete pages for every role. * `format: boolean` if `false` removes all UI elements to change the format of pages for every role. * `maxDuration: number` controls the maximum allowed duration of a page, if in video mode ``` '//ly.img.ubq/page': { manage: true, format: true, maxDuration: 30 * 60 } ``` Kind - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-kind/?platform=web&language=javascript # Kind In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a and query the kind property of design blocks through the `block` API. The `kind` of a design block is a custom string that can be assigned to a block in order to categorize it and distinguish it from other blocks that have the same type. The user interface can then customize its appearance based on the kind of the selected blocks. It can also be used for automation use cases in order to process blocks in a different way based on their kind. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` setKind(id: DesignBlockId, kind: string): void ``` Set the kind of the given block, fails if the block is invalid. * `id`: The block whose kind should be changed. * `kind`: The new kind. * Returns The block's kind. ``` engine.block.setKind(text, 'title'); ``` ``` getKind(id: DesignBlockId): string ``` Get the kind of the given block, fails if the block is invalid. * `id`: The block to query. * Returns The block's kind. ``` const kind = engine.block.getKind(text); ``` ``` findByKind(kind: string): DesignBlockId[] ``` Finds all blocks with the given kind. * `kind`: The kind to search for. * Returns A list of block ids. ``` const allTitles = engine.block.findByKind('title'); ``` Basic Configuration Settings of CreativeEditor SDK - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/configuration/basics/?platform=web&language=js # Basic Configuration Settings of CreativeEditor SDK In this example, we will show you how to configure the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-basics?title=IMG.LY%27s+CE.SDK%3A+Configure+Basics&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-basics). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-basics?title=IMG.LY%27s+CE.SDK%3A+Configure+Basics&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-basics) ## Basic Configuration The CE.SDK comes with a set of basic configuration parameters that let's you influence its look & feel. ``` const config = { license: 'YOUR_API_KEY', userId: 'USER_ID', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.43.0/assets', locale: 'en', // 'de' theme: 'light', // 'dark' role: 'Creator', // 'Adopter' 'Viewer' callbacks: { onUpload: 'local' }, // Enable local uploads in Asset Library. logger: (message, logLevel) => { console.log(`${logLevel}: ${message}}`); } }; ``` * `license: string` A license key that is unique to your product. ``` license: 'YOUR_API_KEY', ``` * `userId: string` An unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. ``` userId: 'USER_ID', ``` * `baseURL: string` defines the root for all assets that are used by the CE.SDK. It defaults to our CDN, but should be changed in production environments. See [Serving Assets](/docs/cesdk/ui/guides/assets-served-from-your-own-servers/) for an in-depth explanation of how to serve assets from your own servers. ``` baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-js/1.43.0/assets', ``` * `locale: string` defines the current language in the user interface. The CE.SDK is shipped with locales for English (`en`) and German (`de`), but any other language is possible. See [Internationalization](/docs/cesdk/ui/guides/i18n/) for an in-depth explaination of how to add new languages or modify the predefined translations. ``` locale: 'en', // 'de' ``` * `theme: string` sets the active theme. The CE.SDK includes predefined `dark` or `light` themes but is also highly customizable to match any style. See [Theming](/docs/cesdk/ui/guides/theming/) for an in-depth explanation of how to create your own theme. ``` theme: 'light', // 'dark' ``` * `role: string` sets the initial role to either `'Creator'`, `'Adopter'`. See [Roles](#roles) for more details on roles in the editor and how to use them properly. ``` role: 'Creator', // 'Adopter' 'Viewer' ``` * `logger: (message: string, logLevel: 'Error'|'Warning'|'Info') => void` lets you redirect log output from the CreativeEngine/CreativeEditor SDK ``` logger: (message, logLevel) => { console.log(`${logLevel}: ${message}}`); } ``` ## Roles User roles allow the CE.SDK to change and adapt its UI layout and functionality to provide the optimal editing experience for a specific purpose. ### Creator The `Creator` role is the most powerful and least restrictive role that is offered by the CE.SDK. Running the editor with this role means that there are no limits to what the user can do with the loaded scene. Elements can be added, moved, deleted, and modified. All types of controls for modifying the selected elements are shown inside of the inspector. ### Adopter The `Adopter` role allows new elements to be added and modified. Existing elements of a scene are only modifiable based on the set of constraints that the `Creator` has manually enabled. This provides the `Adopter` with a simpler interface that is reduced to only the properties that they should be able to change and prevents them from accidentally changing or deleting parts of a design that should not be modified. An example use case for how such a distinction between `Creator` and `Adopter` roles can provide a lot of value is the process of designing business cards. A professional designer (using the `Creator` role) can create a template design of the business card with the company name, logo, colors, etc. They can then use the constraints to make only the name text editable for non-creators. Non-designers (either the employees themselves or the HR department) can then easily open the design in a CE.SDK instance with the `Adopter` role and are able to quickly change the name on the business card and export it for printing, without a designer having to get involved. ### Role customization Roles in the CE.SDK are sets of global scopes and settings. When changing the role via the `setRole` command in the EditorAPI, the internal defaults for that role are applied as described in the previous sections. The CE.SDK and Engine provide a `onRoleChanged` callback subscription on the EditorAPI. Callbacks registered here are invoked whenever the role changes and can be used to configure additional settings or adjust the default scopes and settings. ``` instance.engine.editor.onRoleChanged((role) => { if (role === 'Adopter') { // Enable the filter tab in the appearance panel when previewing the // design in the Adopter role. instance.engine.editor.setGlobalScope('appearance/filter', 'Allow'); } }); ``` Vue.js Video Editor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/video-editor/vuejs/ CESDK/CE.SDK/Web Editor/Solutions/Video Editor # Vue.js Video Editor SDK CreativeEditor SDK provides a powerful Vue.js library designed for creating and editing videos directly within the browser. This CE.SDK configuration is highly customizable and extendible, offering a full suite of video editing features such as splitting, cropping, and composing clips on a timeline. [Explore Demo](https://img.ly/showcases/cesdk/video-ui/web) ## Key Capabilities of the Vue.js Video Editor SDK ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transform Perform operations like video cropping, flipping, and rotating. ![images](/docs/cesdk/static/TrimSplit-9f679312338d9c81b5863f540fb13f39.png) ### Trim & Split Easily set start and end times, and split videos as needed. ![images](/docs/cesdk/static/MergeVideos-9f08894a0142a48861b0bf595804d314.png) ### Merge Videos Edit and combine multiple video clips into a single sequence. ![images](/docs/cesdk/static/VideoCollage-ed31587fdccad3fe7f335c6e70216355.png) ### Video Collage Arrange multiple clips on one canvas. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Processing Execute all video editing operations directly in the browser, with no need for server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit videos within your Vue.js application. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Add new functionalities seamlessly using the plugins and engine API. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Design and integrate custom UIs tailored to your application. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Libraries Incorporate custom assets like filters, stickers, images, and videos. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Green Screen Support Apply chroma keying for background removal. ![images](/docs/cesdk/static/Templating-5fc837e60dafb163c824ed6684fc7b0c.png) ### Templating Create design templates with placeholders and text variables for dynamic content. ## Browser Support Video editing mode relies on modern web codecs, supported only in the latest versions of Google Chrome, Microsoft Edge, or other Chromium-based browsers. ## Prerequisites [Ensure you have the latest stable version of **Node.js & NPM** installed](https://www.npmjs.com/get-npm) ## Supported File Types Creative Editor SDK supports loading, editing, and saving **MP4 files** directly in the browser. Additionally, the following file types can be imported for use as assets during video editing: * MP3 * MP4 (with MP3 audio) * M4A * AAC * JPG * PNG * WEBP Scenes or individual assets from the video can be exported as JPG, PNG, Binary, or PDF files. ## Getting Started If you're ready to start integrating CE.SDK into your Vue.js application, check out the CE.SDK [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/). In order to configure the editor for a video editing use case consult our [video editor UI showcase](https://img.ly/showcases/cesdk/video-ui/web) and its [reference implementation](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-video-ui/src/components/case/CaseComponent.jsx). ## Understanding CE.SDK Architecture & API The sections below provide an overview of the key components of the CE.SDK video editor UI and its API architecture. If you're ready to start integrating CE.SDK into your Vue.js application, check out our [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/) or explore the [Essential Guides](https://img.ly/docs/cesdk/ui/guides/video). ### CreativeEditor Video UI The CE.SDK video UI is designed for intuitive video creation and editing. Below are the main components and customizable elements within the UI: ![](/docs/cesdk/7a1d859e523b71cad4257ca45524e689/Simple-Timeline-Mono.png) * **Canvas:** The main interaction area for video content. * **Dock:** Entry point for interactions not directly related to the selected video block, often used for accessing asset libraries. * **Canvas Menu:** Access block-specific settings such as duplication or deletion. * **Inspector Bar:** Manage block-specific functionalities, like adjusting properties of the selected block. * **Navigation Bar:** Handles global scene actions like undo/redo and zoom. * **Canvas Bar:** Provides tools for managing the overall canvas, such as adding pages or controlling zoom. * **Timeline:** The core video editing control, where clips and audio are arranged over time. Learn more about interacting with and manipulating video controls in our [video editor UI guide](https://img.ly/docs/cesdk/ui/guides/video). ### CreativeEngine CreativeEngine is the core of CE.SDK, responsible for managing the rendering and manipulation of video scenes. It can be used in headless mode or integrated with the CreativeEditor UI. Below are key features and APIs provided by the CreativeEngine: * **Scene Management:** Programmatically create, load, save, and modify video scenes. * **Block Manipulation:** Create and manage video elements such as shapes, text, and images. * **Asset Management:** Load assets like videos and images from URLs or local sources. * **Variable Management:** Define and manipulate variables within scenes for dynamic content. * **Event Handling:** Subscribe to events such as block creation or updates for dynamic interaction. ## API Overview The APIs of CE.SDK are grouped into several categories, reflecting different aspects of scene management and manipulation. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/) * **Creating Blocks**: ``` const block = engine.block.create('shapes/star'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) Variables allow dynamic content within scenes to programmatically create variations of a design. * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source) * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API**](https://img.ly/docs/cesdk/engine/api/events/) * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ## Customizing the Vue.js Video Editor CE.SDK provides extensive customization options to adapt the UI to various use cases. These options range from simple configuration changes to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object**: When initializing the CreativeEditor, you can pass a configuration object that defines basic settings such as the base URL for assets, the language, theme, and license key. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization**: Customize the language and labels used in the editor to support different locales. ``` const config = { i18n: { en: { variables: { my_custom_variable: { label: 'Custom Label' } } } } }; ``` * [Custom Asset Sources](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): Serve custom video or image assets from a remote URL. ### UI Customization Options * **Theme**: Choose between predefined themes such as 'dark' or 'light'. ``` const config = { theme: 'dark' }; ``` * **UI Components**: Enable or disable specific UI components based on your requirements. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations Learn more about extending editor functionality and customizing its UI to your use case by consulting our in-depth [customization guide](https://img.ly/docs/cesdk/ui/customization/guides/). Here is an overview of the APIs and components available to you. ### Order APIs Customization of the web editor's components and their order within these locations is managed through specific Order APIs, allowing the addition, removal, or reordering of elements. Each location has its own Order API, e.g., `setDockOrder`, `setCanvasMenuOrder`, `setInspectorBarOrder`, `setNavigationBarOrder`, and `setCanvasBarOrder`. ### Layout Components CE.SDK provides special components for layout control, such as `ly.img.separator` for separating groups of components and `ly.img.spacer` for adding space between components. ### Registration of New Components Custom components can be registered and integrated into the web editor using builder components like buttons, dropdowns, and inputs. These components can replace default ones or introduce new functionalities, deeply integrating custom logic into the editor. ### Feature API The Feature API enables conditional display and functionality of components based on the current context, allowing for dynamic customization. For example, you can hide certain buttons for specific block types. ## Plugins Customize the CE.SDK web editor during initialization using the outlined APIs. For many use cases, this is sufficient, but for more advanced scenarios, plugins are useful. Follow our [guide on building plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) or explore existing plugins like: [**Background Removal**](https://img.ly/docs/cesdk/ui/customization/plugins/backgroundRemoval/): Adds a button to the canvas menu to remove image backgrounds. [**Vectorizer**](https://img.ly/docs/cesdk/ui/customization/plugins/vectorizer/): Adds a button to the canvas menu to quickly vectorize a graphic. ## Framework Support CreativeEditor SDK’s video editing library is compatible with any Javascript including, React, Angular, Vue.js, Svelte, Blazor, Next.js, Typescript. It is also compatible with desktop and server-side technologies such as electron, PHP, Laravel and Rails. [ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/solutions/video-editor/react/)[ Angular ](/docs/cesdk/ui/solutions/video-editor/angular/)[ Vue ](/docs/cesdk/ui/solutions/video-editor/vuejs/)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABfvSURBVHgB5V0LlFTVld33VnXT3ZKofJQoKo4QmuYTicT/KKhR/I1GE5xEnYiaAN2OjslkObMmOCROVhYr46ijzU8UNRoNGDUqog4qEsJo4o9Pf9COoBBoaDBokC66672bc17xEZqqe8+r96qqca9V3dXd971+9c6795579jn7KnweUN+wABoDuvzeYDOU2kqv9TCmDb7/AXRiA7RZj7KytRg/cAv9zUc3hsKBjpmrquF5K+hd0qG1T0bfRnflI/pOhjcbyMDN9CSsBBKrUOavw7ohf8YUlUY3Qfcw8NyGcmzR58L4lagdOs/9OJNAW+NUMtIPkS/Y4Bot8NVrZPiXqMcvRN3QbShxuDzVxcGc1YdgR+os+OYCbDaX0x0+hJ7H74nO0dZwLBn3bEQBhUPIyKPIuKPopxvotRXTGp6jP8xDIrEEEwZvRgmitAw8Z3UFPt3+FST0WLS3n09GHU43sJJeNNKoTfA73hKdT+lhdI5hiANK0QOH79BrLLx0I6Y3LaEB/hn07b0c4w4rmZ5dOgaetuxkpFITqKdcSsPfIZlffnYGMUvhV7VAAmPGkiHi/oy96DpPp/91Ol3uNfho80JMa6xHbc1rKAEUdw7mHtuxfRTNa7fQ8DeaftMza1ulLsakIc/CFdObBpCFn6bzDkehYZCm+XoRfZ+KPmYxxg3tQJFQnB78ikmiufl0pNqvJONeQo9Z39wH0PBcUbEQEqjEKfDTx6EYUHRfDc6hd8PIOXwZ01fci956aTEMrVFo1LcchabmybTmfJJuwvV244Kv8imMPzYFCUznZfS1CsVFPxq6vwOTeJQM/RPcsfoQFBiFG6K51zY2XEo9azLd/RGCI1vpdTXNae49+K6Go1GmPkDpYQ199smorHpc/MCGRPw92BiFWQ1D0dR0J3m19wmNy4/gu+jTx91hMUajDN9EaWIAfaC7aGq6G9NWyO5DSMRv4FnNX0dakWFRR68vQo5XRcuO299kr/Z8lC56BVMT9K/I2z4HMSM+J2v2yl7oTE6C599KP5UjHLbBqJdER1RUjaSvp6PkoYbS6DSfjHwbUl+4HT84qh0xIJ4eXL/iKKQT/02hRZpvQxuXsRTlQRzZHYnAe61Ad4AJ7s2PULWtHvc0fRkxIHoD168cSIzMwxRivJp+6oF8oNSLuH7YR87t73ibvdST0b3wRbpX36UHczpmknMYMaI1cH3TueRILaAn8wzkPfyrFMq8OaJDypIUUUI8ocl4ock5PAueehWzms5EhIjOwNNXnkWB+Kn0biCigMKzot7L0IkrEIQOuy0GIG3uxIzGCxARojFw/SpyatSD9O54RINtxOH+RnREfUNP6gUXofvjeHhmJn2eSBzF/A08o5kC+t4vydvtj6jAa9/K5GLRMRrjdjI83R+K76V+FNObz0KeyM/As947k0KOPwf2kw6TD5T+Ha4dvMG5PXPHUKUa3AgHZfrTKuT2wK/JA+ENzB5fuvMBRDcs70KK+NXn6Sk2zkfs2DaQnKuv4cDD8WToesxuCe3XhDPwLFqzeWAPdwCihkIjypOrRMf4QdZGHxyYGIjOjvtwP5E0ISA38P+srSS289/o0NGIA0Y9joOXf+jcnq9H6dhDfkWFwalIdf4kiA4KITdw5V9/QP+Rc6TiiYKVe/Mwbpzn3L7HJydQD67GgQ2KKRDt2IFaCCEz0qxV59DTxLHlMKSBHQqLaO0rS8vR+sLAITnw0YNGqsmB08UMnSPcDTy9cRg6vTuRX2w5OxRFroz5tegYTqcFrsLnBUHs2vwU9Y01roe4GZhzp2BuoB42FHHBmPeDXGMJ2ii8Zz4XvXcPFE5CQtViyitOoWA3A7dv/yY5P99CvHgLdYLheeb6Khqe476m0oRPo1bfL13q0tRu4EwQ4TbEGeNVyqOLflF0TOdfjqXe+3V8HqHIB1JmcpDfZkFuA0+hOS6VugVxrHf3gmlAuXpddIjGifRJQ60NDwyYEVCd19uG6twGPlyfSr3kGsQNQ8T+If4a5/acwAd1IBAL+cH4N+LwfjlJiewGznioXAvUD7FC+dDqIVHOcNPy4+jTRR0i7X4IyBVzZcYJ3j+yG7jVOyMgoeMGM0et1X8UHaPLKQCvIs9+6JYwuAypbVmzWLKP32VJLieJt/dyiYfSs8X1tp4/lJ5et1RapSoytU5mAP2Qf5KhMhtpRfEe4gNPPz3peo+g94fCnrveCybJftKiLCfbD6Y1nryzViheKLUR2nsFUiSTP0Y67WashCknA/eEl+hPs8Fp9D8vIwPVhA61+noeffkZ4oKf0DCdlSjTvelahxP5fwl9Pw25VjEGZ+J/yWY3di146/p0zG3oic1qOgoRIeK0nN4U1y5kzU6QFKjvoJsSxkmjaJv/DVqvP49CYc4rFWg/7Bq6Wcy7Z09oUOoBVFRM2rdioutT3KY5474w7IzBbwtekMXBlIodV1OvfgRShKEy88X4MSnUDp3B7+h+fZK1ne9fGtRW74OuBta4GLF7zgwu6O6UFXRHhfEjt6Kj48f0hK0VHSelMqNE6gsv0NcZWf/OHrVCl4qOvQ08c1WfoJC5EGCdC5V8F8XCzSPXkIP3S9ExUiozSnDlQzLxC3qXfcRTauy+FYx7G9jz2LjOTEV+UL8qvoiJx2UxW5yahqEyo0agA6Jezt7ADEd5aq+l7T5DtOHgfQHyikMUdMcBnWgjw221tgtDZcYGsyb731QlrRr2yqnes9QI8opxQUEqhhXmiutjM5V4PS2tWrFp4xuYMsZtXZ322snInezt5UQYKpOTEj31VWs7qRwTC7eZrNer4OFyWgnV7nJek585kG9gIfKK19NLltR+V1Cz83/Wdsr8OxnXvZbYU71obXyQQx28jMrk0GF7OxfeXW9p2Yq+OAYSGHNszr+z3NPmJPfip/jHzBA9hYL3UelJ2aDQgt6933Bu717QnUKy/GFIUKaJjVK5p6RQVGaKHkhbmJclEskPkSwTp3BwxtjTg7V/TmBT7DLwkS2sJTEKhYGsoHv26r7k7V5sbaewEN8btA4S+P4p9PWg3I1CUJlp/0RrrJzlEmEWQILeK75MB/6dtZ1vTkDfxiCNOGPglHcMGbgQmYlbKfQ2X3TEjtQIujZLSajZTud9EhKwz6HUBGu78FRm7lCqMSvRx7hPJ3xenbAN+bvOXU3XHQz9GQMrb3CB6nr+gAojC9S7FHQr9SeoHvY5eu+DLoTVaQtBZa5YyTfW7lwp8wKd130ka152OF3LGKe2QdBDD+G3GQMnVGFqaqUF3UHgBada2xm8jrqB7lEpjrdrZc9pCkNlliXPouNsjhONZGopROjBNnK3UyKTIKmDibsww/M2mvNmio7o7OChOXfghSlHo2Rz2Sb68MaMtp4XIahMmIk7pRmyQ9GwD9UACXzvW9bz7tXeH8a21Ti6pTf9sy8hbmjMF0euXAq6NQ/P+h3IroXpt9zx9jBUJmtTG9jlkXz8GnXV7iPZ9GWH0b0YCQmUPgKHNvbX6OzkxLW4599t5Fk+JTrCtaDb4BlsWu9OAATpLdqejanM2ziUhcAF8DtZfdbGU29DVeXToupJJEfTvZBVGBr0pv7ejwysjrCuBfOFRgN9oEWSQ5wLusvNg86RK8a2HTyP2ackKZU5bTktX/SF1nbMgY8/1h4e3etaNAvaCMuF/ENp6jqCggiGhypbCDA/+Or3NDy3OrfnKjqjr7C2U2oZ1m1shgSZoIklnysEleknhtuVbQ0ZVsl4aB6eYULw86qCRqFe5GDpY2KrFMyAAvWebAmTNtQbTO6lhgpos9mi3svwPZfKyFfF2tRacySwLGcbpVuonUxH2iTZ2w+j+6Xh66Ppi3cY4oRSK8lBkPUyl4Jugw8DKX0Jpq8YTRdkn8sUHsKNgz6BK+59rz9d9N9b2xksFkn/39d8BN2/byMstH8MP8lHIk4Y73FsrHZ3gti5QsKuS6HUO1g/yN0JmkLOlUk45JmFoDI9Q8ZVNqW6dpoTZSNZpz8wvxCyOpQMrA5GnCgn5miKaO+h4+lJd5D18xeI1qj9PuUYrl1kLD5t6jdRnpB55ZwtGaTQhgR50pom4vi0LWIr6DYt1EY2l/kBN2sbntfTTXkUEtQ3EFGj/sGh5QsiMoRlGQ3yUtghA3wxPueKsyB8yOi7TAmGS7rua1CpNXDF3LkJuh67elwYKlMFO6/YIlcdSJiHIEF5BTFSeaZPGb88PgNzFgT8V0XHpFLnOBV0a/UoJozaDldsHkGhSZzg0DIebWqlXsaEocJsTJ97b97xiTh78B/FBd0w33Bo2YKJNc9BBI8JiwGWRnIq86CDOGhiU7elkczIZRmVGo8IEI+BOQtCCclsfHycvaA7yIJ4GhIwmZK5WbZAvZzKBM6DLUik8D46jSwjJENlRhJdpECHiqGywDTQMPq26BDPnGQt6DamleYVGbHPWRCGi8UtCKVNbRyoTPU6bhIMz65UpiO4B7sv6J2hFsuzIOAwl5nlqKpyd4LcsyDkVGZZj1PpwcktShNQmXDfzIvRZobQg3wGooBSmzmJS6bJbD+rTyd+UBSoX9XA616HLAi9ULRGbWnp55RMGIrKhJ3KVLyNjrccsvNy8UE09K3CVg50/AVRgrMgJla79zKGFyxhbN5zKy27/h+i8+5gDtWWBRGWyuS1ryXflvyQtrY1cMWcINp2rv28jlBqvUZCrUZU2JUFIb+Q78LGoyq1BMlyWRaEh0us5w1FZaqL3KhMNSsWKtMZZhORDX5rxjuNAJoiQWlf5jHOWMkljy787JOYcNzHcIVrFoSUygxkpXCltZ1SK+KhMl1BNvXUh+xFb6S7F802a4b42S2tTc7tWabJ1yz0Ys+CqKyQrX1V+dk0jNpi2nIqM6NNbVn78jLRfygmKtMRZhtMegPHormUJKJ52Dwt+lB9DVcAnGZtp9T94iwIPwghWtaoIahMaPZwLfF7sxq6TMZIuVKZ7iDnOUFzsObNH5VbCWUuKGwgclrmXGXU4m1O0Efw07IIExMALlkQUiqTtamN07Z5y+OhMkXYiqqytRobapjhWI/8sYgITGH9rD7fqnyj1PvkXAm3dlf/CJcsCCmV6apNrdTzsVCZMmzAhwO36OADash4yv1CWNDNYijKaZey+fIsiMB7zo0wVKZS5zloU6+htUQcVKYQqoFtm5nQDWTLj65oDbFDN8+9AyytaI26f/2nrNhOjpUtCyIMlRko/5l/sjc0S1D+6Z/gClcqUwpjgj0fMwb2/CYyssyJ+Sy0fky+4bFhJyh3FoRSNKcrmY5HwhtjzYIIQ2WyNrWLul4i+ZuYqEwJtgYBJ+wuPsMH9BJ6k7uxnm7Yb0VH8PAMh2wFPu8/V7v7BwEBoM9zaCkv6Fb6coeWLTSdyKJiblSmFM0o6wyyRzIGbtu0mZ6icJJG/KSIsyCcUlxYF+MxSJDJghiUsw1TmUbJ1tQ7yAlStgcyVipTBh75/jwiCN7srPCntauCsPxy99kWibIgpq08ko6xK7VzQbckwsTQLjwqUZlleBMS+Ook6gCWgm7Dgi7PQAJXKlMKrV/a5cXvWaJsMs+hL4/dgjphoqPoJVuj8s7X9t3SyLlS0uQ3IgD8a63tpAXdARJvIuHnzjbxgmibrKC7sdGtoFsC9qXS6d0O7x4DTyF6r77hCbBkniubYcxbKPPehwjKZYfuFpQboUipY0G38ueI5RNrq5nyk9F+NrhSmVJomn4m1eweUfeJe/rUG437XvLKzBdlQXCESalTHFq+gQk17ptTznzjYGhjF2phf2HS0D+gFOBGZcrAuh/GzPvsr/Y2cFXPl6nVCriAhwIf90OEQCbRIQtC6Kx0VlbTMadbzxuGyowDPDwbzWk5+etXfxbGNO5bzrO3gTmgb4ybVC4PBdIsCKV4HrNVUjQh6S+DBC4F3Vq1Ukz7JZQCmpuJ6YpB0VfpJftG/bpSUwYL7EEP9TEZS65qw0p6Nij1AjZscl/7uhZ0G7xNy8EIQrJ5IiiNNfcgMt53NzjjpYsX39XAB1Uto6fdslinpYbnySr7DK518tAT+j4R5djeTh65cdiRTUhlRg1e885YORad6veIPO7M0OQ59+7iCHadAzjkOK1xJnWlK+imVO73XKyJUdkjtTO7wQ6v/WB0wC5mBryDROcm5/My2lMcYToyp94kU5m+kMrMlNGEqcvdA+9TjY4y+izpI6Gavg1f8wgmky50QbD3hV+Puq7xiP1P8rU1r2FG06vU5cfu/4T+2XRj7Ypru6F60EEnOTTsiY7EIxzEckdALNiyIBaJqUzeEMw4sV05oOn+en3o87M4S34PS85/Q59vUs1+1+DZvTiTnkrjJUdZ9hcZGkwNBiN60NBlYhi+mMqskVGZxoxDwbSz80IrPTxTs/0x+5Nf0ZOfiCfQ/SGnMhNJ7rkD0B3AhW19zOJsf85uYJ6LlXokLxqxFBCGyjQ+yybYCrpLARyrvzdXZC733FVdvYRa3I3uCqXWkbcvrOzj4RnRE/BxgLfS6e3nlETMbeAxxEj43r10pmjjsAWDaUHfvu4qeO5UZilgDTmCU21xdXsObt3wtRQG5P2DYyhSixtCKvP+Vf0oBuBSo1xcZLSmJ+Pmkdbp0y3Jum0DBz5kW9AUG2GozFR6BC0NCyWMngfUPFRWPe7S0s3AHAHyzXR6amTK58UEU5nJdBxUZpFBUUR03OPqOLqXSdTVNJJ3eStybcxUSpBSmaxNrZRLMKaYoHuv/gWTvuIcU3c3MKuj1g1/kTjj28jL3IFSRhgqM905mr4OR8lCfRLc+9oa0ZpeXuhUjmlIUGQo4FdLFPFRmUVCkInyBFIH3w4h5AbmYc9L/ycdKZSkLxRipDKLBa7C8PTPg/0LhQhXqshLJ9+/Dpw7VXIIQWUqXIXCbEoiB8tAaH88bhgSaiPP8LWonDhuvDq6oTI5/dihloq1qaFLde1L91aNl4uo7UF+xcbsdPn4VwqEyDakig9c0C1Ly3HRpi4GOMyq8R+YNGQR8kD+1eQ3DKUbqjk4vwZFh3kDFUlZWNXXXLEQnyBrGATGJcJDrOjXFdHIBdQNXgKjJ5XAcP0k1g52H54zkoH2fK7C4h14/jU0LMv8iCyITsqwrvp5KH0zitmTyznvSqhN7asYEgxCg/2aWzKjYjSIVquS54uEOTMgoaNS7nGF0s/JC7r1JQ4F3fEjyAUn0r7cPz8TTIoO0YuRsseXAA3XeJAuvFAM1HaaTGWViIHoWFCjXGQoigqah4EeV8WxhXw8arPfpzVbqmcdreF+gcLErtfQulymgsfa1ApHoLige2P+Cz3MD0V7LwoQ/4bu01YRQ5O+c2dVYTwwFHeuq7nOuT1rU/tb7w5ytYsG9Tr9/1tRNyTSIXlfxCcIvgu1gxcGqje+PwuBdlPUMFxzJNOm3vHXQVlTgmMHkQaK4vkcCaytDlmT7Y74DcyYVLMSB7XdRB+MI19CjtYGRdRZWrY8S3pfK9LwvJyIkOuwcchN5Ck3yPYvDIfCGJgxfkyKDP0YKjtYcITzeGXV+9nAIqV9lXsoz1WbOlpspeGYaFbvIkysfly+ZW14FM7AuzB+5FZsMrdST+bEcpYyCm/ooGRDzxYVdL/bzDtjFyoth6ek2QEV2bbxpwFJU2BEW5/qiimBQX6HuQ2vY7N3BlSSy0RGi6+HlYEmDpaVmqb98+iGxzs8Z/ZVXAx4PwsKCMQSU9GhOAbehUzPWxi8pjecSAPKJFqbsvPTz3os916deABSuGhThwdrnDwFk5yJ2kEytbuYEP8ySYL6TT2BjSPI0Ofu1K/g2qBsqjnMk16IicMa4Yq7m0chEayXozKwyUheqBVB4XwSC1BetayYPXZfFLcH74tM+ePS4DVz1T3o9E+GNlfQbbyIHsV9Cfm30NrmToKzNnWCgvjRfWbqreYJYqPmo6riZbHccYFQWj04G+aacmxuuiDo1YYcJONX0/sfobbGXXMjED9V8+gj2yScsoENyGqArLG1EH3Mc2K1niKgtHpwNowL9nZ6ClNeeRa9evVDsqw/Egnhppfqq/RwWFRtmCDxUzs3KtlC39cHQq2sxqv9Jpr1P0BbzeZCLnPyRfcw8C5kJBjW7XzJwHsc8eaTu5QAgg3BDJMhH9FSi5czH5BxNwRbHLAKPgulf3/IOur1hWXFIsbfAOICik0ag32bAAAAAElFTkSuQmCC)![Shopify logo](data:image/png;base64,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)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAAB4CAMAAACjMkslAAAAn1BMVEUAAADxaSXxYiLzYiL0YCDzYCTxYSLxYSLyYSLzYCP0YSHxYiLwYCLyYiHyYSPxYSLxYSLyYiLyYSTzZCTxYSLxYSLxYiL7YifxYiLwYSHxYCLxYiLyYSLyYSLyYSPyYSLyYSPxYSLxYSTxYSLyYSL8YSzyYSHyYiHxYiHxYSLyYSLvYiPyYSLxYSLyYSLxYSLxYSLzYSHyYSLyYSHxYSK3XFqqAAAANHRSTlMADOw0MB/P8PtAGMtwc8f34DwoFNuk9Aive4A4YeiPwptIhGrVBlorEE6WHKq7VbWJJOSfTBU+IgAACbJJREFUeNrswTEBAAAAwiD7p7bGDmAAAAAAAAAAAADQcfLsbElNIArA8IGAtIiigIACIsPmvp73f7ZUN00UwSQmaFXIdzVlt+L5q0YF0nM0v4bJeJyE09laUqHG8CjJgXY4uUflTUseo8KbOfnh69gbhzIUVpJHyfA3hGiaEPyBXPZzAx6ENpW0NaA8sqm9ADWezcTwVkLmjpGyylEnF5vS4M+ZfoI1ZH9Ywb0BUuPWUvaQGjWknCCjwRs5bOZqyr6C1BD+mOTa2EQ8ep1NaZ4UbD/leozPKMNVR1MuCbafUgvwObJTO5kyI9h+yoONN4EVDiwd7y1XHUyZW1giVriTW0npjbFABovIBGbrXy2CJa2DKRdY0MOYjdNGynTHQ17iLdw4RpyQYuFodC+lPEYmmJkAbaWMFGRosCr1JCKivXQ6+Fnp68gcANpLuUBmYEBdZqM9Ezr4De4skSIutJjSLMaxD9DET/y0i78r+dRiv82UBkHKTaGJs+3m2Y6sIxWu2kwZIRPDE91Myac+QZspM6T0yf+V0kcme0NKUfoXUzqC4KTwJ2bI9FtNuUbK9l5OuZocYi3Ovq3qhSSmNnguUdvbpihBqudJnExXUrrPR2YhcXktt5F9LV13uhmuTagTJEYF5hz5mpaZt5UNMpnE5c6TlGa5XCUUsxgp3JGQIofXUgrR1RJtQogtXq5rFSo2IgMP8kSkFnebCDJiaUNXtnSfgowuclb/4ZpgGNjIENtaRrWa30RmRjsORxdFJ0RZF7Ho4/bDgQfnJynjcrnKK2YJt3BHtpFavpRSnYp4Y+8ncG+KDDyQxPsjXbHBlKUUsU6J4Cb1Q4L37J33mBKZL3B8i+/VD0UsrHt+OUMrl6smAVI9Ge6sQv5h+ULK+IJV+kb9YEpvT7Dma9WYUj4i9ZGUzokUv/vV306pK/hIX6ofS9lPsIG9kxtS7o749pT1c/Dl9vdSNiObT6X0EmxEjmo9pY6fTOns+ZHCg/lSSqIEgagjp0efSWmEWCDj4zyO40UYkPI/w6mkrCL6+1OCdylr9GZS+rspyX7Wz2Upm5aDD7YvpdRc190r/AaSy7EJzKvrujxYz+Wm34BJ3XL4YTnGeaEgY/vPUl52c80f5kDl9OUSZPYut9m2khJ8HTkijk6RsfqNlMma73KiHn+u9lLKVBCEvJhopAqcAxT9K0JmKJRSYHyCFJnm8IPTT0gx8bkpJelpsgAcf/kvZPq3A7eTEmYi3pDkOJScX6QcfKu9LIbpCyn/8GznXDyD7ITGGwHDppQLA2rmyNymaCslSAnBimCTyT9JGeQNN5yU/vtTzpBxzebr12OjllKZAXwyJUgnEavsZDlxaikb3546Qkofvj2lmSAV1BI4V6RI/JiSzJ3PpeSkhYUP9GOWNqbsqVAxJEhNnXenzHT2vrQUHuUEqbCakj3wyZSc6e8sBesnZPWUC6j6FhRJzDenTJdIDZoGcJEiajWlsv54Ss743s65bqkJAwF4EC8oKhaKyMVrEEFFwfL+z9buJjFC4rFU9Nhz8v3UFZLPGZJM4p4y3ShPuCKBynU1vXBAXwYvVml16TfJM8RheSqr7B7er5KhJJt0XjAWMbdw5NqX4pDovFAly2JNWKbut5hmpvIIb1XJY/nDsKAY3oPSL5PXfrHKhLs9d9rC3pZUZm9XyeP4dou67D9UuScte7FKFy9RBooAa4yHGatcGfoAlQBK3zbIKDh4pHLxepXsG2uNReg9MiR9oEoAh5xw09CHqPxVPCQ0P1LldXXeVT5D5bF4yM9PVQkzcohBqnxKJfuItvkMlav/WaWlkynGR6jc4CVY5t4HfeIIfpvh5/gjVCI8CprwgAZVItzjXQMqV3Q6VF9lVZCKVWa8yliokj8l4ePFYfBGlREuRbWhzLolVrl9EJW1VLIJey6uKXpw5YCn1WEHOHxR4eSAY+H4RpWJ+EjVaSpUOThuFLiDgzubWnVUeuIDtUirnpmzUnw9FTgGohpZfMZPrt37VLYLYSJ4hUilZWu9CO7Q/onjwKmjMuAjigWrceKOwRui2xv4rXLGZGQX6X0qO1NhX1KRSj9kO688rvHd9gzqqFRZ9PMH6S8/uMsXNvDoonOfO5zhc1OYQa9QGY9xIijldhQClX4X2xk6IODQIwXUOirpaGIMRQVF3eKf3lOqhr9kaomiYRUDh+IurOZVKnixWs7beCFSGfKnfhi7MfmEU0ulQ4bwiQ+MHbnTvtT9LikrWvd+l2W4cEtCXuUVWZmmzQaNq4RAcNT61BKpHBrX3WLUqdaAuxp9NtVSCesCo7Nkzscaq9gxMvLqPuYSglieRzEwtjZprVuRPzhPv/Z0zWZVsueSNmLNSC5FSaWSmwoeFCmG7ppwJQ7OU/oBs6ZK+EmXcWiLDSAshtQTGe0JufesDxU2BaaX5cAwWwX7XRbjpONXZ42rhGOlAu4gYpKqbK8yb78DAGtc+tnkLBsGAfJWocEU+1BXZXT99GWFguGequW3rTJ2kmGBAh8Yh0lBOW+CaECl9QrMdBWZMQA4hx8opM1fN69SndMcQ/22uh7hVGUqBzYKkt2vNgDsUmZNRMuD2iotuxCiLaBC58IdvyI4yBAdv1JGBtuq15ez5TKca7SpATSv0smuz8D5ZILfvlU5Uu1jmq2H36mcTQsxZNO5vkrohIWIZQeqRD1OJcE5ilSC4xmFmEsCL1AJyrHgYCq3NtinzcjEQbJdn+/K/BltoY5KitrV+JgUbaU6ox6nkpCfRSrBQaEm7FcfGlXJt4PR6lGVsOj8UjcHNaPiA70Qssc3q68SzLSoYotn1iODU0no6GWVFP9o8NmzyOFFKqFzrnx306B7VelnvnM4eKx9VpROtGrCHNcO1FTJiDddoxSSwwEIcYKxIVYJnf1EE6iEGI3LD62enljwMpVw8MKbvhjLfjxmI3iwSBIP3Zra+ug4Zz2f2MhXAP5OJVp+U4k6E6VT2tUzyuEuA/qHNlRRva7GVDKs9X7C/svACotsQGX7vPziBBVyd0kj8hwcQLlRCbsgULdQJVaT4A8JtshhYeCv2e6Sr8vttvAApx2NXHctFL1GrjvKgSPuR18X7yv3m+qAGId0pA6WHwXByf/uClMpeQKpUqr8TCxdqmyIPKQlQMmT9Ft0d0HyJK5G5lqSJ1HInDYByXM4o4KefJc8x4msClOQPEW+aZHVsMzvf8ey8vXiWj5cyvz+d/alsp4MyqZU7kHSjMpUpnczKrWZCZImVPZcufpuQmVrmcnkfpbol733IlVGpEQikUgkEolEIpFIJBKJ5IbftENwFj4iyG0AAAAASUVORK5CYII=)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous Angular ](/docs/cesdk/ui/solutions/video-editor/angular/)[ Next Javascript ](/docs/cesdk/ui/solutions/photo-editor/javascript/) Exporting Pages - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/guides/export-pages/ CESDK/CE.SDK/Web Editor/Guides # Exporting Pages Learn which export options CreativeEditor SDK offers and how to use them. ### Exporting via the Export button When a user triggers an export by clicking the corresponding button, the editor will export the current page to a PDF or PNG image and notify the `onExport` callback if configured. The callback handler receives the encoded data as a `Blob` object along with the `options` used during export. Besides the `onExport` callback, you have to enable the export button in the navigation bar. See [the configuration of elements](/docs/cesdk/ui/guides/elements/) for further information. [ Previous Elements ](/docs/cesdk/ui/guides/elements/)[ Next Uploading Images ](/docs/cesdk/ui/guides/upload-images/) Animations - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-animations/?platform=web&language=javascript # Animations In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a block's animation through the `block` API. Animations alter the visual appearance of a block over time in a video scene. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating Animations To create an animation simply use `createAnimation(type: string): number`. ``` const slideInAnimation = engine.block.createAnimation('slide'); ``` ``` createAnimation(type: AnimationType): DesignBlockId ``` Creates a new animation, fails if type is unknown. * `type`: The type of animation to create. * Returns The handle of the new animation instance. ``` const slideInAnimation = engine.block.createAnimation('slide'); ``` We currently support the following "in" and "out" animation types: * `'//ly.img.ubq/animation/slide'` * `'//ly.img.ubq/animation/pan'` * `'//ly.img.ubq/animation/fade'` * `'//ly.img.ubq/animation/blur'` * `'//ly.img.ubq/animation/grow'` * `'//ly.img.ubq/animation/zoom'` * `'//ly.img.ubq/animation/pop'` * `'//ly.img.ubq/animation/wipe'` * `'//ly.img.ubq/animation/baseline'` * `'//ly.img.ubq/animation/crop_zoom'` * `'//ly.img.ubq/animation/spin'` and the following "loop" animation types: * `'//ly.img.ubq/animation/spin_loop'` * `'//ly.img.ubq/animation/fade_loop'` * `'//ly.img.ubq/animation/blur_loop'` * `'//ly.img.ubq/animation/pulsating_loop'` * `'//ly.img.ubq/animation/breathing_loop'` * `'//ly.img.ubq/animation/jump_loop'` * `'//ly.img.ubq/animation/squeeze_loop'` * `'//ly.img.ubq/animation/sway_loop'` Note: short types are also accepted, e.g. `'slide'` instead of `'//ly.img.ubq/animation/slide'`. ``` const slideInAnimation = engine.block.createAnimation('slide'); ``` ## Functions You can configure animations just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` engine.block.setFloat(slideInAnimation, 'animation/slide/direction', Math.PI); ``` ``` supportsAnimation(id: DesignBlockId): boolean ``` Returns whether the block supports animation. * `block`: The block to query. * Returns Whether the block supports animation. ``` const supportsAnimation = engine.block.supportsAnimation(block); ``` ``` setInAnimation(id: DesignBlockId, animation: DesignBlockId): void ``` Set the "in" animation of the given block. * `id`: The block whose "in" animation should be set. * `animation`: The animation to set. ``` engine.block.setInAnimation(block, slideInAnimation); ``` ``` setLoopAnimation(id: DesignBlockId, animation: DesignBlockId): void ``` Set the "loop" animation of the given block. * `id`: The block whose "loop" animation should be set. * `animation`: The animation to set. ``` engine.block.setLoopAnimation(block, spinLoopAnimation); ``` ``` setOutAnimation(id: DesignBlockId, animation: DesignBlockId): void ``` Set the "out" animation of the given block. * `id`: The block whose "out" animation should be set. * `animation`: The animation to set. ``` engine.block.setOutAnimation(block, fadeOutAnimation); ``` ``` getInAnimation(id: DesignBlockId): DesignBlockId ``` Get the "in" animation of the given block. * `id`: The block whose "in" animation should be queried. * Returns The "in" animation of the block. ``` const inAnimation = engine.block.getInAnimation(block); ``` ``` getLoopAnimation(id: DesignBlockId): DesignBlockId ``` Get the "loop" animation of the given block. * `id`: The block whose "loop" animation should be queried. * Returns The "loop" animation of the block. ``` const loopAnimation = engine.block.getLoopAnimation(block); ``` ``` getOutAnimation(id: DesignBlockId): DesignBlockId ``` Get the "out" animation of the given block. * `id`: The block whose "out" animation should be queried. * Returns The "out" animation of the block. ``` const outAnimation = engine.block.getOutAnimation(block); ``` Adjusting the Content of the Canvas Bar - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/api/canvasBar/ CESDK/CE.SDK/Web Editor/Customization/API Reference # Adjusting the Content of the Canvas Bar Learn how to adjust the content of the canvas bar in the editor There are 2 APIs for getting and setting the order of components in the Canvas Bar. The Canvas Bar can appear in 2 positions: At the top of the canvas, and at the bottom of the canvas. The APIs can get and set the content for both of these positions independently. The content of the Canvas Bar changes based on the current [edit mode](/docs/cesdk/engine/api/editor-state/#state) (`'Transform'` (the default), `'Text'`, `'Crop'`, `'Trim'`, or a custom value), so both APIs accept an `orderContext` argument to specify the mode. For example usage of similar APIs, see also [Moving Existing Buttons](/docs/cesdk/ui/customization/guides/movingExistingButtons/) or [Adding New Buttons](/docs/cesdk/ui/customization/guides/addingNewButtons/) in the Guides section. ### Get the Current Order ``` getCanvasBarOrder( position: 'top' | 'bottom', orderContext: OrderContext = { editMode: 'Transform' } ) ``` When omitting the `orderContext` parameter, the order for the `'Transform'` edit mode is returned, e.g. ``` cesdk.ui.getCanvasMenuOrder('bottom'); // => [ // {id: 'ly.img.settings.canvasBar'}, // {id: 'ly.img.spacer'}, // ... // ] ``` ### Set a new Order ``` setCanvasBarOrder( canvasBarOrder: (CanvasBarComponentId | OrderComponent)[], position: 'top' | 'bottom', orderContext: OrderContext = { editMode: 'Transform' } ) ``` When omitting the `orderContext` parameter, the order is set for the default edit mode (`'Transform'`), e.g.: ``` // Sets the order for transform mode by default cesdk.ui.setCanvasBarOrder('bottom', [ 'my.component.for.transform.mode']); ``` ## Canvas Bar Components The following lists the default Canvas Bar components available within CE.SDK. ### Layout Helpers Component ID Description Component ID `ly.img.separator` Description Adds a vertical separator (`
` element) in the Canvas Bar. Separators follow some special layouting rules: \- If 2 or more separators end up next to each other (e.g. due to other components not rendering), **only 1** separator will be rendered. \- Separators that end up being the first or last element in the Canvas Bar will **not** be rendered. \- Separators directly adjacent _to the left side_ of a spacer (see below) will **not** be rendered. Component ID `ly.img.spacer` Description Adds horizontal spacing in the Canvas Bar. Spacers will try to fill all available whitespace, by distributing the available space between all spacers found in the Canvas Bar. ### Common Controls Component ID Description Component ID `ly.img.settings.canvasBar` Description Customize Editor button: Opens the Settings Panel when pressed. Component ID `ly.img.page.add.canvasBar` Description Add Page button: Adds a new page to the end of the document when pressed. ## Default Order The default order of the Canvas Bar is the following: ``` [ 'ly.img.settings.canvasBar', 'ly.img.spacer', 'ly.img.page.add.canvasBar', 'ly.img.spacer' ] ``` [ Previous Navigation Bar ](/docs/cesdk/ui/customization/api/navigationBar/)[ Next Custom Components ](/docs/cesdk/ui/customization/api/registerComponents/) How to use custom LUT filter - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/custom-lut-filter/?platform=web&language=javascript # How to use custom LUT filter ## Creating Custom Filters We use a technology called Lookup Tables (LUTs) in order to add new filters to our SDK. The main idea is that colors respond to operations that are carried out during the filtering process. We 'record' that very response by applying the filter to the identity image shown below. ![Identity LUT](/docs/cesdk/7d624d6315581dca5652b65884e5e117/identity.png) The resulting image can be used within our SDK and the recorded changes can then be applied to any image by looking up the transformed colors in the modified LUT. If you want to create a new filter, you'll need to [download](/docs/cesdk/images/identity.png) the identity LUT shown above, load it into an image editing software of your choice, apply your operations, save it and add it to your app. > **WARNING:** As any compression artifacts in the edited LUT could lead to distorted results when applying the filter, you need to save your LUT as a PNG file. ## Using Custom Filters In this example, we will use a hosted CDN LUT filter file. First we will load one of our demo scenes and change the first image to use LUT filter we will provide. We will also configure the necessary setting based on the file. LUT file we will use: ![](https://cdn.img.ly/assets/v1/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png) Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/engine-guides-custom-lut-filter?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Custom+Lut+Filter&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/engine-guides-custom-lut-filter). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/engine-guides-custom-lut-filter?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Custom+Lut+Filter&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/engine-guides-custom-lut-filter) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/index.js'; ``` ## Load Scene After the setup, we create a new scene. Within this scene, we create a page, set its dimensions, and append it to the scene. Lastly, we adjust the zoom level to properly fit the page into the view. ``` const page = engine.block.create('page'); engine.block.setWidth(page, 100); engine.block.setHeight(page, 100); engine.block.appendChild(scene, page); engine.scene.zoomToBlock(page, 40, 40, 40, 40); ``` ## Create Rectangle Next, we create a rectangle with defined dimensions and append it to the page. We will apply our LUT filter to this rectangle. ``` const rect = engine.block.create('graphic'); engine.block.setShape(rect, engine.block.createShape('rect')); engine.block.setWidth(rect, 100); engine.block.setHeight(rect, 100); engine.block.appendChild(page, rect); ``` ## Load Image After creating the rectangle, we create an image fill with a specified URL. This will load the image as a fill for the rectangle to which we will apply the LUT filter. ``` const imageFill = engine.block.createFill('image'); engine.block.setString(imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg'); ``` ## Create LUT Filter Now, we create a Look-Up Table (LUT) filter effect. We enable the filter, set its intensity, and provide a URL for the LUT file. We also define the tile count for the filter. The LUT filter effect is then applied to the rectangle and image should appear black and white. ``` const lutFilter = engine.block.createEffect('lut_filter'); engine.block.setBool(lutFilter, 'effect/enabled', true); engine.block.setFloat(lutFilter, 'effect/lut_filter/intensity', 0.9); engine.block.setString( lutFilter, 'effect/lut_filter/lutFileURI', 'https://cdn.img.ly/packages/imgly/cesdk-js/1.43.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png' ); engine.block.setInt(lutFilter, 'effect/lut_filter/verticalTileCount', 5); engine.block.setInt(lutFilter, 'effect/lut_filter/horizontalTileCount', 5); ``` ## Apply LUT Filter Finally, we apply the LUT filter effect to the rectangle, and set the image fill to the rectangle. Before setting an image fill, we destroy the default rectangle fill. ``` engine.block.appendEffect(rect, lutFilter) engine.block.setFill(rect, imageFill); ``` How to Create a Plugin - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/plugins/createPlugin/ CESDK/CE.SDK/Web Editor/Customization/Plugins # How to Create a Plugin Learn how to bundle features into a plugin Creating your own plugin can be useful for your integration if you want to bundle features together or even publish them for others to use. ### Do I Need to Create a Plugin to Customize the Editor? Short answer: No. Keep in mind that you neither have to create a plugin to customize the CE.SDK, nor do you have to publish it. Feel free to use all APIs directly after initializing the CE.SDK. This is completely fine. Even if you decide to bundle everything into a plugin, you can still keep it private and use it only for your own integration. ## Create a Plugin Plugins are added to the editor using the `addPlugin` method. This method takes a plugin object that needs to provide specific properties and methods. The most important method is `initialize`, which is called when the plugin is added to the editor. It will receive the current execution context as an argument. If the plugin is added only to the engine, the `cesdk` argument will be undefined. This allows you to write plugins that can be used for both the engine and the editor. You can pass this `MyPlugin` object directly to the `addPlugin` method. If you plan to publish your plugin in private or public repositories, this object is what should be exported. However, most plugins will need some kind of configuration. If you expose a plugin, it is a good convention to export a function that returns the plugin object and accepts a config object as an argument. Even if you don't need any configuration yet, it is recommended to use this pattern to avoid breaking user code in the future once you introduce an optional configuration. ``` const MyPlugin = () => ({ name: 'MyPlugin', version: '1.0.0', initialize: ({ // This argument is only defined if the plugin is added to the editor. // If you call the `addPlugin` method on the engine, this argument will be undefined. cesdk, // The engine is always defined engine }) => { // Your plugin code here } }); cesdk.ui.addPlugin(MyPlugin()); ``` ## Initialization of a Plugin What is supposed to be done in the `initialize` method of a plugin? There are no enforced rules, but some conventions are recommended, especially if you plan to publish your plugin in any way. Adding a plugin will call the `initialize` method of the plugin object directly. The purpose of this method is to register components, and features, or subscribe to events and changes of blocks. It is not supposed to immediately execute any action or make changes to the editor. This includes changing the order of any location, such as adding a new component to it. The integrator of the plugin needs to decide when to add which component and where. If the plugin wants to add a shortcut for that, the default locations can be optionally set via the plugin configuration. ``` const MyPlugin = ({ ui: { locations } }) => ({ name: 'MyPlugin', version: '1.0.0', initialize: ({ cesdk, engine }) => { // Register a new components cesdk.ui.registernComponent('myComponent.canvasMenu', [...]); cesdk.ui.registernComponent('myComponent.inspectorBar', [...]); // Register a new feature cesdk.feature.enable('myFeature', true); // Subscribe to events or similar // [...] if (locations.includes('canvasMenu') { cesdk.ui.setCanvasMenuOrder(['myComponent.canvasMenu', ...cesdk.ui.getCanvasMenuOrder()]); } if (locations.includes('inspectorBar') { cesdk.ui.setInspectorBarOrder(['myComponent.inspectorBar', ...cesdk.ui.getInspectorBarOrder()]); } } }); ``` [ Previous Overview ](/docs/cesdk/ui/customization/plugins/)[ Next Background Removal ](/docs/cesdk/ui/customization/plugins/backgroundRemoval/) Editing Text in the Headless CreativeEngine - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/text-editing/?platform=web&language=javascript # Editing Text in the Headless CreativeEngine In this example, we want to show how to set up text editing in the Headless CreativeEngine. Depending on your use cases, text editing can require a little more complicated setup, because the focus for the user's keyboard input might need to be managed between the CreativeEngine itself and your UI components. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-text-editing?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Text+Editing&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-text-editing). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-text-editing?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Text+Editing&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-text-editing) ## Text Edit Mode The Engine always operates in one of three `EditMode`s: * `Transform`: In this mode you can transform blocks in the scene by moving them around, selecting, or resizing them. This is the default mode. * `Crop`: This mode is used when cropping image blocks. * `Text`: Then double-clicking on a text block, the engine switches to this mode to let you edit the text content of a text block. You can query and set the `EditMode` using the [EditorAPI](/docs/cesdk/engine/api/editor/)'s `getEditMode()` and `setEditMode(mode)` methods. Certain interactions will cause the engine to switch the mode by itself (such as double-clicking on a text block). You get notified of changes in the EditorState using the `onStateChanged` method of the EditorAPI. Inside the callback, use `getEditMode()` to know the current mode. ``` instance.editor.setEditMode('Transform'); instance.editor.onStateChanged(() => { console.log('EditMode is ', instance.editor.getEditMode()); }); ``` ## Text Editing Focus When the engine enters `Text` mode, it will focus the browser's input on the text content of the block you are editing. From here, several things can happen: * The user presses `ESC`, clicks somewhere on the canvas outside of the text block, or `Text` mode is ended via a call to `setEditMode('Transform')`. In this case, the browser will blur the text input of the engine, and you are back in the `Transform` mode. * The user focuses a text input field, number input field, text area or a contentEditable element outside of the canvas. In this case the engine will stay in `Text` mode, but will no longer have focus. Keyboard input will now go to the input field that has received focus. * The user clicks on a blank space or any focusable DOM element, that does not require keyboard input. This behavior should be sufficient for most use cases. If your requirements are more complicated, we provide a way to customize what's happening through events. ## CreativeEngine DOM Events There are currently two types of custom DOM events that the CreativeEngine dispatches on the `canvas` element. You can add a listener for it there or on any of its parent elements. ### `cesdk-blur` When the text input in the engine has been blurred, this event will be dispatched. You can call `preventDefault()` on the event to force focus back to the canvas input. The `event.detail` property will contain a reference to the element that has received focus (if available, otherwise it's `null`). You can use that element during the decision whether to call `preventDefault()`. A use case for this could be to prevent disabling the text-input heuristic, forcing refocus even if the user clicked on a text input, or to call `editor.setEditMode('Transform')` to exit the text edit mode whenever the input is blurred. ``` document.addEventListener('cesdk-blur', (event) => { const relatedTarget = event.detail; if (focusShouldStayOnEngine(relatedTarget)) { event.preventDefault(); } else if (engineShouldExitTextMode(relatedTarget)) { instance.editor.setEditMode('Transform'); } }); function focusShouldStayOnEngine(newActiveElement) { // When clicking on blank space, don't blur the engine input return newActiveElement == null; } function engineShouldExitTextMode() { return false; } ``` ### `cesdk-refocus` Just before the engine refocuses its text input, you can call `preventDefault()` on this event to prevent this from happening. This event also contains currently focused element (or `null`) it in its `event.detail` property. A use case for this would be to treat a particular input element as if it were a text input and let it keep keyboard focus after it's been focused. ``` document.addEventListener('cesdk-refocus', (event) => { const relatedTarget = event.detail; if (focusShouldStayOnUI(relatedTarget)) { event.preventDefault(); } }); function focusShouldStayOnUI(newActiveElement) { // User might have clicked a button that opens a dialog // Afterwards we want an input in the dialog to receive focus return newActiveElement?.id === 'open-dialog'; } ``` Scene Contents - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/scene-contents/?platform=web&language=javascript # Scene Contents Learn how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to explore scene contents through the `scene` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` getPages(): DesignBlockId[] ``` Get the sorted list of pages in the scene. * Returns The sorted list of pages in the scene. ``` const pages = engine.scene.getPages(); ``` ``` setDesignUnit(designUnit: DesignUnit): void ``` Converts all values of the current scene into the given design unit. * `designUnit`: The new design unit of the scene ``` engine.scene.setDesignUnit('Pixel'); ``` ``` getDesignUnit(): DesignUnit ``` Returns the design unit of the current scene. * Returns The current design unit. ``` /* Now returns 'Pixel' */ engine.scene.getDesignUnit(); ``` ``` type DesignUnit = 'Pixel' | 'Millimeter' | 'Inch' ``` The unit type in which the page values (size, distances, etc.) are defined. ``` engine.scene.setDesignUnit('Pixel'); ``` ``` getCurrentPage(): DesignBlockId | null ``` Get the current page, i.e., the page of the first selected element if this page is at least 25% visible or, otherwise, the page nearest to the viewport center. * Returns The current page in the scene or null. ``` const currentPage = engine.scene.getCurrentPage(); ``` ``` findNearestToViewPortCenterByType(type: DesignBlockType): DesignBlockId[] ``` Find all blocks with the given type sorted by the distance to viewport center. * `type`: The type to search for. * Returns A list of block ids sorted by distance to viewport center. ``` const nearestPageByType = engine.scene.findNearestToViewPortCenterByType('//ly.img.ubq/page')[0]; ``` ``` findNearestToViewPortCenterByKind(kind: string): DesignBlockId[] ``` Find all blocks with the given kind sorted by the distance to viewport center. * `kind`: The kind to search for. * Returns A list of block ids sorted by distance to viewport center. ``` const nearestImageByKind = engine.scene.findNearestToViewPortCenterByKind('image')[0]; ``` Customizing the Web Editor - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/ CESDK/CE.SDK/Web Editor/Customization # Customizing the Web Editor Get to know the various customization options of the web editor Design use-cases are unique and require customized solutions. CE.SDK provides multiple options to adapt to your specific product and workflow. For instance, we allow users to build their complete user interface from the ground up by using the CE.SDK engine directly. While this offers the greatest flexibility, it comes at a cost. Building and maintaining a user interface for a design editor is time-consuming and difficult. The CE.SDK web editor is built with modification in mind, so most use-cases can be addressed by carefully customizing our existing editor UI. This approach saves time and cost, ensuring smooth integration. The following documentation will guide you through all possible modifications that are available. * [Basics](/docs/cesdk/ui/customization/basics/) will teach the core principles and concepts behind our extension points * [Plugins](/docs/cesdk/ui/customization/plugins/) shows how our APIs can be bundled into modular plugins and lists the plugins provided by IMG.LY * [API Reference](/docs/cesdk/ui/customization/api/) provides a detailed description of all available APIs to extend the web editor [ Previous Initialization ](/docs/cesdk/ui/initialization/)[ Next Basics & Principles ](/docs/cesdk/ui/customization/basics/) The Vectorizer Plugin - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/plugins/vectorizer/ CESDK/CE.SDK/Web Editor/Customization/Plugins # The Vectorizer Plugin Learn about IMG.LY's Vectorizer plugin This plugin introduces vectorization for the CE.SDK editor, leveraging the power of the [vectorizer library](https://github.com/imgly/vectorizer). It integrates seamlessly with CE.SDK, providing users with an efficient tool to vectorize images directly in the browser with ease and no additional costs or privacy concerns. ## 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 register a vectorizer canvas menu component that can be rendered for every block with an image fill. To automatically add this button to the canvas menu, please use the `locations` configuration option. ``` 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({ // This will automatically prepend a button to the canvas menu ui: { locations: 'canvasMenu' } })); await cesdk.createDesignScene(); ``` ## Configuration ### Adding Canvas Menu Component After adding the plugin to CE.SDK, it will register a component that can be used inside the canvas menu. It is not added by default but can be included using the following configuration: ``` // Either pass a location via the configuration object, ... VectorizerPlugin({ ui: { locations: 'canvasMenu' } }) ``` ### Further Configuration Options #### Timeout The duration of the vectorization process depends on the complexity of the input image. For highly detailed images, it can take a considerable amount of time. A timeout is configured to abort the process after 30 seconds by default, but this can be customized using the `timeout` option. ``` VectorizerPlugin({ // Reduce the timeout to 5s timeout: 5000 }) ``` #### Threshold for Group Creation For simple vectorized images, using groups makes a lot of sense. Single paths can be selected, allowing the user to change the color, for instance. However, once the number of paths exceeds a certain threshold, the user experience deteriorates significantly as it becomes difficult to select individual paths. Based on the use case you can adapt this threshold (default is 500). ``` VectorizerPlugin({ // Reducing the maximal number of groups to 200 groupingThreshold: 200 }) ``` [ Previous Background Removal ](/docs/cesdk/ui/customization/plugins/backgroundRemoval/)[ Next Overview ](/docs/cesdk/ui/customization/guides/) CreativeEngine APIs - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/?platform=web&language=javascript Platform Web Node.JS iOS Catalyst macOS Android Language JavaScript Platform: Web Language: JavaScript # CreativeEngine APIs Use the CreativeEngine APIs of the CreativeEditor SDK to implement highly customized workflows The APIs of the CreativeEngine allow you to programmatically manipulate scenes inside the editor to automate workflows and customize the user experience. ## Accessing the CreativeEngine APIs You can access the CreativeEngine APIs via the `engine` object and interact with the scene seen on screen. The examples in the API Guides will use the headless CreativeEngine: ``` import 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/index.js'; const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/assets' }; CreativeEngine.init(config).then((engine) => { // Work with the engine... engine.scene.create(); }); ``` ## API Guides **[Scene](/docs/cesdk/engine/api/scene/)** Load, create, and save scenes or control the zoom. **[Block](/docs/cesdk/engine/api/block/)** Manipulate blocks, the elements a scene is made of, in various ways. **[Editor](/docs/cesdk/engine/api/editor/)** Control settings or history and observe state changes in your engine instance. **[Asset](/docs/cesdk/engine/api/assets/)** Manage assets by creating and reading from AssetSources. **[Event](/docs/cesdk/engine/api/events/)** Subscribe to block creation, update and destruction events. **[Variable](/docs/cesdk/engine/api/variables/)** Manage the values of pre-defined variables, allowing for quick customization of things like headlines. [ Previous Export with underlayer ](/docs/cesdk/engine/guides/underlayer/)[ Next Overview ](/docs/cesdk/engine/api/scene/) Change Settings - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/editor-change-settings/?platform=web&language=javascript # Change Settings In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to control with the `editor` API. A list of all available settings can be found on the [Settings](/docs/cesdk/engine/api/editor-settings/) page. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Exploration ``` findAllSettings(): string[] ``` Returns a list of all the settings available. * Returns A list of settings keypaths. ``` engine.editor.findAllSettings(); ``` ``` getSettingType(keypath: string): SettingType ``` Returns the type of a setting. * `keypath`: The settings keypath, e.g. `doubleClickSelectionMode`. * Returns The setting type. ``` engine.editor.getSettingType('doubleClickSelectionMode'); ``` ## Functions ``` onSettingsChanged: (callback: () => void) => (() => void) ``` Subscribe to changes to the editor settings. * `callback`: This function is called at the end of the engine update, if the editor settings have changed. * Returns A method to unsubscribe. ``` const unsubscribeSettings = engine.editor.onSettingsChanged(() => console.log('Editor settings have changed'); ); ``` ``` onRoleChanged: (callback: (role: RoleString) => void) => (() => void) ``` Subscribe to changes to the editor role. This lets you react to changes in the role of the user and update engine and editor settings in response. * `callback`: This function will be called immediately after a role has been set and the default settings for that role have been applied. This function will also be called in case the role is set to the same value as before. * Returns A function for unsubscribing ``` const unsubscribeRoleChange = engine.editor.onRoleChanged((role) => { console.log('Role': role); }); ``` ``` setSettingBool(keypath: SettingsBool, value: boolean): void ``` Set a boolean setting. * `keypath`: The settings keypath, e.g. `doubleClickToCropEnabled` * `value`: The value to set. * Throws An error, if the keypath is invalid. ``` setSettingBool(keypath: `ubq://${SettingsBool}`, value: boolean): void ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.setSettingBool('doubleClickToCropEnabled', true); ``` ``` getSettingBool(keypath: SettingsBool): boolean ``` Get a boolean setting. * `keypath`: The settings keypath, e.g. `doubleClickToCropEnabled` * Throws An error, if the keypath is invalid. ``` getSettingBool(keypath: `ubq://${SettingsBool}`): boolean ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.getSettingBool('doubleClickToCropEnabled'); ``` ``` setSettingInt(keypath: string, value: number): void ``` Set an integer setting. * `keypath`: The settings keypath. * `value`: The value to set. * Throws An error, if the keypath is invalid. ``` engine.editor.setSettingInt('integerSetting', 0); ``` ``` getSettingInt(keypath: string): number ``` Get an integer setting. * `keypath`: The settings keypath. * Throws An error, if the keypath is invalid. ``` engine.editor.getSettingInt('integerSetting'); ``` ``` setSettingFloat(keypath: SettingsFloat, value: number): void ``` Set a float setting. * `keypath`: The settings keypath, e.g. `positionSnappingThreshold` * `value`: The value to set. * Throws An error, if the keypath is invalid. ``` setSettingFloat(keypath: `ubq://${SettingsFloat}`, value: number): void ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.setSettingFloat('positionSnappingThreshold', 2.0); ``` ``` getSettingFloat(keypath: SettingsFloat): number ``` Get a float setting. * `keypath`: The settings keypath, e.g. `positionSnappingThreshold` * Throws An error, if the keypath is invalid. ``` getSettingFloat(keypath: `ubq://${SettingsFloat}`): number ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.getSettingFloat('positionSnappingThreshold'); ``` ``` setSettingString(keypath: SettingsString, value: string): void ``` Set a string setting. * `keypath`: The settings keypath, e.g. `license` * `value`: The value to set. * Throws An error, if the keypath is invalid. ``` setSettingString(keypath: `ubq://${SettingsString}`, value: string): void ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.setSettingString('license', 'invalid'); ``` ``` getSettingString(keypath: SettingsString): string ``` Get a string setting. * `keypath`: The settings keypath, e.g. `license` * Throws An error, if the keypath is invalid. ``` getSettingString(keypath: `ubq://${SettingsString}`): string ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.getSettingString('license'); ``` ``` setSettingColor(keypath: SettingsColor, value: Color): void ``` Set a color setting. * `keypath`: The settings keypath, e.g. `highlightColor`. * `value`: The The value to set. ``` setSettingColor(keypath: `ubq://${SettingsColor}`, value: Color): void ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.setSettingColor('highlightColor', { r: 1, g: 0, b: 1, a: 1 }); // Pink ``` ``` getSettingColor(keypath: SettingsColor): Color ``` Get a color setting. * `keypath`: The settings keypath, e.g. `highlightColor`. * Throws An error, if the keypath is invalid. ``` getSettingColor(keypath: `ubq://${SettingsColor}`): Color ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.getSettingColor('highlightColor'); ``` ``` setSettingEnum(keypath: T, value: SettingsEnum[T]): void ``` Set an enum setting. * `keypath`: The settings keypath, e.g. `doubleClickSelectionMode`. * `value`: The enum value as string. ``` setSettingEnum(keypath: `ubq://${T}`, value: SettingsEnum[T]): void ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.setSettingEnum('doubleClickSelectionMode', 'Direct'); ``` ``` getSettingEnum(keypath: T): SettingsEnum[T] ``` Get an enum setting. * `keypath`: The settings keypath, e.g. `doubleClickSelectionMode`. * Returns The value as string. ``` getSettingEnum(keypath: `ubq://${T}`): SettingsEnum[T] ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.getSettingEnum('doubleClickSelectionMode'); ``` ``` getSettingEnumOptions(keypath: T): string[] ``` Get the possible enum options for a given enum setting. * `keypath`: The settings keypath, e.g. `doubleClickSelectionMode`. * Returns The possible enum options as strings. ``` getSettingEnumOptions(keypath: `ubq://${T}`): string[] ``` Support for `ubq://` prefixed keypaths will be removed in a future release. ``` engine.editor.getSettingEnumOptions('doubleClickSelectionMode'); ``` ``` getRole(): RoleString ``` Get the current role of the user ``` engine.editor.getRole(); ``` ``` setRole(role: RoleString): void ``` Set the role of the user and apply role-dependent defaults for scopes and settings ``` engine.editor.setRole('Adopter'); ``` Adjusting the Content of the Inspector Bar - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/api/inspectorBar/ CESDK/CE.SDK/Web Editor/Customization/API Reference # Adjusting the Content of the Inspector Bar Learn how to adjust the content of the inspector bar in the editor There are 2 APIs for getting and setting the order of components in the Inspector Bar. The content of the Inspector Bar changes based on the current [edit mode](/docs/cesdk/engine/api/editor-state/#state) (`'Transform'` (the default), `'Text'`, `'Crop'`, `'Trim'`, or a custom value), so both APIs accept an `orderContext` argument to specify the mode. For example usage of these APIs, see also [Moving Existing Buttons](/docs/cesdk/ui/customization/guides/movingExistingButtons/#rearranging-the-navigation-bar) in the Guides section. ### Get the Current Order ``` cesdk.ui.getInspectorBarOrder( orderContext: OrderContext = { editMode: 'Transform' } ) ``` When omitting the `orderContext` parameter, the order for the `'Transform'` edit mode is returned, e.g. ``` cesdk.ui.getInspectorBarOrder(); // => [ // {id: 'ly.img.text.typeFace.inspectorBar'}, // {id: 'ly.img.text.fontSize.inspectorBar'}, // ... // ] ``` ### Set a new Order ``` cesdk.ui.setInspectorBarOrder( inspectorBarOrder: (InspectorBarComponentId | OrderComponent)[], orderContext: OrderContext = { editMode: 'Transform' } ) ``` When omitting the `orderContext` parameter, the order is set for the default edit mode (`'Transform'`), e.g.: ``` // Sets the order for transform mode by default cesdk.ui.setInspectorBarOrder([ 'my.component.for.transform.mode']); ``` ## Inspector Bar Components The following lists the default Inspector Bar components available within CE.SDK. Take special note of the "Feature ID" column. Most components can be hidden/disabled by disabling the corresponding feature using the [Feature API](/docs/cesdk/ui/customization/api/features/). Also note that many components are only rendered for the block types listed in the "Renders for" column, because their associated controls (e.g. font size) are only meaningful for specific kinds of blocks (e.g. text). ### Layout Helpers Component ID Description Component ID `ly.img.separator` Description Adds a vertical separator (`
` element) in the Inspector Bar. Separators follow some special layouting rules: \- If 2 or more separators end up next to each other (e.g. due to other components not rendering), **only 1** separator will be rendered. \- Separators that end up being the first or last element in the Inspector Bar will **not** be rendered. \- Separators directly adjacent _to the left side_ of a spacer (see below) will **not** be rendered. Component ID `ly.img.spacer` Description Adds horizontal spacing in the Inspector Bar. Spacers will try to fill all available whitespace, by distributing the available space between all spacers found in the Inspector Bar. ### Common Controls These components are useful for editing various different block types. Component ID Description Feature ID Renders for Component ID `ly.img.fill.inspectorBar` Description Fill controls button: Opens the Fill panel, containing controls for selecting the fill type (Color, Image, Video, Audio) , and for editing the fill. For Color, this contains the Color Picker and the Color Library. For Images, this contains a Preview card, a "Replace" button to replace the media, and a "Crop" button for opening the Crop panel. For Video, this contains a Preview card, a "Replace" button to replace the media, a "Crop" button for opening the Crop panel, a "Trim" button for opening the Trim panel, and a Volume slider. Feature ID `ly.img.fill` Renders for All blocks except: Stickers and Cutouts Component ID `ly.img.combine.inspectorBar` Description Combine button: Opens a dropdown offering a choice of boolean operations (Union, Subtract, Intersect, Exclude). Feature ID `ly.img.combine` Renders for Selections containing multiple Shapes or Cutouts Component ID `ly.img.crop.inspectorBar` Description Crop button: Enters Crop mode when pressed. See the section on [Crop Mode](/docs/cesdk/ui/customization/api/inspectorBar/#crop-mode) for components used during that mode. Feature ID `ly.img.crop` Renders for Image, Video, Shapes with Image Fill Component ID `ly.img.stroke.inspectorBar` Description Stroke controls button: Renders a labeled color swatch button when stroke is inactive, which opens the Color Panel when pressed. When stroke is active, renders 2 additional controls: a "Width" button opening a dropdown containing a number input to control stroke width, and a "Style" button opening a dropdown containing a selection of stroke styles (Solid, Dashed, Dashed Round, Long Dashes, Long Dashed Round, Dotted). Feature ID `ly.img.stroke` Renders for Image, Shape, Text, Video Component ID `ly.img.adjustment.inspectorBar` Description Adjustment button: Opens the Adjustment panel containing sliders to influence numerous image properties (Brightness, Saturation, Contrast, Gamma, Clarity, Exposure, Shadows, Highlights, Blacks, Whites, Temperature, Sharpness). Feature ID `ly.img.adjustment` Renders for Image, Shape, Text, Video Component ID `ly.img.filter.inspectorBar` Description Filter button: Opens the Filter panel containing a large selection of image filters, and an "Intensity" slider for the currently selected filter. Feature ID `ly.img.filter` Renders for Image, Shape, Text, Video Component ID `ly.img.effect.inspectorBar` Description Effect button: Opens the Effect panel containing a large selection of image effects, and various sliders controlling the properties of the selected effect. Feature ID `ly.img.effect` Renders for Image, Shape, Text, Video Component ID `ly.img.blur.inspectorBar` Description Blur button: Opens the Blur panel containing a selection of blur effects, and various sliders controlling the properties of the selected blur. Feature ID `ly.img.blur` Renders for Image, Shape, Text, Video Component ID `ly.img.shadow.inspectorBar` Description Shadow controls button: Opens the Shadow panel containing a "Color" Subinspector controlling the shadow color, an "Angle" number input controlling the shadow offset direction, a "Distance" number input controlling the shadow offset, and a "Blur" number input controlling the shadows blur intensity). Feature ID `ly.img.shadow` Renders for All blocks expect Pages, Cutouts, and Groups Component ID `ly.img.position.inspectorBar` Description Position button: Opens a dropdown containing a selection of position commands (Bring to Front, Bring Forward, Send Backward, Send to Back, Fixed to Front, Fixed to Back, Align Left/Centered/Right/Top/Middle/Bottom) Feature ID `ly.img.position` Renders for Any block except: Pages. Selections containing multiple elements. Component ID `ly.img.options.inspectorBar` Description More options button: Opens a dropdown containing an Opacity slider (if opacity is supported by the selected block), a Blend mode button opening a dropdown containing a selection of different blend modes (if blending is supported by the selected block), a "Copy Element" button to copy the element, a "Paste Element" button to insert a previously copied element, and a "Show Inspector" button that opens the Advanced UI Inspector when pressed. Feature ID `ly.img.options` Renders for Every block ### Text These components are relevant for editing text blocks, and will only render when a text block is selected. Component ID Description Feature ID Component ID `ly.img.text.typeFace.inspectorBar` Description Typeface selection button: Opens a dropdown containing available typefaces. Feature ID `ly.img.text.typeface` Component ID `ly.img.text.fontSize.inspectorBar` Description Font size controls: A labeled number input to set the font size, with a button to open a dropdown containing many preset sizes and the "Auto-Size" option. Feature ID `ly.img.text.fontSize` Component ID `ly.img.text.bold.inspectorBar` Description Bold font toggle: Toggles the bold cut for the selected text, if available in the currently used font. Feature ID `ly.img.text.fontStyle` Component ID `ly.img.text.italic.inspectorBar` Description Italic font toggle: Toggles the italic cut for the selected text, if available in the currently used font. Feature ID `ly.img.text.fontStyle` Component ID `ly.img.text.alignHorizontal.inspectorBar` Description Text alignment button: Opens a dropdown containing options to align the selected text horizontally (to the left, to the right, or centered). Feature ID `ly.img.text.alignment` ### Shape These components are relevant for editing shape blocks, and will only render when a shape block is selected. Component ID Description Feature ID Component ID `ly.img.shape.options.inspectorBar` Description Shape options button: Opens a dropdown containing sliders to set number of sides, number of points, and inner diameter (depending on the specific shape selected). Only renders when a shape block is selected. Feature ID `ly.img.shape.options` ### Cutout These components are relevant for editing cutouts, and will only render when a cutout block is selected. Component ID Description Feature ID Component ID `ly.img.cutout.type.inspectorBar` Description Cutout type button: Opens a dropdown to select the cut type (Cut, Perforated). Feature ID `ly.img.cutout` Component ID `ly.img.cutout.offset.inspectorBar` Description Cutout offset button: Opens a dropdown containing a labeled number input to set the cutout offset. Feature ID `ly.img.cutout` Component ID `ly.img.cutout.smoothing.inspectorBar` Description Cutout smoothing button: Opens a dropdown containing a labeled slider controlling the outline smoothing. Feature ID `ly.img.cutout` ### Video, Audio These components are relevant for editing video and audio. Component ID Description Feature ID Renders for Component ID `ly.img.trim.inspectorBar` Description Trim button: Enters Trim mode when pressed. See the section on [Trim Mode](/docs/cesdk/ui/customization/api/inspectorBar/#trim-mode) for components used during that mode. Feature ID `ly.img.trim` Renders for Video, Audio Component ID `ly.img.volume.inspectorBar` Description Volume control for audio and video. Feature ID `ly.img.volume` Renders for Video, Audio Component ID `ly.img.audio.replace.inspectorBar` Description Replace button: Opens the "Replace Audio" panel when pressed. Feature ID `ly.img.replace` Renders for Audio ### Groups Component ID Description Feature ID Renders for Component ID `ly.img.group.create.inspectorBar` Description Group button: When pressed, creates a new group from all selected elements. Feature ID `ly.img.group` Renders for Selections containing multiple elements Component ID `ly.img.group.ungroup.inspectorBar` Description Ungroup button: When pressed, the selected group is dissolved, and all contained elements are released. Feature ID `ly.img.group` Renders for Groups ### Crop This component only appears in Crop mode by default. Registering it for other edit modes is not meaningful. Component ID Description Feature ID Component ID `ly.img.cropControls.inspectorBar` Description Controls used when cropping images: These include a "Done" button to finish cropping, a "Straighten" slider for rotating the image, a "Turn" button to rotate the image by 90 degrees, "Mirror" buttons to flip the image vertically/horizontally, and a "Reset" button to reset the crop.) Feature ID `ly.img.crop` ### Trim This component only appears in Trim mode by default. Registering it for other edit modes is not meaningful. Component ID Description Feature ID Component ID `ly.img.trimControls.inspectorBar` Description Controls used when trimming video and audio: These include a "Play" button to preview the trimmed video, a "Duration" number input to set the trim duration, a video/audio strip visualizing the trimmed section in relation to the full media, and a "Done" button to finish trimming. Feature ID `ly.img.trim` ## Default Order The default order of the Inspector Bar is the following: ### Transform Mode ``` [ 'ly.img.text.typeFace.inspectorBar', 'ly.img.text.fontSize.inspectorBar', 'ly.img.shape.options.inspectorBar', 'ly.img.cutout.type.inspectorBar', 'ly.img.cutout.offset.inspectorBar', 'ly.img.cutout.smoothing.inspectorBar', 'ly.img.group.create.inspectorBar', 'ly.img.group.ungroup.inspectorBar', 'ly.img.audio.replace.inspectorBar', 'ly.img.separator', 'ly.img.text.bold.inspectorBar', 'ly.img.text.italic.inspectorBar', 'ly.img.text.alignHorizontal.inspectorBar', 'ly.img.combine.inspectorBar', 'ly.img.separator', 'ly.img.fill.inspectorBar', 'ly.img.trim.inspectorBar', 'ly.img.volume.inspectorBar', 'ly.img.crop.inspectorBar', 'ly.img.separator', 'ly.img.stroke.inspectorBar', 'ly.img.separator', 'ly.img.adjustment.inspectorBar', 'ly.img.filter.inspectorBar', 'ly.img.effect.inspectorBar', 'ly.img.blur.inspectorBar', 'ly.img.separator', 'ly.img.shadow.inspectorBar', 'ly.img.spacer', 'ly.img.separator', 'ly.img.position.inspectorBar', 'ly.img.separator', 'ly.img.options.inspectorBar' ] ``` ### Crop Mode ``` [ 'ly.img.cropControls.inspectorBar' ] ``` ### Trim Mode ``` [ 'ly.img.trimControls.inspectorBar' ] ``` [ Previous Canvas Menu ](/docs/cesdk/ui/customization/api/canvasMenu/)[ Next Navigation Bar ](/docs/cesdk/ui/customization/api/navigationBar/) How to Use Animations - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/using-animations/?platform=web&language=javascript # How to Use Animations CreativeEditor SDK supports many different types of configurable animations for animating the appearance of design blocks in video scenes. Similarly to blocks, each animation object has a numeric id which can be used to query and [modify its properties](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-animations?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Using+Animations&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-animations). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-animations?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Using+Animations&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-animations) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/index.js'; const config = { license: 'mtLT-_GJwMhE7LDnO8KKEma7qSuzWuDxiKuQcxHKmz3fjaXWY2lT3o3Z2VdL5twm', userId: 'guides-user', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.33.0/assets' }; CreativeEngine.init(config).then(async (engine) => { document.getElementById('cesdk_container').append(engine.element); const scene = engine.scene.createVideo(); const page = engine.block.create('page'); engine.block.setWidth(page, 1920); engine.block.setHeight(page, 1080); engine.block.appendChild(scene, page); engine.editor.setSettingColor('clearColor', { r: 0.2, g: 0.2, b: 0.2, a: 1 }); const graphic = engine.block.create('graphic'); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_star.svg' ); engine.block.setFill(graphic, imageFill); engine.block.setShape(graphic, engine.block.createShape('rect')); engine.block.setWidth(graphic, 500); engine.block.setHeight(graphic, 500); engine.block.setPositionX(graphic, (1920 - 500) / 2); engine.block.setPositionY(graphic, (1080 - 500) / 2); engine.block.appendChild(page, graphic); engine.scene.zoomToBlock(page, 60, 60, 60, 60); ``` ## Accessing Animation APIs In order to query whether a block supports animations, you should call the `supportsAnimation(id: number): boolean` API. ``` if (!engine.block.supportsAnimation(graphic)) { return; } ``` ## Animation Categories There are three different categories of animations: _In_, _Out_ and _Loop_ animations. ### In Animations _In_ animations animate a block for a specified duration after the block first appears in the scene. For example, if a block has a time offset of 4s in the scene and it has an _In_ animation with a duration of 1s, then the appearance of the block will be animated between 4s and 5s with the _In_ animation. ### Out Animations _Out_ animations animate a block for a specified duration before the block disappears from the scene. For example, if a block has a time offset of 4s in the scene and a duration of 5s and it has an _Out_ animation with a duration of 1s, then the appearance of the block will be animated between 8s and 9s with the _Out_ animation. ### Loop Animations _Loop_ animations animate a block for the total duration that the block is visible in the scene. _Loop_ animations also run simultaneously with _In_ and _Out_ animations, if those are present. ## Creating Animations In order to create a new animation, we must call the `createAnimation(type: string): number` API. We currently support the following _In_ and _Out_ animation types: * `'//ly.img.ubq/animation/slide'` * `'//ly.img.ubq/animation/pan'` * `'//ly.img.ubq/animation/fade'` * `'//ly.img.ubq/animation/blur'` * `'//ly.img.ubq/animation/grow'` * `'//ly.img.ubq/animation/zoom'` * `'//ly.img.ubq/animation/pop'` * `'//ly.img.ubq/animation/wipe'` * `'//ly.img.ubq/animation/baseline'` * `'//ly.img.ubq/animation/crop_zoom'` * `'//ly.img.ubq/animation/spin'` * `'//ly.img.ubq/animation/typewriter_text'` (text-only) * `'//ly.img.ubq/animation/block_swipe_text'` (text-only) * `'//ly.img.ubq/animation/merge_text'` (text-only) * `'//ly.img.ubq/animation/spread_text'` (text-only) and the following _Loop_ animation types: * `'//ly.img.ubq/animation/spin_loop'` * `'//ly.img.ubq/animation/fade_loop'` * `'//ly.img.ubq/animation/blur_loop'` * `'//ly.img.ubq/animation/pulsating_loop'` * `'//ly.img.ubq/animation/breathing_loop'` * `'//ly.img.ubq/animation/jump_loop'` * `'//ly.img.ubq/animation/squeeze_loop'` * `'//ly.img.ubq/animation/sway_loop'` Note: short types are also accepted, e.g. `'slide'` instead of `'//ly.img.ubq/animation/slide'`. ``` const slideInAnimation = engine.block.createAnimation('slide'); const breathingLoopAnimation = engine.block.createAnimation('breathing_loop'); const fadeOutAnimation = engine.block.createAnimation('fade'); ``` ## Assigning Animations In order to assign an _In_ animation to the block, call the `setInAnimation(id: number, animation: number): void` API. ``` engine.block.setInAnimation(graphic, slideInAnimation); ``` In order to assign a _Loop_ animation to the block, call the `setLoopAnimation(id: number, animation: number): void` API. ``` engine.block.setLoopAnimation(graphic, breathingLoopAnimation); ``` In order to assign an _Out_ animation to the block, call the `setOutAnimation(id: number, animation: number): void` API. ``` engine.block.setLoopAnimation(graphic, breathingLoopAnimation); ``` To query the current animation ids of a design block, call the `getInAnimation(id: number): number`, `getLoopAnimation(id: number): number` or `getOutAnimation(id: number): number` API. You can now pass this id into other APIs in order to query more information about the animation, e.g. its type via the `getType(id: number): string` API. ``` const animation = engine.block.getLoopAnimation(graphic); const animationType = engine.block.getType(animation); ``` When replacing the animation of a design block, remember to destroy the previous animation object if you don't intend to use it any further. Animation objects that are not attached to a design block will never be automatically destroyed. Destroying a design block will also destroy all of its attached animations. ``` const squeezeLoopAnimation = engine.block.createAnimation('squeeze_loop'); engine.block.destroy(engine.block.getLoopAnimation(graphic)); engine.block.setLoopAnimation(graphic, squeezeLoopAnimation); /* The following line would also destroy all currently attached animations */ // engine.block.destroy(graphic); ``` ## Animation Properties Just like design blocks, animations with different types have different properties that you can query and modify via the API. Use `findAllProperties(id: number): string[]` in order to get a list of all properties of a given animation. For the slide animation in this example, the call would return `['name', 'animation/slide/direction', 'animationEasing', 'includedInExport', 'playback/duration', 'type', 'uuid']`. Please refer to the [API docs](/docs/cesdk/engine/api/block-animation-types/) for a complete list of all available properties for each type of animation. ``` const allAnimationProperties = engine.block.findAllProperties(slideInAnimation); ``` Once we know the property keys of an animation, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `setFloat(id: number, property: string, value: number): void` in order to change the direction of the slide animation to make our block slide in from the top. ``` engine.block.setFloat(slideInAnimation, 'animation/slide/direction', 0.5 * Math.PI); ``` All animations have a duration. For _In_ and _Out_ animations, the duration defines the total length of the animation as described above. For _Loop_ animations, the duration defines the length of each loop cycle. We can use the `setDuration(id: number, value: number): void` API in order to change the animation duration. Note that changing the duration of an _In_ animation will automatically adjust the duration of the _Out_ animation (and vice versa) in order to avoid overlaps between the two animations. ``` engine.block.setDuration(slideInAnimation, 0.6); ``` Some animations allow you to configure their easing behavior by choosing from a list of common easing curves. The easing controls the acceleration throughout the animation. We can use the `setEnum(id: number, value: number): void` API in order to change the easing curve. Call `engine.block.getEnumValues('animationEasing')` in order to get a list of currently supported easing options. In this example, we set the easing to `EaseOut` so that the animation starts fast and then slows down towards the end. An `EaseIn` easing would start slow and then speed up, while `EaseInOut` starts slow, speeds up towards the middle of the animation and then slows down towards the end again. ``` engine.block.setEnum(slideInAnimation, 'animationEasing', 'EaseOut'); console.log("Available easing options:", engine.block.getEnumValues('animationEasing')); ``` ### Text Animation Properties When applied to text blocks, some animations allow you to control whether the animation should be applied to the entire text at once, line by line, word by word or character by character. We can use the `setEnum(id: number, value: number): void` API in order to change the text writing style. Call `engine.block.getEnumValues('textAnimationWritingStyle')` in order to get a list of currently supported text writing style options. The default writing style is `Line`. In this example, we set the easing to `Word` so that the text animates in one word at a time. ``` const text = engine.block.create('text'); const textAnimation = engine.block.createAnimation('baseline'); engine.block.setInAnimation(text, textAnimation); engine.block.appendChild(page, text); engine.block.setPositionX(text, 100); engine.block.setPositionY(text, 100); engine.block.setWidthMode(text, 'Auto'); engine.block.setHeightMode(text, 'Auto'); engine.block.replaceText(text, "You can animate text\nline by line,\nword by word,\nor character by character\nwith CE.SDK"); engine.block.setEnum(textAnimation, 'textAnimationWritingStyle', 'Word'); engine.block.setDuration(textAnimation, 2.0); engine.block.setEnum(textAnimation, 'animationEasing', 'EaseOut'); ``` Together with the writing style, you can also configure the overlap between the individual segments of a text animation using the `textAnimationOverlap` property. With an overlap value of `0`, the next segment only starts its animation once the previous segment's animation has finished. With an overlap value of `1`, all segments animate at the same time. ``` const text2 = engine.block.create('text'); const textAnimation2 = engine.block.createAnimation('pan'); engine.block.setInAnimation(text2, textAnimation2); engine.block.appendChild(page, text2); engine.block.setPositionX(text2, 100); engine.block.setPositionY(text2, 500); engine.block.setWidth(text2, 500); engine.block.setHeightMode(text2, 'Auto'); engine.block.replaceText(text2, "You can use the textAnimationOverlap property to control the overlap between text animation segments."); engine.block.setFloat(textAnimation2, 'textAnimationOverlap', 0.4); engine.block.setDuration(textAnimation2, 1.0); engine.block.setEnum(textAnimation2, 'animationEasing', 'EaseOut'); ``` Customize The Asset Library - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/guides/customize-asset-library/?platform=web&language=js # Customize The Asset Library In this example, we will show you how to customize the asset library in [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/guides-customize-the-asset-library?title=IMG.LY%27s+CE.SDK%3A+Guides+Customize+The+Asset+Library&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/guides-customize-the-asset-library). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/guides-customize-the-asset-library?title=IMG.LY%27s+CE.SDK%3A+Guides+Customize+The+Asset+Library&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/guides-customize-the-asset-library) The asset library is used to find assets to insert them into a scene or replace a currently selected asset. By default, we have already added a configuration of entries shown in the dock panel for images, text, stickers, shapes, and more. You can add and remove every entry or change the appearance of any entry. Every entry has a couple of configuration options to determine what and how assets will be displayed. See the documentation on the [Asset Library Entry](/docs/cesdk/ui/customization/api/assetLibraryEntry/) for more information. ``` instance.ui.addAssetLibraryEntry({ id: 'empty-custom-asset-source', sourceIds: ['emptySource'], previewLength: 3, gridColumns: 3, gridItemHeight: 'square', previewBackgroundType: 'contain', gridBackgroundType: 'contain', icon: ({ theme, iconSize }) => { if (theme === 'dark') { if (iconSize === 'normal') { return 'https://img.ly/static/cesdk/guides/icon-normal-dark.svg'; } else { return 'https://img.ly/static/cesdk/guides/icon-large-dark.svg'; } } if (iconSize === 'normal') { return 'https://img.ly/static/cesdk/guides/icon-normal-light.svg'; } else { return 'https://img.ly/static/cesdk/guides/icon-large-light.svg'; } } }); instance.ui.addAssetLibraryEntry({ id: 'empty-custom-asset-source-for-replace', sourceIds: ['emptySource'], previewLength: 3, gridColumns: 3, gridItemHeight: 'square' }); instance.ui.addAssetLibraryEntry({ id: 'custom-images', sourceIds: ['ly.img.image'], previewLength: 5, gridColumns: 5, icon: 'https://img.ly/static/cesdk/guides/icon-normal-dark.svg' }); ``` Adding, changing, or removing entries is done using the [Asset Library Entries API](/docs/cesdk/ui/customization/api/assetLibraryEntry/#managing-asset-library-entries). You can for example use `cesdk.ui.updateAssetLibraryEntry` to change the number of grid columns in the images library. ``` instance.ui.updateAssetLibraryEntry('ly.img.image', { ...instance.ui.getAssetLibraryEntry('ly.img.image'), gridColumns: 4 }); ``` The basics of customizing the entries for replacement are the same as for insertion. The main difference is that these entries might vary depending on what block is currently selected and that the returned entries will determine if a replace action is shown on a selected block. Most of the time it makes sense to provide a custom function to return different entries based on the type or fill of a block. If no replacement should be available for the current block, an empty array has to be returned. The UI will not show a replace button in that case. As an argument to the callback, you will get the current context in the editor, mainly what blocks are selected, their block type as well as fills. This makes it more convenient to quickly decide what to return, but of course, you can also use the API for further checks as well. ``` instance.ui.setReplaceAssetLibraryEntries( ({ defaultEntryIds, selectedBlocks }) => { if (selectedBlocks.length !== 1) { return []; } const [selectedBlock] = selectedBlocks; if (selectedBlock.blockType === '//ly.img.ubq/graphic') { return [...defaultEntryIds, 'empty-custom-asset-source-for-replace']; } return []; } ); ``` If you do not use a custom `title` function for an entry, you need a translation provided with the following keys in the `i18n` configuration: * `libraries..label` The label used in the dock panel * `libraries..label` The label for groups for a given entry. * `libraries...label` The label for a source in the source overview of a given entry * `libraries....label` The label for a given group in the group overview for a given entry. * `libraries..label` The label used for a source in all entries * `libraries..label` The label for groups for source in all entries. ``` i18n: { en: { 'libraries.empty-custom-asset-source.label': 'Empty' } }, ``` Blur - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-blur/?platform=web&language=javascript # Blur In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a block's blur through the `block` API. Blurs can be added to any shape or page. You can create and configure individual blurs and apply them to blocks. Blocks support at most one blur at a time. The same blur may be used for different blocks at the same time. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Blur To create a blur, simply use `createBlur`. ``` createBlur(type: BlurType): DesignBlockId ``` Create a new blur, fails if type is unknown or not a valid blur type. * `type`: The type id of the block. * Returns The handle of the newly created blur. We currently support the following blur types: * `'//ly.img.ubq/blur/uniform'` * `'//ly.img.ubq/blur/linear'` * `'//ly.img.ubq/blur/mirrored'` * `'//ly.img.ubq/blur/radial'` Note: Omitting the prefix is also accepted, e.g. `'uniform'` instead of `'//ly.img.ubq/blur/uniform'`. ``` const radialBlur = engine.block.createBlur('radial'); ``` ## Configuring a Blur You can configure blurs just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` engine.block.setFloat(radialBlur, 'radial/radius', 100); ``` ## Functions ``` setBlur(id: DesignBlockId, blurId: DesignBlockId): void ``` Connects `block`'s blur to the given `blur` block. Required scope: 'appearance/blur' * `id`: The block to update. * `blurId`: A 'blur' block. ``` engine.block.setBlur(block, radialBlur); ``` ``` setBlurEnabled(id: DesignBlockId, enabled: boolean): void ``` Enable or disable the blur of the given design block. * `id`: The block to update. * `enabled`: The new enabled value. ``` engine.block.setBlurEnabled(block, true); ``` ``` isBlurEnabled(id: DesignBlockId): boolean ``` Query if blur is enabled for the given block. * `id`: The block to query. * Returns True, if the blur is enabled. False otherwise. ``` const isBlurEnabled = engine.block.isBlurEnabled(block); ``` ``` supportsBlur(id: DesignBlockId): boolean ``` Checks whether the block supports blur. * `id`: The block to query. * Returns True, if the block supports blur. ``` if (engine.block.supportsBlur(block)) { ``` ``` getBlur(id: DesignBlockId): DesignBlockId ``` Get the 'blur' block of the given design block. * `id`: The block to query. * Returns The 'blur' block. ``` const existingBlur = engine.block.getBlur(block); ``` Variables - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/variables/?platform=web&language=javascript # Variables In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify variables through the `variable` API. The `variable` API lets you set or get the contents of variables that exist in your scene. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` findAll(): string[] ``` Get all text variables currently stored in the engine. * Returns Return a list of variable names ``` const variableNames = engine.variable.findAll(); ``` ``` setString(key: string, value: string): void ``` Set a text variable. * `key`: The variable's key. * `value`: The text to replace the variable with. ``` engine.variable.setString('my_custom_variable', 'IMG.LY'); ``` ``` getString(key: string): string ``` Set a text variable. * `key`: The variable's key. * Returns The text value of the variable. ``` const name = engine.variable.getString('my_custom_variable'); ``` ``` remove(key: string): void ``` Destroy a text variable. * `key`: The variable's key. ``` variableNames.forEach((name) => { engine.variable.remove(name); }); ``` ``` referencesAnyVariables(id: DesignBlockId): boolean ``` Checks whether the given block references any variables. Doesn't check the block's children. * `id`: The block to inspect. * Returns true if the block references variables and false otherwise. ``` engine.block.referencesAnyVariables(block) ``` ## Localizing Variable Keys (CE.SDK only) You can show localized labels for the registered variables to your users by adding a corresponding label property to the object stored at `i18n..variables..label` in the configuration. Otherwise, the name used in `variable.setString()` will be shown. ![](/docs/cesdk/53ae2ef96327bbd7ef5994cdfe73e792/variables-dark.png) How to Use Shapes - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/using-shapes/?platform=web&language=javascript # How to Use Shapes The `graphic` [design block](/docs/cesdk/engine/guides/blocks/) in CE.SDK allows you to modify and replace its shape. CreativeEditor SDK supports many different types of shapes, such as rectangles, lines, ellipses, polygons and custom vector paths. Similarly to blocks, each shape object has a numeric id which can be used to query and [modify its properties](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-shapes?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Using+Shapes&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-shapes). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-shapes?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Using+Shapes&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-using-shapes) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` import CreativeEngine from 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/index.js'; const config = { license: 'mtLT-_GJwMhE7LDnO8KKEma7qSuzWuDxiKuQcxHKmz3fjaXWY2lT3o3Z2VdL5twm', userId: 'guides-user', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/assets' }; CreativeEngine.init(config).then(async (engine) => { document.getElementById('cesdk_container').append(engine.element); const scene = engine.scene.create(); engine.editor.setSettingBool('page/dimOutOfPageAreas', false); const graphic = engine.block.create('graphic'); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(graphic, imageFill); engine.block.setWidth(graphic, 100); engine.block.setHeight(graphic, 100); engine.block.appendChild(scene, graphic); engine.scene.zoomToBlock(graphic, 40, 40, 40, 40); ``` ## Accessing Shapes In order to query whether a block supports shapes, you should call the `supportsShape(id: number): boolean` API. Currently, only the `graphic` design block supports shape objects. ``` engine.block.supportsShape(graphic); // Returns true const text = engine.block.create('text'); engine.block.supportsShape(text); // Returns false ``` ## Creating Shapes `graphic` blocks don't have any shape after you create them, which leaves them invisible by default. In order to make them visible, we need to assign both a shape and a fill to the `graphic` block. You can find more information on fills [here](/docs/cesdk/engine/guides/using-fills/). In this example we have created and attached an image fill. In order to create a new shape, we must call the `createShape(type: string): number` API. We currently support the following shape types: * `'//ly.img.ubq/shape/rect'` * `'//ly.img.ubq/shape/line'` * `'//ly.img.ubq/shape/ellipse'` * `'//ly.img.ubq/shape/polygon'` * `'//ly.img.ubq/shape/star'` * `'//ly.img.ubq/shape/vector_path'` Note: short types are also accepted, e.g. `'rect'` instead of `'//ly.img.ubq/shape/rect'`. ``` const rectShape = engine.block.createShape('rect'); ``` In order to assign this shape to the `graphic` block, call the `setShape(id: number, shape: number): void` API. ``` engine.block.setShape(graphic, rectShape); ``` To query the shape id of a design block, call the `getShape(id: number): number` API. You can now pass this id into other APIs in order to query more information about the shape, e.g. its type via the `getType(id: number): string` API. ``` const shape = engine.block.getShape(graphic); const shapeType = engine.block.getType(shape); ``` When replacing the shape of a design block, remember to destroy the previous shape object if you don't intend to use it any further. Shape objects that are not attached to a design block will never be automatically destroyed. Destroying a design block will also destroy its attached shape block. ``` const starShape = engine.block.createShape('star'); engine.block.destroy(engine.block.getShape(graphic)); engine.block.setShape(graphic, starShape); /* The following line would also destroy the currently attached starShape */ // engine.block.destroy(graphic); ``` ## Shape Properties Just like design blocks, shapes with different types have different properties that you can query and modify via the API. Use `findAllProperties(id: number): string[]` in order to get a list of all properties of a given shape. For the star shape in this example, the call would return `['name', 'shape/star/innerDiameter', 'shape/star/points', 'type', 'uuid']`. Please refer to the [API docs](/docs/cesdk/engine/api/block-shape-types/) for a complete list of all available properties for each type of shape. ``` const allShapeProperties = engine.block.findAllProperties(starShape); ``` Once we know the property keys of a shape, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `setInt(id: number, property: string, value: number): void` in order to change the number of points of the star to six. ``` engine.block.setInt(starShape, 'shape/star/points', 6); ``` Vue.js Image Editor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/photo-editor/vuejs/ CESDK/CE.SDK/Web Editor/Solutions/Photo Editor # Vue.js Image Editor SDK The CreativeEditor SDK provides a powerful and intuitive solution designed for seamless photo editing directly in the browser. This CE.SDK configuration is fully customizable and offers a range of features that cater to various use cases, from simple photo adjustments and image compositions with background removal to programmatic editing at scale. Whether you are building a photo editing application for social media, e-commerce, or any other platform, the CE.SDK Vue.js Image Editor provides the tools you need to deliver a best-in-class user experience. [Explore Demo](https://img.ly/showcases/cesdk/photo-editor-ui/web) ## Key Capabilities of the Vue.js image Editor ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transform Easily perform operations like cropping, rotating, and resizing your design elements to achieve the perfect composition. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Management Import and manage stickers, images, shapes, and other assets to build intricate and visually appealing designs. ![images](/docs/cesdk/static/TextEditing-2529ca84ae5a97dd9bd3da74d0b86e85.png) ### Text Editing Add and style text blocks with a variety of fonts, colors, and effects, giving users the creative freedom to express themselves. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Processing All editing operations are performed directly in the browser, ensuring fast performance without the need for server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit designs using the engine API, allowing for automated workflows and advanced integrations within your application. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Enhance functionality with plugins and custom scripts, making it easy to tailor the editor to specific needs and use cases. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Design and integrate custom user interfaces that align with your application’s branding and user experience requirements. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Background Removal Utilize the powerful background removal plugin to allow users to effortlessly remove backgrounds from images, entirely on the Client-Side. ![images](/docs/cesdk/static/Filters-348c44197a3cde0e9982fd7560139502.png) ### Filters & Effects Choose from a wide range of filters and effects to add professional-grade finishing touches to photos, enhancing their visual appeal. ![images](/docs/cesdk/static/SizePresets-2fe404893ff6b91bb1695eb39ee9a658.png) ### Size Presets Access a variety of size presets tailored for different use cases, including social media formats and print-ready dimensions. ## Browser Support The CE.SDK Photo Editor is optimized for use in modern web browsers, ensuring compatibility with the latest versions of Chrome, Firefox, Edge, and Safari. ## Prerequisites To get started with the CE.SDK Photo Editor, ensure you have the latest versions of **Node.js** and **NPM** installed. ## Supported File Types The CE.SDK Photo Editor supports loading, editing, and saving various image formats directly in the browser. Supported formats include: * JPG * PNG * SVG * WEBP Exports can be made in formats such as JPG, PNG, and SVG, depending on your requirements. ## Getting Started If you're ready to start integrating CE.SDK into your Vue.js application, check out the CE.SDK [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/). In order to configure the editor for an image editing use case consult our [photo editor UI showcase](https://img.ly/showcases/cesdk/photo-editor-ui/web#c) and its [reference implementation](https://github.com/imgly/cesdk-web-examples/tree/main/showcase-photo-editor-ui/src/components/case/CaseComponent.jsx). ## Understanding CE.SDK Architecture & API The following sections provide an overview of the key components of the CE.SDK photo editor UI and its API architecture. If you're ready to start integrating CE.SDK into your Vue.js application, check out our [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/) or explore the Essential Guides. ### CreativeEditor Photo UI The CE.SDK photo editor UI is a specific configuration of the CE.SDK that focuses the Editor UI on essential photo editing features. It also includes our powerful background removal plugin that runs entirely on the user's device, saving on computing costs. This configuration can be further modified to suit your needs. Key components include: ![](/docs/cesdk/f6c8e768b60bb12f5dc766f8ca63ca62/CESDK-UI.png) * **Canvas:** The primary workspace where users interact with their photo content. * **Dock:** Provides access to tools and assets that are not directly related to the selected image or block, often used for adding or managing assets. * **Inspector Bar:** Controls properties specific to the selected block, such as size, rotation, and other adjustments. * **Canvas Menu:** Provides block-specific settings and actions such as deletion or duplication. * **Navigation Bar:** Offers global actions such as undo/redo, zoom controls, and access to export options. * **Canvas Bar:** For actions affecting the canvas or scene as a whole, such as adding pages or controlling zoom. This is an alternative place for actions like zoom or undo/redo. Learn more about interacting with and customizing the photo editor UI in our design editor UI guide. ### CreativeEngine At the heart of CE.SDK is the CreativeEngine, which powers all rendering and design manipulation tasks. It can be used in headless mode or integrated with the CreativeEditor UI. Key features and APIs provided by CreativeEngine include: * **Scene Management:** Create, load, save, and manipulate design scenes programmatically. * **Block Manipulation:** Create and manage elements such as images, text, and shapes within the scene. * **Asset Management:** Load and manage assets like images and SVGs from URLs or local sources. * **Variable Management:** Define and manipulate variables for dynamic content within scenes. * **Event Handling:** Subscribe to events such as block creation or selection changes for dynamic interaction. ## API Overview CE.SDK’s APIs are organized into several categories, each addressing different aspects of scene and content management. The engine API is relevant if you want to programmatically manipulate images to create or modify them at scale. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/): * **Creating Blocks**: ``` const block = engine.block.create('shapes/rectangle'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API:**](https://img.ly/docs/cesdk/engine/api/events/): * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ### Basic Automation Example The following automation example shows how to turn an image block into a square format for a platform such as Instagram: ``` // Assuming you have an initialized engine and a selected block (which is an image block) const newWidth = 1080; // Width in pixels const newHeight = 1080; // Height in pixels const imageBlockId = engine.block.findByType('image')[0]; engine.block.setWidth(imageBlockId, newWidth); engine.block.setHeight(imageBlockId, newHeight); engine.block.setContentFillMode(imageBlockId, 'Cover'); ``` ## Customizing the CE.SDK Photo Editor CE.SDK provides extensive customization options, allowing you to tailor the UI and functionality to meet your specific needs. This can range from basic configuration settings to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object:** Customize the editor’s appearance and functionality by passing a configuration object during initialization. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization:** Customize the language and labels used in the editor to support different locales. ``` const config = { i18n: { en: { variables: { my_custom_variable: { label: 'Custom Label' } } } } }; ``` * [Custom Asset Sources](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): Serve custom image or SVG assets from a remote URL. ### UI Customization Options * **Theme:** Select from predefined themes like 'dark' or 'light'. ``` const config = { theme: 'dark' }; ``` * **UI Components:** Enable or disable specific UI components as needed. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations For deeper customization, [explore the range of APIs](https://img.ly/docs/cesdk/ui/customization/) available for extending the functionality of the photo editor. You can customize the order of components, add new UI elements, and even develop your own plugins to introduce new features. ## Plugins For cases where encapsulating functionality for reuse is necessary, plugins provide an effective solution. Use our [guide on building plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) to get started, or explore existing plugins like **Background Removal** and **Vectorizer**. ## Framework Support CreativeEditor SDK’s photo editor is compatible with Vue.js and other JavaScript frameworks like React, Angular, Svelte, Blazor, Next.js, and TypeScript. It also integrates well with desktop and server-side technologies such as Electron, PHP, Laravel, and Rails. [ Vanilla JS ](/docs/cesdk/ui/quickstart/)[ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/quickstart?framework=react)[ Angular ](/docs/cesdk/ui/quickstart?framework=angular)[ Vue ](/docs/cesdk/ui/quickstart?framework=vue)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node)[ Apple ](/docs/cesdk/mobile-editor/quickstart/)[ Android ](/docs/cesdk/mobile-editor/quickstart?framework=composable&language=kotlin&platform=android) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,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)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous Angular ](/docs/cesdk/ui/solutions/photo-editor/angular/)[ Next Overview ](/docs/cesdk/mobile-editor/) Angular Creative Editor - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/design-editor/angular/ CESDK/CE.SDK/Web Editor/Solutions/Design Editor # Angular Creative Editor The CreativeEditor SDK offers a robust Angular library designed for crafting and editing rich visual designs directly within the browser. This CE.SDK configuration is fully customizable and extendable, providing a comprehensive suite of design editing features, such as templating, layout management, asset integration, and more, including advanced capabilities like background removal. [Explore Demo](https://img.ly/showcases/cesdk/default-ui/web) ## Key Features of the Angular Creative Editor SDK ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transforms Execute operations like cropping, rotating, and resizing design components. ![images](/docs/cesdk/static/Templating-5fc837e60dafb163c824ed6684fc7b0c.png) ### Templating Build and apply design templates with placeholders and text variables for dynamic content. ![images](/docs/cesdk/static/Placeholders-5b1a42097f1b9d363d48c30ca90fbcc2.png) ### Placeholders & Lockable Design Constrain templates to guide your users’ design and ensure brand consistency. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Handling Import and manage images, shapes, and other assets for your design projects. ![images](/docs/cesdk/static/VideoCollage-ed31587fdccad3fe7f335c6e70216355.png) ### Design Collages Arrange multiple elements on a single canvas to create intricate layouts. ![images](/docs/cesdk/static/TextEditing-2529ca84ae5a97dd9bd3da74d0b86e85.png) ### Text Editing Incorporate and style text blocks using various fonts, colors, and effects. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Operations All design editing tasks are performed directly in the browser, eliminating the need for server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit designs within your React application using the engine API. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Easily enhance functionality with plugins and custom scripts. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Develop and integrate custom UIs tailored to your application’s design requirements. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Background Removal This plugin simplifies the process of removing backgrounds from images entirely within the browser. ![images](/docs/cesdk/static/CutoutLines-fae102d8e0218cdf5070f7b344fad81e.png) ### Print Optimization Ideal for web-to-print scenarios, supporting spot colors and cut-outs. ## Browser Compatibility The CE.SDK Design Editor is optimized for use in modern web browsers, ensuring compatibility with the latest versions of Chrome, Firefox, Edge, and Safari. See the full list of [supported browsers here](https://img.ly/docs/cesdk/faq/browser-support/). ## Prerequisites [Install the latest stable version of **Node.js & NPM**](https://www.npmjs.com/get-npm) ## Supported Formats Creative Editor SDK supports loading, editing, and saving a variety of image formats directly in the browser, with no need for server-side dependencies. Supported formats include: * JPG * PNG * SVG * WEBP You can export individual assets or entire designs as PDF, JPG, PNG, or SVG files. ## Understanding CE.SDK Architecture & API The following sections provide an overview of the key components of the CE.SDK design editor UI and its API structure. If you're ready to begin integrating CE.SDK into your Angular application, refer to our [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/) or delve into the Essential Guides. ### CreativeEditor Design UI The CE.SDK design UI is optimized for intuitive design creation and editing. Key components and customizable elements within the UI include: ![](/docs/cesdk/f6c8e768b60bb12f5dc766f8ca63ca62/CESDK-UI.png) * **Canvas:** The primary interaction area for design content. * **Dock:** An entry point for interactions not directly related to the selected design block, often used for accessing asset libraries. * **Canvas Menu:** Access block-specific settings such as duplication or deletion. * **Inspector Bar:** Manage block-specific functionalities, such as adjusting the properties of the selected block. * **Navigation Bar:** Handles global scene actions like undo/redo and zoom. * **Canvas Bar:** Offers tools for managing the overall canvas, such as adding pages or controlling zoom. * **Layer Panel:** Manage the stacking order and visibility of design elements. Learn more about interacting with and manipulating design controls in our design editor UI guide. ### CreativeEngine The CreativeEngine is the core of CE.SDK, responsible for rendering and manipulating design scenes. It can operate in headless mode or be integrated with the CreativeEditor UI. Key features and APIs provided by CreativeEngine include: * **Scene Management:** Create, load, save, and modify design scenes programmatically. * **Block Manipulation:** Create and manage design elements such as shapes, text, and images. * **Asset Management:** Load assets like images and SVGs from URLs or local sources. * **Variable Management:** Define and manipulate variables within scenes for dynamic content. * **Event Handling:** Subscribe to events like block creation or updates for dynamic interaction. ## API Overview CE.SDK’s APIs are categorized into several groups, reflecting different aspects of scene management and manipulation. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/): * **Creating Blocks**: ``` const block = engine.block.create('shapes/rectangle'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) Variables enable dynamic content within scenes, allowing programmatic creation of design variations. * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API:**](https://img.ly/docs/cesdk/engine/api/events/): * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ## Customizing the Angular Creative Editor CE.SDK provides extensive customization options to adapt the UI to various use cases. These options range from simple configuration changes to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object:** When initializing the CreativeEditor, pass a configuration object that defines basic settings such as the base URL for assets, language, theme, and license key. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization:** Customize the language and labels used in the editor to support different locales. ``` const config = { i18n: { en: { variables: { my_custom_variable: { label: 'Custom Label' } } } } }; ``` * [Custom Asset Sources](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): Serve custom image or SVG assets from a remote URL. ### UI Customization Options * **Theme:** Select from predefined themes like 'dark' or 'light'. ``` const config = { theme: 'dark' }; ``` * **UI Components:** Enable or disable specific UI components as per your requirements. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations Explore more ways to extend editor functionality and customize its UI to your specific needs by consulting our detailed [customization guide](https://img.ly/docs/cesdk/ui/customization/guides/). Below is an overview of the available APIs and components. ### Order APIs The Order APIs manage the customization of the web editor's components and their order within these locations, allowing the addition, removal, or reordering of elements. Each location has its own Order API, such as `setDockOrder`, `setCanvasMenuOrder`, `setInspectorBarOrder`, `setNavigationBarOrder`, and `setCanvasBarOrder`. ### Layout Components CE.SDK offers special components for layout control, such as `ly.img.separator` for separating groups of components and `ly.img.spacer` for adding space between components. ### Registration of New Components Custom components can be registered and integrated into the web editor using builder components like buttons, dropdowns, and inputs. These components can replace default ones or introduce new functionalities, seamlessly integrating custom logic into the editor. ### Feature API The Feature API allows for the conditional display and functionality of components based on the current context, enabling dynamic customization. For instance, certain buttons can be hidden for specific block types. ## Plugins Customizing the CE.SDK web editor during its initialization is possible through the APIs outlined above. For many use cases, this will be sufficient. However, there are situations where encapsulating functionality for reuse is necessary. Plugins come in handy here. Follow our [guide on building your own plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) to learn more or explore some of the plugins we've created using this API: * **Background Removal**: Adds a button to the canvas menu to remove image backgrounds. * **Vectorizer**: Adds a button to the canvas menu to quickly vectorize a graphic. ## Framework Support CreativeEditor SDK’s video editing library is compatible with any Javascript including, React, Vue.js, Svelte, Blazor, Next.js, Typescript. It is also compatible with desktop and server-side technologies such as electron, PHP, Laravel and Rails. [ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/quickstart?framework=react)[ Vue ](/docs/cesdk/ui/quickstart?framework=vue)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVAAAAB4CAMAAACTvloEAAACK1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVv0cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVv0cAAAAAAAAAAAAAAACVv0cAAACZwkiVv0cAAACVv0cAAAAAAAAAAACEsEEAAAAAAAAAAAAAAACWwUYAAAAAAACYwUyVwVIAAAAAAAAAAACVv0eWv0eXwEmWv0eXv0eVv0eWwEeWwEeVv0eWwEeYwEhejj6VwEeVwEcAAABfjj6Vv0eVv0Zejj5ejz6Vv0eVwEeVv0Zejj6UvkeWwEeVwEeWwEeXwEdikkNejz4AAACWwEgAAABfjz6XwUVijT2Vv0dejj5ejj+WwEdfjz6WwEeVv0Zfjz9ikEFejz9ejj5ejj6Xv0eVwEZejj6WwEdejz6VwEdgkD+Vv0dgjj9fjj5fjz5hjz5ejj5fjz5fjz5djz9ekD9smT8AAACVv0dejj7///+ItEX9/vqXwUuszXCZwk7v9uP6/Pb4+/Lg7cq61Yeoy2n0+ezs9N3R467D25edxFTo8dbK4KS/2I+204CkyGChx12fxVnl79Da6b/V5rbH3Z2wz3alyWTY57rc314sAAAAl3RSTlMA+/cJ8sd46+QSBPAPB88w5jQbDBjWgO3acJ+/VUhDm5KIOxb9r444UCneYCDo06M/4bosI8OFTCbqs6uLaO6nEvf0z8x9XgiWdWtSJGIdDQW3e1rXxxawMeSRinVtG/r58ryAfFP04UpCOuzBtquXKQ3OyaRlLh4W27umno1oWUwfeG5nYzXFu6+CP183dV8l2JpWlpUqU/DWcAAAE4hJREFUeNrs1s1q20AUhuFzMXMT2giJWXhjkBb6/+sIy8Y2cqWCwQiMTdMmtCaELj+Tm+2MQ6AQSmWnq3Ce1dH2ZY5miDHGGGOMMcYYYx+Ms2y6ff4YEfsP/D4fbCUg6uHgEHuv6D6A5roCUJskmU6IvUN1SgEls6LIJYC2jU9rTno7b+sCVmmOZSJxoYKMf6a3imKBeuuYsqUFQ9UCCHfEbuEUNeyuIk33FOmPGlYXu8C+InaDQwq3i0ibW4BcVi3UocpsuLlH7Gp9CvHZIW016KknKoG95zQK6ZLY9RcSEExJc2IhZK+HNRBG+hPo+El6Lf9owT74ZiptpHMzeYBV6a4Kli7t895fYxoL9bLwzgDkEzJsCHNSJVD2xaa94yfpaH4DyAcyTkAQ0YUE1kSTAnABPC+euOhYOwnkZHgDVEkvWmCZHJsQF8/nxdMXYqPMXdg7Mh5f7yZtA4TSFngNel7MuOg4GyCjiy3Q+GRM7y38wQQ9f5rx1o/xYCNNyOgDWCua7FZFKIA3QXXRr8T+qXGxdcg4WmhXx6w1i/42qCn6c6bdffv+i9jfRPFv7uyzR4koCgPwGYo0aQKidEFw0UVW1sWCvWLv3Wg0GnvsXRN7Lx80nomxu9bY+89TlHvvgRkkMySY+HzZZPeG2bz3PXBnkCdUP4iuDJbHLJ0gCzRQYf36HTfgf2Xzzhk2bFjEawO9ti6Vl62sHN3nnp8qC8pAqdvQmtS8/sz0JLRZ35Q/ZVT9Q3Z8aUDO4nA4grkRhRjos3bD4BML5m5eu2KDzDQP9PIRaIl7KDK5edBWtkjcHNifmKOI1NhVtiBxEHRZsFsevHP1pTH0bbN5oBcPQUtiBmRcMWinzoQVKzz9oVa/6S6sMR90WbdLHjy5ci+kKdB9V6El45HbOBrayJhhWxnsAspYNmGNAaDdpO3nT7AstQW65Sa0JIRcyAlt1IFcgQ69P4114pqruXXTCjbomgNdfwZaIqbLMB3aieTm6ibzHjdgnRnaurlt7YqlrJwaAuVOLoRWOJAxzYA2MgaQCySBcQ4MYh1LEjRYuXMMS1NnoMf3QguyyOW80EYpK3LDxZWTQ7Fez2jQYI3M6A302GlowTzkhvqgjfqS5MJ+YCYiIbl6ely5hA80WNZyoPvuQAsSyIWhrTYiVxQDYyJxBgbF3O7YsC7QovG8f/zW+/WX3gcP/x7ojuvQgh7kEtBWB5EJioJmSJ6LhjhBs+WymofP3n5+cpe59+a+IlDqFOg30oNcBNrKVnZU51pUcH4OuWl9QYcLanE+ePPo3l3ii7Kh1FnQr8siGjEE2qtz0HCHyTE8FBVFXCIm3jAD9NgsK7xncXK9fw/0JOg33oSMtR+0mbG7I9LhJUW0JyRkzCnQY5Nc5/6DFyxO5t6Hvwd6DPQrS8gUbPCvzd8o5mWWHfTYXZ/nu0d3672Q/x7oFtDNWEIu5IN/bcgAZHIdoMuKujx7WZ7EyyaBrl8Ieo3sQY5V4h+a4xAT7wU9Ji2re/9keVK9aoFSp6G5VGxg//LEiZlBsyIjnWr3JcE5UGFMzgqVxuaLUWPDVg85WMyXxpbG9Ykp19j8VTb2i1HFfCGfGZ/VdpLCfD/QY+7U2oI+vav04pkiUI3nJmPn7DAS1nHdTkUlhncB2PsV+dB5lqhFaovlrcjl4kPsDe4nh7orF3bH2QWkQpQu9WfM3GIfgN1s/SWInMP624jxdm+PmZvtAyEWNjPhjuqLbz8qU72P7yp9+dgs0HPwN86uhAfrBIZAhX0WcumR0G+2CwVHUXEQ7LckbcAalkQWiO4cVu0fDTB6uoMuHUR2KFv3kMudQ1WmCLjJDsZTZG9DyE3rZqemVTLxoaag9548/v3jleJOSdO5KTXLhQrmPwPliyOX8I1Om5Ay9TdCjWjaggo9EbIqxhcU5kN0Y236hoGiX0NEGa0zAKAjiKoGJKGTTFdpPtk8coQex9p/bYJMvBV3R89fPvjw8cOz3pcvnn+TmwV6GRrrm5mCSpnqJodFerP7hSWsZYgCNcwjoQrrEjHL8/iW5G3eofXLTUW+cgZpVxIAZtVmT6tnm4hceDRwg5DzuPkxdLAsvP6hMub3379uGujFI43zDKGaIdURtpCBDKNCAQR/XEJ1nhgw0/mactSCCoGYShpjK9MSR3VjbQBFUlgvML7hyKWhauZqmfj+guX5qSbEpoHuOwQN+IqqOz/CCb95kZuiVj/PSGBSEw3YiMvP7nTE9i1yoZKUMSoeNUllO4B/EaoyJGrv5hxJYJaQJ+N8nyYdkIkH/CPprawp0C23oIGuHKopsyHGvwtGgElL2Fgf9jmRFhukun4EG1mLyGwWALhdqCo4EABGWUX6UagyxtVuSQ7vkoneu8w3bYHuOAvqOumYOjwBs2tEziGhtAT+iCsqVBvDlIH8iwkD7U3QYzEh4ao2ORvGJiLV5MV1HB0AEPWgqgEdlbQHIMf+c7oFuS5g9kyViVfiKK8t0PUnQd0ok4hq//isE8DZHRkXXsSOOgGsYSkkMnmrhOTTszrKESsKPYtjWe8wWllDNfio4gXzabMBiVD1H0POUyltMh4KhTaiUAr9Nmh+pYxkmxazgo7nLyvFbcCsXFUbqJb3UOr4zKaP44cn7by33Ub4zS/V1mFJPwBfxKEMdDQ9AITdUOGfiMJEG1TMyyEhFeZ0On3ZBC1zwAkV48hVjaB8AzL0BSKvfAg+siR2bZ4dmO0b1AN9/OW1hkAbf61kRq5sBwUvUpZRv5cYx9JAlQNvZgOWdJGcuqFisUTzrA5CZ4n81jofKkrILQKmSF4PqP7I5ZWz5+ps9DT07c/2zrs5iSAM43uAdAIJXSEaUCAJwURibLFF7L333nXsvfeuY5lxzrH3Fnv/eAqRu2d37/DUm8Fx+P0nk+Pk4d23X4L15pt76oJqDvOQLU8mPBMUU5rJIjupdwfB0t1SWAjLQhkKFzspnzy8aGW1IZHxd07wIAGpouuHngEZAMeD9/6NRCL/GALw5ibw4MU77YJeVRkriTgH4+mO9jnBCBsdEIHppLHDbJQ/pwV2EfLWXdMHA7qUMnr74TEoHA2LwuBlSk6UCBMkDe9KChg98ivgHaYuuIE8eUpVno++aBZ049FfCup3ExY7RoHpcYKC4upDawO8TQ14DEitRxvpAl3sGK/4xQULYXqM7KcNWWiGSgwgiD0o//jPJFR5M2/EZnrMyfSaHj6/pyKo1vaIBxWrZr1oGkxCmEgUqsJg4SSbDcqmg8ljnTcvSROczXrF2OgofEUB+R39PikmWWSrSxIKMPF4QWE5IGZqcOTJrNW+eHiT5uMTjYKqPAs2RASCjWla0mawvCoiEYaUJu/vbRClgtlSgkbAWQRcRGk2bIkyYXuwE8pWbg2QD1f1hbMhGbMwAz/TnmFMe7nLRPHYP9Em6LVtyiM4QQQMfVJeAjQ6wPKUcy173u780D5zEjzyKCgdjUNJoihoUyzva8Hi2o3FGAf3HRdnOvmiRP7MuMZ3QDcKmMdq9eU+q+iHt5oE3a/cHqlfISJCQ3cbZiOy3Fb4fw0G8ynoDt9KSs3CxxupfTqhjxEPCpPGu+GbGKq0xRh2EYqBBqpUqhkuJ/Wu0jPkN1yH+dEdLYKuBUGRiR62ERqTPqkNDl532RN5W+i6xIZJuJsAGOUb8x9MFrgjgB2a/rg/RV9olcKPO4SpAI0vQ7UNJkrXe6oJspgT9M7jTkbQB6+1CLpGJbM3hk1sB62xqGirXNEJYUlmLqXB+ryPj3IZAt20cOLJVnEN3cAT0bO4Zgd2lWmcsvW2E+JNyC6ImyHzY3nWj8KhB0FZ1PbCne1s160hyptEMAVGzaQ01Q1Q+BnR8GYwm5DVIiaHMmOsdOlorIPsv9iCtzdChpBm7aIdS6W45NQdTC64WXGv6dttOnl6o0VQ1b1w5wQHO7Ow/TQJAUdMEtA7z/joHxNmECCdd40wQSJDVRb5ehnobpFziEJMciZAZSNX02HPOypI/2AyQcya8NjTuyOvXmoQdCVRwxXtwYx2uoZlLig8+0Ey1xMKZyeTlo4hQBIMrxDsxmHDVWZKf7h3Xnif7KUdUloOyZmQICwpi+QjbHLSZWJ87VTsNSF3O6mw9FaDoAeIOq0Bv4hUddXTvRRNwjcdQzeTlqYoq8Ga306Is4dy5wB7nT3ywg+E7CIqDVQ9ckQbSgDGDVelfX506cikQTfUFH2qZS6PbCcl8CaLDR9oecS7ySYRUM7Wo0ye31JLgMGM0lm4EoKvqx3uHbDTtW1GyuBjcBAgJnEB1O+OmKRKhdCsh7ye5t4rEPTpXQ2Cri29aO9LCCJzlFuD8sGbqWhPnmQpQVs7mKn0RDnImdLgav0iY5AJsFhohmKLj8Urud2WAX2li+OE5hTVDVXtkzzUIujG3aQ03QVwmVPonpglppgLdRvInOymiWB4baJMXaFOkKNfDj6qWYR03wdL03TaMw5TKx5p2OhJ5IpmECYMi+h18K/8AFS7hW68TEpT3cCkJePBJJyKudDwOPRKWOfmzsEZzStv7ylgK7RIEu5rLVxvVKx4uylGNH7gb8oUT0bORhgW9qaO+UdYEXkCNeh9LT50zUrNe3b8JLw/LNvAYCPhYp4FFepcUmY4wwDKM2FaTEgn2UudjHShjhRlJB/ipOoznloLN0YcTRjGbqYD0f2b9188udOVOX3+3Si/9MCvBO0GB80LT9DRJuHOsCmNG3xgrl5quwShVMgy0UyYKe+tGNhFFDR5wSYZsgVUVn0IBwlxBjpnE9e7e9D56s3XO3e+vsI+3nPVPBTZzzXwshOw0E056CjvszJJDhaAWGbXdMcZk1cab8LbuTCaYS3uGm3Bjiy3NNIiu26T7ECmqD2tghjMLm6VcTaVzn8qZPO373/4+PED6nn79R0tgm5nq3n7+GAuMMBZNNAVIr2dFbPQizl8sM1ku95ngoAbeXlFjakcVl6tXVcaYN+jq1DwtaGeLQMg3WIHdOBBcj7Cgx4KH75DTs9Sbdcj9zEmqQu6lh0r2fqLohAM9TVHmmORnri4NSSeD94d8uGpgTIRhHLymaloqEoEeu0I4q7Yz0XdOvypUPfJqcmJDOZqTaOdBPJf2g0acVLsVHniD3G0uwjLukGMC1XkNs5BeEHVx0rVBlEZS21BOQEXsiR6YKjpwl7HvxNGftzCUcfQ9jNQVcveQojA0woSjXb1Z1LxWHAcGVmiE4pJkyZB17BjpRmiCn2MdENMTHgVp1CjIeNSxVDs8MZzYkmmFxWINInYzQSVYSLN4zIx21Q8hykXirUR8lxTg5lvj7jwoCIhNwR91iRqRJkoTJnU9awpXmkQS9GtXnozg8JjxlGLCLvkiuREIGgjHCMW0tOPD7dv8jzAA19a0Ot08Rl1KCvQv5md2TqScBVEdNmzOqEIomgIu+BKdYJdUQqWIZjR0VCmjFNeIsBdKp4lB5lq8/ltXs9P7zQN6fjfjuNqg4CAxV/SzmZR/iz4CTqlwXSSR8hFsPekjiUgGxQuQ0hzAhyz4NgLMYvUIjPPpLNsR+R1JyPpw8/vb2gW9MwVStChVqXjHmgtjkaYuol/Wn0IAeJjgvyuZ3vWjgtdahgG4/Mk2Qw+kPCTNIz32lxEEdDcM9ROeObN4qR6+ebj04c/Rb394Omnu+qLDjxrdxPEVR/YYQG7Ejo8O8Kt3qJJ1JkkIE1xTpdfHs1E2bqQCXr3lqrEQKOKy87I9xUMnsEpG378ZJN0h0xMUjknvWgdQxSpb5Bvjs+CENy847nz9fHzR52dzzo7P77WsNuErOHGSt5Yot8Of4vVY20IVfVra7bDGY6YJWKQu06WX84ShnS4T1XI2mTxtPhz3Sf6mM/rgTqheVw3v9Xi8FhDPdrzd0WmmCUabZIfCEsvTmDzIT5R9SiHrXU31Lj3/t3Le9rWGRHFvfC4u3ZAKhqrrnGRv8eVjqUikVSt21ZiRiEavPnbRiNjUtFqL9EH1y4Tt6/HZ02a0C7oSlJGZnTAbqf+1Lawj0jwLNRb0AOkjMCMpS/RG3uzlSt1eTbrLegZUj5s3bBprDNG3PB3BJxEmdl6C7qRlA9smg4g+hI3h0R+4s0zTBdBkW2kbESgGZokupKd3oG9GDdRY/3iBWdHzRq0XD9BL5FygQV6Vb2ersQdMIiAw0xKsuT06sULNs+e31sXQU+ScmHshd1W/eSc2Dck0D1AG/klY+dMmrf+8IJNoOofCnqIlAtfH2q0pxcxpmMmDNb8ZU2bOmfJvCMLN80aNGwkLaz2SmnjNVIuBspB3hTWNXdAhOlp8ttMWn9q4c6zswaN/C1Bl649c+D40b2kXCShnRkh+hGgzvvwNPkzpi05ve7I+c2zB/XWJOia7YdOXrgylpSRCMwm3EQ/mlHPIX8X7abu2bph3aqDs4eVFnTN/uNHd++dRsrLeKoZqB/eICSgWaIL0+atXrVz9qh9w5ahoIVjvv3cid3/xp8EoXY79QKHsILVTPRk6qT1FxdtmTtqWO+fgq7df+jEhWPTyD9CCw5B9SQidB33vrV2ojsjJm34kbXOHXXr6oETR3f/U3//p62uSEBHFyo97FgFTVK9GTtizp69/5SYeYwyRFe8bYam/pPT5f8lcv8NSXPMSCpUqFChQoUKFSpUqFChQoUK/yHfAYTLavSW/S0uAAAAAElFTkSuQmCC)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous React ](/docs/cesdk/ui/solutions/design-editor/react/)[ Next Vue.js ](/docs/cesdk/ui/solutions/design-editor/vuejs/) The Basics of Customizing the Web Editor - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/customization/basics/ CESDK/CE.SDK/Web Editor/Customization # The Basics of Customizing the Web Editor Learn core principles and concepts behind the web editor's extension points Let's start with the basics. ## Locations To understand the extension points of the web editor, it is helpful to know about the different areas or locations of the editor where you can customize the behavior or appearance. Let's take a look at these locations. ![Locations of the web editor](/docs/cesdk/1585aa3b8f3b22712cbbbfed86ca1046/Editor_locations.png) ### Canvas The canvas is the main area of the editor where the user interacts with design content. This part is completely controlled by the Creative Engine. It is basically the core of CE.SDK and the web editor is build around it. This is also the part you need to interact with if you want to create a complete custom UI and not use the web editor. [Here](/docs/cesdk/engine/) is more information about how to control this area via API. ### Dock The dock is the main entry point for user interactions not directly related to the currently selected block. It occupies a prominent place in the editor and is primarily, though not exclusively, used to open panels with asset libraries. Therefore, the default and recommended position of the asset library panel is on the side of the dock. However, depending on the use case, it might be beneficial to add other buttons and functionalities (even block-specific ones) to highlight them due to the prominence of the dock. [Here](/docs/cesdk/ui/customization/api/dock/) is more information about APIs specific to this location. ### Canvas Menu For every selected block on the canvas, the canvas menu appears either above or below the frame of the selected block. This menu provides the most important block-specific actions and settings, ensuring they are immediately visible to the user. It is recommended to add as few elements as possible to the canvas menu to keep it clean and focused. [Here](/docs/cesdk/ui/customization/api/canvasMenu/) is more information about APIs specific to this location. ### Inspector Bar The main location for block-specific functionality is the inspector bar. Any action or setting available to the user for the currently selected block that does not appear in the canvas menu should be added here. [Here](/docs/cesdk/ui/customization/api/inspectorBar/) is more information about APIs specific to this location. ### Navigation Bar Actions that affect browser navigation (e.g. going back or closing the editor), have global effects on the scene (e.g. undo/redo and zoom), or process the scene in some way (e.g. saving and exporting) should be placed in the navigation bar. [Here](/docs/cesdk/ui/customization/api/navigationBar/) is more information about APIs specific to this location. ### Canvas Bar The canvas bar is intended for actions that affect the canvas or scene as a whole. Undo/redo or zoom controls can be placed here as an alternative to the navigation bar. [Here](/docs/cesdk/ui/customization/api/canvasBar/) is more information about APIs specific to this location. ## Controlling the Order of a Location ![Orders of the web editor](/docs/cesdk/07c89c2fc09823d76e046c12a3523765/Edtior_order.png) The key APIs to customize the different locations of the web editor are the specific Order APIs. These APIs allow you to control what is displayed in the different locations and in what order or layout. Each location has its own Order API that you can use to add, remove, or reorder components, e.g., `setDockOrder`, `setCanvasMenuOrder`, `setInspectorBarOrder`, `setNavigationBarOrder`, and `setCanvasBarOrder`. At its core, these methods take an array of IDs that represent components. The CE.SDK web editor provides a set of predefined components for various purposes. For instance, `ly.img.delete.canvasMenu` represents a button in the canvas menu that is used to delete the currently selected block. In the default order of the canvas menu, this ID is already included at the very end. If, for some use-case-related reason, you decide that this button should be placed at the beginning of the canvas menu, you need to push this ID to the beginning of the order and set it with `setCanvasMenuOrder`. If you decide to remove it completely from the canvas menu and allow the user to delete blocks only via the inspector bar, you can simply omit this ID from the order you set with `setCanvasMenuOrder`. Please refer to the corresponding API reference for a list of available components for each location. ### Layout Components In addition to functional components, the CE.SDK web editor provides a few special components that can be used to control the layout of different locations. For instance, `ly.img.separator` is a special component that can be used to separate different groups of components. If you want to separate the default components of the canvas menu from your custom components, you can add this separator to the order you set with `setCanvasMenuOrder`. Another layout component is `ly.img.spacer`. The spacer is a simple component that can be used to add space between components. All spacers will take up the remaining space of the location and push the following components to the right side of the location. If a single spacer is placed at the beginning of an order, all other components will be aligned to the right side. If a spacer is placed at both the beginning and the end of an order, all components will be centered. ## Registration of new Components Besides the predefined components, you can also register your own components. We provide a set of builder components such as buttons, dropdowns, and inputs that you can use to create custom components with entirely unique behavior and logic. Once registered with `registerComponent`, they can be used in various locations of the web editor via the Order APIs. This allows you to deeply integrate your logic into the web editor and make it a part of the user experience. Additionally, if you are not satisfied with the behavior or appearance of our default components, you can override them with your own implementations. ``` cesdk.ui.registerComponent( 'myCustomComponent', ({ builder: { Button }, engine }) => { const selectedBlockIds = engine.block.findAllSelected(); if (selectedBlockIds.length !== 0) { return; } const [selectedBlockId] = selectedBlockIds; const kind = engine.block.getKind(selectedBlockId); if (kind === 'jumper') { Button('jumper-button', { label: 'Jump!', onClick: () => { engine.block.setPositionX( selectedBlockId, engine.block.getPositionX(selectedBlockId) + 100 ); } }); } } ); ``` ## Enable or Disable Features Sometimes it is sufficient to completely remove a component from a location by removing it from the order. However, in some cases, you might want to keep the component only for certain blocks but keep it for other. You could do this by overriding the component with a custom implementation that checks the current block and decides whether to show the component or not but that is rather cumbersome. To make this easier, we provide the [Feature API](/docs/cesdk/ui/customization/api/features/) that allows you to control components on a feature level for the current context. You can write your own or hook into existing features from the web editor or plugins. Let's say you introduced a new kind of block that represents an Avatar. Regardless of scopes or the current mode of the editor, you want to not show the duplicate & delete button in the canvas menu for this kind of block. You can achieve this calling `enable('ly.img.delete.canvasMenu', (context) = {[...]})` and return `false` for the Avatar block. ## Putting It All Together The web editor provides a set of APIs that can be grouped into three categories: Order APIs, Component APIs, and Feature APIs. This separation allows for multiple use cases of customization. When used together, these APIs can be employed to make small adjustments to the default behavior of the editor or installed plugins, extend and deeply integrate custom logic, or completely redefine the default behavior. [ Previous Overview ](/docs/cesdk/ui/customization/)[ Next Overview ](/docs/cesdk/ui/customization/plugins/) Interacting with Panels - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/customization/api/panel/?platform=web&language=js # Interacting with Panels Panels are a basic concept of our UI, e.g. extensively used for asset libraries and inspectors. Given a CreativeEditorSDK instance, we can query if a given panel is open as well as access methods to open/close them. * `cesdk.ui.isPanelOpen(panelId: string, options?: { position?: PanelPosition, floating?: boolean }): boolean` returns `true` if a panel with the given panel id is open. If options are provided they will be matched against the panel's options. E.g. `isPanelOpen('//ly.img.panel/assetLibrary', { floating: false })` will only return true if the asset library is open as a floating panel. * `cesdk.ui.openPanel(panelId: string, options?: { position?: PanelPosition, floating?: boolean, closableByUser?: boolean, payload?: PanelPayload })` opens a panel with the given id if and only if it exists, is not open and is currently registered (known to the UI). Otherwise the method does nothing and is a noop. Options can be provided additionally that will override the default position and floating of the given panel. (Please note, that the default position and floating behavior are not changed for this panel.) In addition, the `closableByUser` option can control if the panel can be closed by the user. Finally, the `payload` option is available specifically when opening panels with panel id `'//ly.img.panel/assetLibrary'`. The expected value is of type `{title?: string | string[], entries?: string[]}` and determines the panel title and content (asset library entries) of the opening asset library panel. * `cesdk.ui.closePanel(panelId: string)` closes a panel with the given id if and only if it exists and is open. Otherwise the method does nothing and is a noop. ### Panel Position Every panel in the UI is typically displayed either on the left or right side of the canvas. An exception exists for rendering in [smaller viewports](/docs/cesdk/ui/guides/smallviewport/), where all panels are situated at the bottom. The setPanelPosition API is used to determine a panel's default placement. * `cesdk.ui.setPanelPosition(panelId: string, panelPosition: PanelPosition)` assigns a specific position to a panel identified by `panelId`. This function will also alter the panel's position while it is open, provided the panel wasn't initially opened with a pre-defined position option (see above). ### Floating Panel Panels can either float over the canvas, potentially obscuring content, or be positioned adjacent to the canvas. The setPanelFloating API allows you to modify this behavior to suit different use cases. * `cesdk.ui.setPanelFloating(panelId: string, floating: boolean)` when set to true, the specified panel will float above the canvas. Conversely, setting it to false places the panel beside the canvas. ### Available Panels By default, CE.SDK provides several different panels for user interaction. Additional panels can be added by the user or through plugins. The findAllPanels function helps retrieve the current panels and filter them based on their state. * `cesdk.ui.findAllPanels(options?: { open?: boolean, position?: PanelPosition, floating?: boolean }): string[]` returns all panel ids. If options are provided they will be matched against the panel's options. E.g. `findAllPanels({ open: true })` will only return panel ids of currently open panels. Some important panels that are registered by default are: Panel Id Description Panel Id `//ly.img.panel/inspector` Description The inspector panel for the currently selected block. Please note, that if no block is selected at all, this will open an empty panel with no content. In the advanced view, inspector panels are always open and cannot be opened/closed. Panel Id `//ly.img.panel/assetLibrary` Description The panel for the asset library, Please note, that currently it is ot possible to open a new asset library with library entries defined. Thus, opening an asset library would show an empty library. More useful is `closePanel` to close an already open asset library. Panel Id `//ly.img.panel/assetLibrary.replace` Description The panel containing the replace library for the currently selected block. Please note, that if no block is selected or the selected block does not have asset replacement configured, the opened library will be empty. See [Custom Asset Library](/docs/cesdk/ui/guides/customize-asset-library/) for more information. Panel Id `//ly.img.panel/settings` Description The settings panel to customize the editor during runtime. Integrate Creative Editor - CE.SDK | IMG.LY Docs [web/nextjs/javascript] https://img.ly/docs/cesdk/ui/quickstart/?platform=web&language=javascript&framework=nextjs # Integrate Creative Editor In this example, we will show you how to integrate [CreativeEditor SDK](https://img.ly/products/creative-sdk) with Next.js. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-next-js?title=IMG.LY%27s+CE.SDK%3A+Integrate+With+Next+Js&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-next-js). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-next-js?title=IMG.LY%27s+CE.SDK%3A+Integrate+With+Next+Js&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/integrate-with-next-js) ## Prerequisites * [Get the latest stable version of **Node.js & NPM**](https://www.npmjs.com/get-npm) * [Setup Next](https://nextjs.org/docs/getting-started) ## Setup Note that, for convenience we serve all SDK assets (e.g. images, stickers, fonts etc.) from our CDN by default. For use in a production environment we recommend [serving assets from your own servers](/docs/cesdk/ui/guides/assets-served-from-your-own-servers/). ### 1\. Add CE.SDK to your Project Install the `@cesdk/cesdk-js` dependency via `npm install --save @cesdk/cesdk-js`. The SDK is served entirely via CDN, so we just need to import the CE.SDK module and the stylesheet containing the default theme settings. ``` import CreativeEditorSDK from '@cesdk/cesdk-js'; ``` ### 2\. Create an empty Container We need to add an empty `
` as a container for the SDK. We configure the `
` element to fully fill the browser window. ### 3\. Create a component containing the SDK We'll now create a `CreativeEditorSDK` component, that's responsible for initializing the SDK and attaching it to the previously created container. To allow configuration of the SDK from outside the component, we pass a `config` object from the component's `props`. ``` const Component = (props = {}) => { const cesdk_container = useRef(null); useEffect(() => { if (cesdk_container.current) { props.config.license = process.env.NEXT_PUBLIC_LICENSE; // Serve assets from IMG.LY CDN or locally props.config.baseURL = 'https://cdn.img.ly/packages/imgly/cesdk-js/1.43.0/assets'; // Enable local uploads in Asset Library props.config.callbacks = { onUpload: 'local' }; CreativeEditorSDK.create(cesdk_container.current, props.config).then( async (instance) => { // Do something with the instance of CreativeEditor SDK, for example: // Populate the asset library with default / demo asset sources. instance.addDefaultAssetSources(); instance.addDemoAssetSources({ sceneMode: 'Design' }); await instance.createDesignScene(); } ); } }, [props]); return ( ); }; ``` ### 4\. Managing the container reference To let CE.SDK use the container created by our component, we use `useRef` to acquire a reference to the element and pass it as the first parameter to `CreativeEditorSDK.create`. ``` const cesdk_container = useRef(null); ``` ### 5\. Dealing with server side rendering (SSR) The CreativeEditor SDK is a Client-Side library that requires various browser features. Therefore, it must be loaded and executed in the browser. The easiest way is to introduce an additional component (`CreativeEditorSDKWithNoSSR`), that loads the library dynamically when the page is loaded in the browser: ``` import dynamic from 'next/dynamic'; const CreativeEditorSDKWithNoSSR = dynamic( () => import('./CreativeEditorSDK'), { ssr: false } ); export default CreativeEditorSDKWithNoSSR; ``` ### 6\. Serving Webpage locally In order to use the editor we need to run our project via `npx` which comes bundled with **npm**. `npx next dev` will spin up a development server at [http://localhost:3000](http://localhost:3000) ### Congratulations! You've got CE.SDK up and running. Get to know the SDK and dive into the next steps, when you're ready: * [Perform Basic Configuration](/docs/cesdk/ui/configuration/basics/) * [Serve assets from your own servers](/docs/cesdk/ui/guides/assets-served-from-your-own-servers/) * [Create and use a license key](/docs/cesdk/engine/quickstart/#licensing) * [Configure Callbacks](/docs/cesdk/ui/configuration/callbacks/) * [Add Localization](/docs/cesdk/ui/guides/i18n/) * [Adapt the User Interface](/docs/cesdk/ui/guides/theming/) History - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/editor-history/?platform=web&language=javascript # History In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to undo and redo steps in the `editor` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` createHistory(): HistoryId ``` Create a history which consists of an undo/redo stack for editing operations. There can be multiple. But only one can be active at a time. * Returns The handle of the created history. ``` const newHistory = engine.editor.createHistory(); ``` ``` destroyHistory(history: HistoryId): void ``` Destroy the given history, throws an error if the handle doesn't refer to a history. * `history`: The history to destroy. ``` engine.editor.destroyHistory(oldHistory); ``` ``` setActiveHistory(history: HistoryId): void ``` Mark the given history as active, throws an error if the handle doesn't refer to a history. All other histories get cleared from the active state. Undo/redo operations only apply to the active history. * `history`: The history to make active. ``` engine.editor.setActiveHistory(newHistory); ``` ``` getActiveHistory(): HistoryId ``` Get the handle to the currently active history. If there's none it will be created. * Returns History - The handle of the active history. ``` const oldHistory = engine.editor.getActiveHistory(); ``` ``` addUndoStep(): void ``` Adds a new history state to the stack, if undoable changes were made. ``` engine.editor.addUndoStep(); ``` ``` undo(): void ``` Undo one step in the history if an undo step is available. ``` engine.editor.undo(); ``` ``` canUndo(): boolean ``` If an undo step is available. * Returns True if an undo step is available. ``` if (engine.editor.canUndo()) { ``` ``` redo(): void ``` Redo one step in the history if a redo step is available. ``` engine.editor.redo(); ``` ``` canRedo(): boolean ``` If a redo step is available. * Returns True if a redo step is available. ``` if (engine.editor.canRedo()) { ``` ``` onHistoryUpdated: (callback: () => void) => (() => void) ``` Subscribe to changes to the undo/redo history. * `callback`: This function is called at the end of the engine update, if the undo/redo history has been changed. * Returns A method to unsubscribe ``` const unsubscribe = engine.editor.onHistoryUpdated(() => { const canUndo = engine.editor.canUndo(); const canRedo = engine.editor.canRedo(); console.log("History updated", {canUndo, canRedo}); }) ``` Fonts & Typefaces - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/guides/fonts-typefaces/?platform=web&language=js # Fonts & Typefaces The CreativeEditor SDK allows adding text elements to the scene. The available fonts can be controlled through the `config.ui.typefaceLibraries` object and by creating custom asset sources. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/guides-fonts-typefaces?title=IMG.LY%27s+CE.SDK%3A+Guides+Fonts+Typefaces&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/guides-fonts-typefaces). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/guides-fonts-typefaces?title=IMG.LY%27s+CE.SDK%3A+Guides+Fonts+Typefaces&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/guides-fonts-typefaces) Added typefaces are directly accessible from the typeface dropdown in the text inspector: ![](/docs/cesdk/4454132c9e1f81285956a18783d26682/typefaces-light.png) ## Configuring Custom Typefaces You can define your own typefaces by [creating a custom asset source](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) and by registering it in the configuration under `ui.typefaceLibraries`. Each of the asset objects in the asset source must define a value for its `payload.typeface` property. A typeface consists of the following properties: ``` // Add a custom typeface asset source. instance.engine.asset.addLocalSource('my-custom-typefaces'); instance.engine.asset.addAssetToSource('my-custom-typefaces', { id: 'orbitron', label: { en: 'Orbitron' }, payload: { typeface: { name: 'Orbitron', fonts: [ { uri: `${window.location.protocol}//${window.location.host}/Orbitron-Regular.ttf`, subFamily: 'Regular', weight: 'regular', style: 'normal' }, { uri: `${window.location.protocol}//${window.location.host}/Orbitron-Bold.ttf`, subFamily: 'Bold', weight: 'bold', style: 'normal' } ] } } }); ``` * `name: string` the name of the typeface, which is also shown in the dropdown. ``` name: 'Orbitron', ``` * `fonts[]: Object` an array of objects, where each object describes a single font of the typeface. Each font has the following properties: * `uri: string` points at the font file for this font style. Supports `http(s)://` and relative paths which resolve to the `baseURL`. TrueType, OpenType, and WOFF (and WOFF2) formats are supported. * `subFamily: string` of the font. This is an arbitrary string as defined by the font file, e.g. 'Regular', 'ExtraBold Italic', 'Book', 'Oblique', etc. It is shown in the style dropdown in the inspector which lets users choose between all fonts of the selected typeface. * `weight: string` of the font. One of `thin`, `extraLight`, `light`, `normal`, `medium`, `semiBold`, `bold`, `extraBold`, `heavy`. Defaults to `normal`. * `style: string` of the font. One of `normal` & `italic`. Defaults to `normal`. ``` fonts: [ { uri: `${window.location.protocol}//${window.location.host}/Orbitron-Regular.ttf`, subFamily: 'Regular', weight: 'regular', style: 'normal' }, { uri: `${window.location.protocol}//${window.location.host}/Orbitron-Bold.ttf`, subFamily: 'Bold', weight: 'bold', style: 'normal' } ] ``` ## Removing the Default Typefaces In case you want to remove all of the default typefaces we ship with the CE.SDK editor, you need to remove the id of our default asset source `ly.img.typeface` from the `ui.typefaceLibraries` array in the editor configuration. ``` // 'ly.img.typeface', 'my-custom-typefaces' ] }, ``` Lifecycle - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-lifecycle/?platform=web&language=javascript # Lifecycle In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify scenes through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. Only blocks that are direct or indirect children of a `page` block are rendered. Scenes without any `page` child may not be properly displayed by the CE.SDK editor. ## Functions ``` create(type: DesignBlockType): DesignBlockId ``` Create a new block, fails if type is unknown. * `type`: The type of the block that shall be created. * Returns The created blocks handle. ``` const block = engine.block.create('graphic'); ``` By default, the following blocks are available: * Page: `//ly.img.ubq/page` or `page` * Graphic: `//ly.img.ubq/graphic` or `graphic` * Text: `//ly.img.ubq/text` or `text` * Audio: `//ly.img.ubq/audio` or `audio` * Cutout: `//ly.img.ubq/cutout` or `cutout` To create a scene, use [`scene.create`](/docs/cesdk/engine/guides/create-scene/) instead. ``` saveToString(blocks: DesignBlockId[], allowedResourceSchemes?: string[]): Promise ``` Saves the given blocks into a string. If given the root of a block hierarchy, e.g. a page with multiple children, the entire hierarchy is saved. * `blocks`: The blocks to save * Returns A promise that resolves to a string representing the blocks or an error. ``` const savedBlocksString = await engine.block.saveToString([block]); ``` ``` saveToArchive(blocks: DesignBlockId[]): Promise ``` Saves the given blocks and all of their referenced assets into an archive. The archive contains all assets that were accessible when this function was called. Blocks in the archived scene reference assets relative from to the location of the scene file. These references are resolved when loading such a scene via `loadSceneFromURL`. * `blocks`: The blocks to save * Returns A promise that resolves with a Blob on success or an error on failure. ``` const savedBlocksArchive = await engine.block.saveToArchive([block]); ``` ``` loadFromString(content: string): Promise ``` Loads existing blocks from the given string. The blocks are not attached by default and won't be visible until attached to a page or the scene. The UUID of the loaded blocks is replaced with a new one. * `content`: A string representing the given blocks. * Returns A promise that resolves with a list of handles representing the found blocks or an error. ``` const loadedBlocksString = await engine.block.loadFromString(savedBlocks); ``` ``` loadFromArchiveURL(url: string): Promise ``` Loads existing blocks from the given URL. The blocks are not attached by default and won't be visible until attached to a page or the scene. The UUID of the loaded blocks is replaced with a new one. * `url`: The URL to load the blocks from. * Returns A promise that resolves with a list of handles representing the found blocks or an error. ``` const loadedBlocksArchive = await engine.block.loadFromArchiveURL('https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1_blocks.zip'); ``` ``` getType(id: DesignBlockId): ObjectTypeLonghand ``` Get the type of the given block, fails if the block is invalid. * `id`: The block to query. * Returns The blocks type. ``` const blockType = engine.block.getType(block); ``` ``` setName(id: DesignBlockId, name: string): void ``` Update a block's name. * `id`: The block to update. * `name`: The name to set. ``` engine.block.setName(block, 'someName'); ``` ``` getName(id: DesignBlockId): string ``` Get a block's name. * `id`: The block to query. ``` const name = engine.block.getName(block); ``` ``` getUUID(id: DesignBlockId): string ``` Get a block's UUID. * `id`: The block to query. ``` const uuid = engine.block.getUUID(block); ``` ``` duplicate(id: DesignBlockId): DesignBlockId ``` Duplicates a block including its children. Required scope: 'lifecycle/duplicate' * `id`: The block to duplicate. * Returns The handle of the duplicate. ``` const duplicate = engine.block.duplicate(block); ``` ``` destroy(id: DesignBlockId): void ``` Destroys a block. Required scope: 'lifecycle/destroy' * `id`: The block to destroy. ``` engine.block.destroy(duplicate); ``` ``` isValid(id: DesignBlockId): boolean ``` Check if a block is valid. A block becomes invalid once it has been destroyed. * `id`: The block to query. * Returns True, if the block is valid. ``` engine.block.isValid(duplicate); // false ``` Text - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-text/?platform=web&language=javascript # Text In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to edit ranges within text blocks. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` replaceText(id: DesignBlockId, text: string, from?: number, to?: number): void ``` Inserts the given text into the selected range of the text block. Required scope: 'text/edit' * `block`: The text block into which to insert the given text. * `text`: The text which should replace the selected range in the block. * `from`: The start index of the UTF-16 range that should be replaced. If the value is negative, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme that should be replaced by the inserted text. If `from` and `to` are negative, a this will fall back to the end of the entire text range, so the entire text will be replaced. If `to` is negative but `from` is greater than or equal to 0, the text will be inserted at the index defined by `from`. ``` engine.block.replaceText(text, 'Hello World'); ``` ``` removeText(id: DesignBlockId, from?: number, to?: number): void ``` Removes selected range of text of the given text block. Required scope: 'text/edit' * `block`: The text block from which the selected text should be removed. * `from`: The start index of the UTF-16 range that should be removed. If the value is negative, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme that should be removed. If the value is negative, this will fall back to the end of the entire text range. ``` engine.block.removeText(text, 0, 6); ``` ``` setTextColor(id: DesignBlockId, color: Color, from?: number, to?: number): void ``` Changes the color of the text in the selected range to the given color. Required scope: 'text/edit' * `block`: The text block whose color should be changed. * `color`: The new color of the selected text range. * `from`: The start index of the UTF-16 range whose color should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose color should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextColor(text, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, 1, 4); ``` ``` getTextColors(id: DesignBlockId, from?: number, to?: number): Array ``` Returns the ordered unique list of colors of the text in the selected range. * `block`: The text block whose colors should be returned. * `from`: The start index of the UTF-16 range whose colors should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose colors should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` const colorsInRange = engine.block.getTextColors(text, 2, 5); ``` ``` setTextFontWeight(id: DesignBlockId, fontWeight: FontWeight, from?: number, to?: number): void ``` Changes the weight of the text in the selected range to the given weight. Required scope: 'text/edit' * `block`: The text block whose weight should be changed. * `fontWeight`: The new weight of the selected text range. * `from`: The start index of the UTF-16 range whose weight should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose weight should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextFontWeight(text, 'bold', 0, 5); ``` ``` getTextFontWeights(id: DesignBlockId, from?: number, to?: number): FontWeight[] ``` Returns the ordered unique list of font weights of the text in the selected range. * `block`: The text block whose font weights should be returned. * `from`: The start index of the UTF-16 range whose font weights should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose font weights should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` const fontWeights = engine.block.getTextFontWeights(text); ``` ``` setTextFontSize(id: DesignBlockId, fontSize: number, from?: number, to?: number): void ``` Sets the given font size for the text block. If the font size is applied to the entire text block, its font size property will be updated. Required scope: 'text/character' * `block`: The text block whose font size should be changed. * `fontSize`: The new font size. * `from`: The start index of the UTF-16 range whose font size should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose font size should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextFontSize(text, 12, 0, 5); ``` ``` getTextFontSizes(id: DesignBlockId, from?: number, to?: number): number[] ``` Returns the ordered unique list of font sizes of the text in the selected range. * `block`: The text block whose font sizes should be returned. * `from`: The start index of the grapheme range whose font sizes should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose font sizes should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` const fontSizes = engine.block.getTextFontSizes(text); ``` ``` setTextFontStyle(id: DesignBlockId, fontStyle: FontStyle, from?: number, to?: number): void ``` Changes the style of the text in the selected range to the given style. Required scope: 'text/edit' * `block`: The text block whose style should be changed. * `fontStyle`: The new style of the selected text range. * `from`: The start index of the UTF-16 range whose style should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose style should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextFontStyle(text, 'italic', 0, 5); ``` ``` getTextFontStyles(id: DesignBlockId, from?: number, to?: number): FontStyle[] ``` Returns the ordered unique list of font styles of the text in the selected range. * `block`: The text block whose font styles should be returned. * `from`: The start index of the UTF-16 range whose font styles should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose font styles should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` const fontStyles = engine.block.getTextFontStyles(text); ``` ``` setTextCase(id: DesignBlockId, textCase: TextCase, from?: number, to?: number): void ``` Sets the given text case for the selected range of text. Required scope: 'text/character' * `id`: The text block whose text case should be changed. * `textCase`: The new text case value. * `from`: The start index of the UTF-16 range whose text cases should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose text cases should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTextCase(text, 'Titlecase'); ``` ``` getTextCases(id: DesignBlockId, from?: number, to?: number): TextCase[] ``` Returns the ordered list of text cases of the text in the selected range. * `block`: The text block whose text cases should be returned. * `from`: The start index of the UTF-16 range whose text cases should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose text cases should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` const textCases = engine.block.getTextCases(text); ``` ``` canToggleBoldFont(id: DesignBlockId, from?: number, to?: number): boolean ``` Returns whether the font weight of the given text block can be toggled between bold and normal. If any part of the selected range is not already bold and the necessary bold font is available, then this function returns true. * `id`: The text block whose font weight should be toggled. * `from`: The start index of the UTF-16 range whose font weight should be toggled. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose font weight should be toggled. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns Whether the font weight of the given block can be toggled between bold and normal. ``` const canToggleBold = engine.block.canToggleBoldFont(text); ``` ``` canToggleItalicFont(id: DesignBlockId, from?: number, to?: number): boolean ``` Toggles the bold font style of the given text block. If any part of the selected range is not already italic and the necessary italic font is available, then this function returns true. * `id`: The text block whose font style should be toggled. * `from`: The start index of the UTF-16 range whose font style should be toggled. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose font style should be toggled. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns Whether the font style of the given block can be toggled between italic and normal. ``` const canToggleItalic = engine.block.canToggleItalicFont(text); ``` ``` toggleBoldFont(id: DesignBlockId, from?: number, to?: number): void ``` Toggles the font weight of the given text block between bold and normal. If any part of the selected range is not already bold, all of the selected range will become bold. Only if the entire range is already bold will this function toggle it all back to normal. Required scope: 'text/character' * `id`: The text block whose font weight should be toggled. * `from`: The start index of the UTF-16 range whose font weight should be toggled. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose font weight should be toggled. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.toggleBoldFont(text); ``` ``` toggleItalicFont(id: DesignBlockId, from?: number, to?: number): void ``` Toggles the font style of the given text block between italic and normal. If any part of the selected range is not already italic, all of the selected range will become italic. Only if the entire range is already italic will this function toggle it all back to normal. Required scope: 'text/character' * `id`: The text block whose font style should be toggled. * `from`: The start index of the UTF-16 range whose font style should be toggled. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose font style should be toggled. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.toggleItalicFont(text); ``` ``` setFont(id: DesignBlockId, fontFileUri: string, typeface: Typeface): void ``` Sets the given font and typeface for the text block. Existing formatting is reset. Required scope: 'text/character' * `id`: The text block whose font should be changed. * `fontFileUri`: The URI of the new font file. * `typeface`: The typeface of the new font. ``` const typefaceAssetResults = await engine.asset.findAssets('ly.img.typeface', { query: 'Open Sans', page: 0, perPage: 100 }); const typeface = typefaceAssetResults.assets[0].payload.typeface; const font = typeface.fonts.find((font) => font.subFamily === 'Bold'); engine.block.setFont(text, font.uri, typeface); ``` ``` setTypeface(id: DesignBlockId, typeface: Typeface, from?: number, to?: number): void ``` Sets the given typeface for the text block. The current formatting, e.g., bold or italic, is retained as far as possible. Some formatting might change if the new typeface does not support it, e.g. thin might change to light, bold to normal, and/or italic to non-italic. If the typeface is applied to the entire text block, its typeface property will be updated. If a run does not support the new typeface, it will fall back to the default typeface from the typeface property. Required scope: 'text/character' * `id`: The text block whose font should be changed. * `typeface`: The new typeface. * `from`: The start index of the UTF-16 range whose typeface should be changed. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose typeface should be changed. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. ``` engine.block.setTypeface(text, typeface, 2, 5); engine.block.setTypeface(text, typeface); ``` ``` getTypeface(id: DesignBlockId): Typeface ``` Returns the current typeface of the given text block. * `id`: The text block whose typeface should be queried. * Returns the typeface property of the text block. Does not return the typefaces of the text runs. * Throws An error if the block does not have a valid typeface. ``` const defaultTypeface = engine.block.getTypeface(text); ``` ``` getTypefaces(id: DesignBlockId, from?: number, to?: number): Typeface[] ``` Returns the current typefaces of the given text block. * `id`: The text block whose typefaces should be queried. * `from`: The start index of the grapheme range whose typefaces should be returned. If the value is negative and the block is currently being edited, this will fall back to the start of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the start of the entire text range. * `to`: The UTF-16 index after the last grapheme whose typefaces should be returned. If the value is negative and the block is currently being edited, this will fall back to the end of the current cursor index / selected grapheme range. If the value is negative and the block is not being edited, this will fall back to the end of the entire text range. * Returns The typefaces of the text block. * Throws An error if the block does not have a valid typeface. ``` const typeface = engine.block.getTypefaces(text); ``` ``` getTextCursorRange(): Range ``` Returns the UTF-16 indices of the selected range of the text block that is currently being edited. If both the start and end index of the returned range have the same value, then the text cursor is positioned at that index. * Returns The selected UTF-16 range or { from: -1, to: -1 } if no text block is currently being edited. ``` const selectedRange = engine.block.getTextCursorRange(); ``` ``` getTextVisibleLineCount(id: DesignBlockId): number ``` Returns the number of visible lines in the given text block. * `id`: The text block whose line count should be returned. * Returns The number of lines in the text block. ``` const lineCount = engine.block.getTextVisibleLineCount(text); ``` ``` getTextVisibleLineGlobalBoundingBoxXYWH(id: DesignBlockId, lineIndex: number): XYWH ``` Returns the bounds of the visible area of the given line of the text block. The values are in the scene's global coordinate space (which has its origin at the top left). * `id`: The text block whose line bounding box should be returned. * `lineIndex`: The index of the line whose bounding box should be returned. * Returns The bounding box of the line. ``` const lineBoundingBox = engine.block.getTextVisibleLineGlobalBoundingBoxXYWH( ``` Configuring Callbacks - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/configuration/callbacks/?platform=web&language=js # Configuring Callbacks In this example, we will show you how to configure the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-callbacks?title=IMG.LY%27s+CE.SDK%3A+Configure+Callbacks&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-callbacks). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-callbacks?title=IMG.LY%27s+CE.SDK%3A+Configure+Callbacks&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-callbacks) ## Callbacks The CE.SDK offers several different callbacks that are triggered by user interaction. Follow this guide to see some examples with descriptions. ``` callbacks: { onUnsupportedBrowser: () => { /* This is the default window alert which will be shown in case an unsupported * browser tries to run CE.SDK */ window.alert( 'Your current browser is not supported.\nPlease use one of the following:\n\n- Mozilla Firefox 115 or newer\n- Apple Safari 15.6 or newer\n- Microsoft Edge 114 or newer\n- Google Chrome 114 or newer' ); }, onBack: () => { window.alert('Back callback!'); }, onClose: () => { window.alert('Close callback!'); }, onSave: (scene) => { window.alert('Save callback!'); console.info(scene); }, onDownload: (scene) => { window.alert('Download callback!'); console.info(scene); }, onLoad: () => { window.alert('Load callback!'); const scene = '...'; // Fill with sene return Promise.resolve(scene); }, onExport: (blobs, options) => { window.alert('Export callback!'); console.info(options.mimeType); console.info(options.jpegQuality); console.info(options.pages); return Promise.resolve(); }, onUpload: (file, onProgress) => { window.alert('Upload callback!'); const newImage = { id: 'exampleImageIdentifier', meta: { uri: 'https://YOURSERVER/images/file.jpg', thumbUri: 'https://YOURSERVER/images/thumb.jpg' } }; return Promise.resolve(newImage); } }, ``` In this example, all corresponding navigational elements in the user interface are enabled so that the callbacks can be tested. ``` navigation: { action: { close: true, back: true, save: true, download: true, load: true, export: true } } ``` ### General * `onUnsupportedBrowser: () => void` will be triggered when the browser version or type is [unsupported](/docs/cesdk/faq/browser-support/). If this handle is not configured, CE.SDK shows a native `window.alert` explaining that the current browser is unsupported and showing the list of supported ones. Alternatively a \[separate method can be imported and used to check browser support\](../shared/\_partials/integrate-with-vanilla-js. ``` onUnsupportedBrowser: () => { /* This is the default window alert which will be shown in case an unsupported * browser tries to run CE.SDK */ window.alert( 'Your current browser is not supported.\nPlease use one of the following:\n\n- Mozilla Firefox 115 or newer\n- Apple Safari 15.6 or newer\n- Microsoft Edge 114 or newer\n- Google Chrome 114 or newer' ); }, ``` ### Navigation * `onBack: () => void | Promise` allows the registration of a function that is called when the `back` action is triggered by the user. When a `Promise` is returned, a loading indicator will be shown on the 'Back'-button until the Promise resolves. ``` onBack: () => { window.alert('Back callback!'); }, ``` * `onClose: () => void | Promise` allows the registration of a function that is called when the `close` action is triggered by the user. When a `Promise` is returned, a loading indicator will be shown on the 'Close'-button until the Promise resolves. ``` onClose: () => { window.alert('Close callback!'); }, ``` ### Scene Load & Save * `onSave: (scene: string) => Promise` allows the registration of a function that is called when the `save` button is triggered by the user. It contains the information of the current `scene` as a string that can be stored and reloaded at a later time. The user flow is continued when the returned `Promise` is resolved. ``` onSave: (scene) => { window.alert('Save callback!'); console.info(scene); }, onDownload: (scene) => { window.alert('Download callback!'); console.info(scene); }, ``` * `onDownload: 'download' | (scene: string) => Promise` allows the registration of a function that is called when the `download` button is triggered by the user. It contains the information of the current `scene` as a string and thus is similar to the `onSave` callback. The purpose here is to download the scene to the client and is most useful in developer settings. If the callback is set to the string `download` instead of a function, the current scene is automatically downloaded. ``` onDownload: (scene) => { window.alert('Download callback!'); console.info(scene); }, ``` * `onLoad: () => Promise` allows the registration of a function that is called when the `load` button is triggered by the user. It returns a `Promise` with the `scene` as `string` as payload. ``` onLoad: () => { window.alert('Load callback!'); const scene = '...'; // Fill with sene return Promise.resolve(scene); }, ``` ### Export & Upload * `onExport: (blobs: Blob[], options: ExportOptions) => void` allows the registration of a function that is called when the `export` button is triggered by the user. It'll receive an array of `Blob` objects, one for each page that was selected for export. `ExportOptions` contains the `mimeType`, `quality` settings and `pages` that where selected for export. See [Exporting Pages](/docs/cesdk/ui/guides/export-pages/) for details. ``` onExport: (blobs, options) => { window.alert('Export callback!'); console.info(options.mimeType); console.info(options.jpegQuality); console.info(options.pages); return Promise.resolve(); }, ``` * `onUpload: (file: File, onProgress: (progress: number) => void, context: UploadCallbackContext) => Promise` allows the registration of a function that is called when the `upload` button is triggered by the user. The `file` parameter contains the data being uploaded via the upload dialog. The `onProgress` callback can be used to indicate progress to the UI, where a number of `1.0` equals 100 percent completion. The callback must return a Promise that must contain a unique `id: string`, the `uri: string` of the uploaded data, and the `thumbUri: string`. See [Uploading Images](/docs/cesdk/ui/guides/upload-images/) for details. ``` onUpload: (file, onProgress) => { window.alert('Upload callback!'); const newImage = { id: 'exampleImageIdentifier', meta: { uri: 'https://YOURSERVER/images/file.jpg', thumbUri: 'https://YOURSERVER/images/thumb.jpg' } }; return Promise.resolve(newImage); } ``` Adding Image Upload Support - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/guides/upload-images/ CESDK/CE.SDK/Web Editor/Guides # Adding Image Upload Support Learn how to offer custom image upload to your users when integrating CreativeEditor SDK. ![](/docs/cesdk/5bcdc487fd03c0b81dab2ca0ea0721de/add_file.png) Giving your users the option to use their own images within their creations will boost their creative output to new levels. While CE.SDK handles the bulk of the uploading process, handling the uploaded files requires implementing the `onUpload` callback on your side. ## Local Uploads To quickly get going, you may want to use the `localUpload` callback, that ships with CE.SDK and allows uploading images locally. To do so, pass `local` for the `callbacks.onUpload` configuration setting: ``` callbacks: { ... onUpload: 'local' } ``` This enables the "Add file" button within the image library but only stores the uploaded files locally. Therefore they won't be available when opening the same scene in a different environment or after a page reload. ## Remote Uploads To manage uploads within your application, you may provide an `onUpload` handler that processes the received `file` somewhere appropriate and returns an object describing the stored image: ``` callbacks.onUpload = (file) => { // Handle the received `File` object window.alert('Upload callback triggered!'); // For example, upload the file to your server const newImage = { id: 'uniqueImageIdentifier', // A unique ID for the uploaded image meta: { uri: 'https://YOURSERVER/images/file.jpg', // Fully qualified URL to the image source, e.g., `http://yourdomain.com/image.png` thumbUri: 'https://YOURSERVER/images/thumb.jpg' // Fully qualified URL to a thumbnail version of the image } }; // Usually, return a Promise that resolves with the new image object return Promise.resolve(newImage); }; ``` Once provided, the "Add file" button will be visible to your users and they can start adding images to their creations. [ Previous Exporting Pages ](/docs/cesdk/ui/guides/export-pages/)[ Next Editing Video ](/docs/cesdk/ui/guides/edit/) React Video Editor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/video-editor/react/ CESDK/CE.SDK/Web Editor/Solutions/Video Editor # React Video Editor SDK CreativeEditor SDK offers a comprehensive React library designed for creating and editing videos directly within the browser. This CE.SDK configuration is highly customizable and extendible, offering a complete suite of video editing features such as splitting, cropping, and composing clips on a timeline. [Explore Demo](https://img.ly/showcases/cesdk/video-ui/web) ## Key Capabilities of the React Video Editor SDK ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transform Perform video cropping, flipping, and rotating operations. ![images](/docs/cesdk/static/TrimSplit-9f679312338d9c81b5863f540fb13f39.png) ### Trim & Split Set start and end times, and split videos effortlessly. ![images](/docs/cesdk/static/MergeVideos-9f08894a0142a48861b0bf595804d314.png) ### Merge Videos Edit and merge multiple video clips into a single sequence. ![images](/docs/cesdk/static/VideoCollage-ed31587fdccad3fe7f335c6e70216355.png) ### Video Collage Arrange multiple clips on a single canvas. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Processing All video editing operations are executed directly in the browser, without server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit videos within your React application. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Easily add new functionalities using the plugins and engine API. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Build and integrate custom UIs tailored to your application's needs. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Libraries Integrate custom assets like filters, stickers, images, and videos. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Green Screen Support Utilize chroma keying for background removal. ![images](/docs/cesdk/static/Templating-5fc837e60dafb163c824ed6684fc7b0c.png) ### Templating Create design templates with placeholders and text variables for dynamic content. ## Browser Support Video editing mode relies on modern web codecs, which are currently only available in the latest versions of Google Chrome, Microsoft Edge, or other Chromium-based browsers. ## Prerequisites [Get the latest stable version of **Node.js & NPM**](https://www.npmjs.com/get-npm) ## Supported File Types [IMG.LY](http://img.ly/)'s Creative Editor SDK enables you to load, edit, and save **MP4 files** directly in the browser without server dependencies. The following file types can also be imported for use as assets during video editing: * MP3 * MP4 (with MP3 audio) * M4A * AAC * JPG * PNG * WEBP Individual scenes or assets from the video can be exported as JPG, PNG, Binary, or PDF files. ## Getting Started If you're ready to start integrating CE.SDK into your Vue.js application, check out the CE.SDK [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/). In order to configure the editor for a video editing use case consult our [video editor UI showcase](https://img.ly/showcases/cesdk/video-ui/web) and its [reference implementation](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-video-ui/src/components/case/CaseComponent.jsx). ## Understanding CE.SDK Architecture & API The following sections provide an overview of the key components of the CE.SDK video editor UI and its API architecture. If you're ready to start integrating CE.SDK into your React application, check out our [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/) or dive into the [Essential Guides](https://img.ly/docs/cesdk/ui/guides/video). ### CreativeEditor Video UI The CE.SDK video UI is built for intuitive video creation and editing. Here are the main components and customizable elements within the UI: ![](/docs/cesdk/7a1d859e523b71cad4257ca45524e689/Simple-Timeline-Mono.png) * **Canvas:** The core interaction area for video content. * **Dock:** Entry point for interactions not directly related to the selected video block, often used for accessing asset libraries. * **Canvas Menu:** Access block-specific settings like duplication or deletion. * **Inspector Bar:** Manage block-specific functionalities, such as adjusting properties of the selected block. * **Navigation Bar:** Handles global scene actions like undo/redo and zoom. * **Canvas Bar:** Provides tools for managing the overall canvas, such as adding pages or controlling zoom. * **Timeline:** Core video editing control, where clips and audio are arranged in time. Learn more about interacting with and manipulating video controls in our [video editor UI guide](https://img.ly/docs/cesdk/ui/guides/video). ### CreativeEngine CreativeEngine is the heart of CE.SDK, managing the rendering and manipulation of video scenes. It can be used in headless mode or integrated with the CreativeEditor UI. Below are key features and APIs provided by the CreativeEngine: * **Scene Management:** Create, load, save, and modify video scenes programmatically. * **Block Manipulation:** Create and manage video elements, such as shapes, text, and images. * **Asset Management:** Load assets like videos and images from URLs or local sources. * **Variable Management:** Define and manipulate variables within scenes for dynamic content. * **Event Handling:** Subscribe to events like block creation or updates for dynamic interaction. ## API Overview The APIs of CE.SDK are grouped into several categories, reflecting different aspects of scene management and manipulation. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/) * **Creating Blocks**: ``` const block = engine.block.create('shapes/star'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) Variables allow dynamic content within scenes to programmatically create variations of a design. * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source) * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API:**](https://img.ly/docs/cesdk/engine/api/events/) * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ## Customizing the React Video Editor CE.SDK provides extensive customization options to adapt the UI to various use cases. These options range from simple configuration changes to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object**: When initializing the CreativeEditor, you can pass a configuration object that defines basic settings such as the base URL for assets, the language, theme, and license key. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization**: Customize the language and labels used in the editor to support different locales. ``` const config = { i18n: { en: { variables: { my_custom_variable: { label: 'Custom Label' } } } } }; ``` * [Custom Asset Sources](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): Serve custom video or image assets from a remote URL. ### UI Customization Options * **Theme**: Choose between predefined themes such as 'dark' or 'light'. ``` const config = { theme: 'dark' }; ``` * **UI Components**: Enable or disable specific UI components based on your requirements. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations Learn more about extending editor functionality and customizing its UI to your use case by consulting our in-depth [customization guide](https://img.ly/docs/cesdk/ui/customization/guides/). Here is an overview of the APIs and components available to you. ### Order APIs Customization of the web editor's components and their order within these locations is managed through specific Order APIs, allowing the addition, removal, or reordering of elements. Each location has its own Order API, e.g., `setDockOrder`, `setCanvasMenuOrder`, `setInspectorBarOrder`, `setNavigationBarOrder`, and `setCanvasBarOrder`. ### Layout Components CE.SDK provides special components for layout control, such as `ly.img.separator` for separating groups of components and `ly.img.spacer` for adding space between components. ### Registration of New Components Custom components can be registered and integrated into the web editor using builder components like buttons, dropdowns, and inputs. These components can replace default ones or introduce new functionalities, deeply integrating custom logic into the editor. ### Feature API The Feature API enables conditional display and functionality of components based on the current context, allowing for dynamic customization. For example, you can hide certain buttons for specific block types. ## Plugins You can customize the CE.SDK web editor during its initialization using the APIs outlined above. For many use cases, this will be adequate. However, there are times when you might want to encapsulate functionality for reuse. This is where plugins become useful. Follow our [guide on building your own plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) to learn more or check out one of the plugins we built using this api: [**Background Removal**](https://img.ly/docs/cesdk/ui/customization/plugins/backgroundRemoval/): Adds a button to the canvas menu to remove image backgrounds. [**Vectorizer**](https://img.ly/docs/cesdk/ui/customization/plugins/vectorizer/): Adds a button to the canvas menu to quickly vectorize a graphic. ## Framework Support CreativeEditor SDK’s video editing library is compatible with any Javascript including, React, Angular, Vue.js, Svelte, Blazor, Next.js, Typescript. It is also compatible with desktop and server-side technologies such as electron, PHP, Laravel and Rails. [ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/solutions/video-editor/react/)[ Angular ](/docs/cesdk/ui/solutions/video-editor/angular/)[ Vue ](/docs/cesdk/ui/solutions/video-editor/vuejs/)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,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)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous Javascript ](/docs/cesdk/ui/solutions/video-editor/javascript/)[ Next Angular ](/docs/cesdk/ui/solutions/video-editor/angular/) Creating a Scene From an HTMLImageElement - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/create-scene-from-image-element/?platform=web&language=javascript # Creating a Scene From an HTMLImageElement In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image provided from an image element. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-element?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Image+Element&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-element). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-element?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Image+Element&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-element) Starting from an existing image allows you to use the editor for customizing individual assets. This is done by using `engine.scene.createFromImage(url: string, dpi = 300, pixelScaleFactor = 1): Promise` and passing a URL as argument. The `dpi` argument sets the dots per inch of the scene. The `pixelScaleFactor` sets the display's pixel scale factor. To use the image shown by an image element as the initial image, use the image element's `src` attribute as the `imageURL`. ``` const element = document.getElementById('image-element'); const imageURL = element.src; ``` Use the created URL as a source for the initial image. ``` let scene = await engine.scene.createFromImage(imageURL); ``` We can retrieve the graphic block id of this initial image using `cesdk.engine.block.findByType(type: ObjectType): number[]`. Note that that function returns an array. Since there's only a single graphic block in the scene, the id is at index `0`. ``` // Find the automatically added graphic block with an image fill in the scene. const block = engine.block.findByType('graphic')[0]; ``` We can then manipulate and modify this block. Here we modify its opacity with `cesdk.engine.block.setOpacity(id: number, opacity: number): void`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. engine.block.setOpacity(block, 0.5); ``` When starting with an initial image, the scenes page dimensions match the given image and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Layout - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-layout/?platform=web&language=javascript#size # Layout In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify scenes layout through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Layout of Blocks #### Note on layout and frame size The frame size is determined during the layout phase of the render process inside the engine. This means that calling `getFrameSize()` immediately after modifying the scene might return an inaccurate result. The CreativeEngine supports three different modes for positioning blocks. These can be set for each block and both coordinates independently: * `'Absolute'`: the position value is interpreted in the scene's current design unit. * `'Percent'`: the position value is interpreted as percentage of the block's parent's size, where 1.0 means 100%. * `'Auto'` : the position is automatically determined. Likewise there are also three different modes for controlling a block's size. Again both dimensions can be set independently: * `'Absolute'`: the size value is interpreted in the scene's current design unit. * `'Percent'`: the size value is interpreted as percentage of the block's parent's size, where 1.0 means 100%. * `'Auto'` : the block's size is automatically determined by the size of the block's content. ### Positioning ``` getPositionX(id: DesignBlockId): number ``` Query a block's x position. * `id`: The block to query. * Returns The value of the x position. ``` const x = engine.block.getPositionX(block); ``` ``` getPositionY(id: DesignBlockId): number ``` Query a block's y position. * `id`: The block to query. * Returns The value of the y position. ``` const y = engine.block.getPositionY(block); ``` ``` getPositionXMode(id: DesignBlockId): PositionMode ``` Query a block's mode for its x position. * `id`: The block to query. * Returns The current mode for the x position: absolute, percent or undefined. ``` const xMode = engine.block.getPositionXMode(block); ``` ``` getPositionYMode(id: DesignBlockId): PositionMode ``` Query a block's mode for its y position. * `id`: The block to query. * Returns The current mode for the y position: absolute, percent or undefined. ``` const yMode = engine.block.getPositionYMode(block); ``` ``` setPositionX(id: DesignBlockId, value: number): void ``` Update a block's x position. The position refers to the block's local space, relative to its parent with the origin at the top left. Required scope: 'layer/move' * `id`: The block to update. * `value`: The value of the x position. ``` engine.block.setPositionX(block, 0.25); ``` ``` setPositionY(id: DesignBlockId, value: number): void ``` Update a block's y position. The position refers to the block's local space, relative to its parent with the origin at the top left. Required scope: 'layer/move' * `id`: The block to update. * `value`: The value of the y position. ``` engine.block.setPositionY(block, 0.25); ``` ``` setPositionXMode(id: DesignBlockId, mode: PositionMode): void ``` Set a block's mode for its x position. Required scope: 'layer/move' * `id`: The block to update. * `mode`: The x position mode: absolute, percent or undefined. ``` engine.block.setPositionXMode(block, 'Percent'); ``` ``` setPositionYMode(id: DesignBlockId, mode: PositionMode): void ``` Set a block's mode for its y position. Required scope: 'layer/move' * `id`: The block to update. * `mode`: The y position mode: absolute, percent or undefined. ``` engine.block.setPositionYMode(block, 'Percent'); ``` ``` type PositionMode = 'Absolute' | 'Percent' | 'Auto' ``` * Absolute: Position in absolute design units. - Percent: Position in relation to the block's parent's size in percent, where 1.0 means 100%. - Auto: Position is automatically determined ``` engine.block.setPositionXMode(block, 'Percent'); ``` ### Size ``` getWidth(id: DesignBlockId): number ``` Query a block's width. * `id`: The block to query. * Returns The value of the block's width. ``` const width = engine.block.getWidth(block); ``` ``` getWidthMode(id: DesignBlockId): SizeMode ``` Query a block's mode for its width. * `id`: The block to query. * Returns The current mode for the width: absolute, percent or auto. ``` const widthMode = engine.block.getWidthMode(block); ``` ``` getHeight(id: DesignBlockId): number ``` Query a block's height. * `id`: The block to query. * Returns The value of the block's height. ``` const height = engine.block.getHeight(block); ``` ``` getHeightMode(id: DesignBlockId): SizeMode ``` Query a block's mode for its height. * `id`: The block to query. * Returns The current mode for the height: absolute, percent or auto. ``` const heightMode = engine.block.getHeightMode(block); ``` ``` setWidth(id: DesignBlockId, value: number, maintainCrop?: boolean): void ``` Update a block's width and optionally maintain the crop. If the crop is maintained, the crop values will be automatically adjusted. The content fill mode `Cover` is only kept if the `features/transformEditsRetainCoverMode` setting is enabled, otherwise it will change to `Crop`. Required scope: 'layer/resize' * `id`: The block to update. * `value`: The new width of the block. * `maintainCrop`: Whether or not the crop values, if available, should be automatically adjusted. ``` engine.block.setWidth(block, 0.5); engine.block.setWidth(block, 2.5, true); ``` ``` setWidthMode(id: DesignBlockId, mode: SizeMode): void ``` Set a block's mode for its width. Required scope: 'layer/resize' * `id`: The block to update. * `mode`: The width mode: Absolute, Percent or Auto. ``` engine.block.setWidthMode(block, 'Percent'); ``` ``` setHeight(id: DesignBlockId, value: number, maintainCrop?: boolean): void ``` Update a block's height and optionally maintain the crop. If the crop is maintained, the crop values will be automatically adjusted. The content fill mode `Cover` is only kept if the `features/transformEditsRetainCoverMode` setting is enabled, otherwise it will change to `Crop`. Required scope: 'layer/resize' * `id`: The block to update. * `value`: The new height of the block. * `maintainCrop`: Whether or not the crop values, if available, should be automatically adjusted. ``` engine.block.setHeight(block, 0.5); engine.block.setHeight(block, 2.5, true); ``` ``` setHeightMode(id: DesignBlockId, mode: SizeMode): void ``` Set a block's mode for its height. Required scope: 'layer/resize' * `id`: The block to update. * `mode`: The height mode: Absolute, Percent or Auto. ``` engine.block.setHeightMode(block, 'Percent'); ``` ``` type SizeMode = 'Absolute' | 'Percent' | 'Auto' ``` * Absolute: Size in absolute design units. - Percent: Size in relation to the block's parent's size in percent, where 1.0 means 100%. - Auto: Size is automatically determined ``` engine.block.setWidthMode(block, 'Percent'); ``` ### Layers ``` setAlwaysOnTop(id: DesignBlockId, enabled: boolean): void ``` Update the block's always-on-top property. If true, this blocks's global sorting order is automatically adjusted to be higher than all other siblings without this property. If more than one block is set to be always-on-top, the child order decides which is on top. * `id`: the block to update. * `enabled`: whether the block shall be always-on-top. ``` engine.block.setAlwaysOnTop(block, false) ``` ``` isAlwaysOnTop(id: DesignBlockId): boolean ``` Query a block's always-on-top property. * `id`: the block to query. * Returns true if the block is set to be always-on-top, false otherwise. ``` val isAlwaysOnTop = engine.block.isAlwaysOnTop(block) ``` ``` setAlwaysOnBottom(id: DesignBlockId, enabled: boolean): void ``` Update the block's always-on-bottom property. If true, this blocks's global sorting order is automatically adjusted to be lower than all other siblings without this property. If more than one block is set to be always-on-bottom, the child order decides which is on bottom. * `id`: the block to update. * `enabled`: whether the block shall always be below its siblings. ``` engine.block.setAlwaysOnBottom(block, false) ``` ``` isAlwaysOnBottom(id: DesignBlockId): boolean ``` Query a block's always-on-bottom property. * `id`: the block to query. * Returns true if the block is set to be always-on-bottom, false otherwise. ``` val isAlwaysOnBottom = engine.block.isAlwaysOnBottom(block) ``` ``` bringToFront(id: DesignBlockId): void ``` Updates the sorting order of this block and all of its manually created siblings so that the given block has the highest sorting order. If the block is parented to a track, it is first moved up in the hierarchy. * `id`: The id of the block to be given the highest sorting order among its siblings. ``` engine.block.bringToFront(block) ``` ``` sendToBack(id: DesignBlockId): void ``` Updates the sorting order of this block and all of its manually created siblings so that the given block has the lowest sorting order. If the block is parented to a track, it is first moved up in the hierarchy. * `id`: The id of the block to be given the lowest sorting order among its siblings. ``` engine.block.sendToBack(block) ``` ``` bringForward(id: DesignBlockId): void ``` Updates the sorting order of this block and all of its superjacent siblings so that the given block has a higher sorting order than the next superjacent sibling. If the block is parented to a track, it is first moved up in the hierarchy. * `id`: The id of the block to be given a higher sorting than the next superjacent sibling. ``` engine.block.bringForward(block) ``` ``` sendBackward(id: DesignBlockId): void ``` Updates the sorting order of this block and all of its manually created and subjacent siblings so that the given block will have a lower sorting order than the next subjacent sibling. If the block is parented to a track, it is first moved up in the hierarchy. * `id`: The id of the block to be given a lower sorting order than the next subjacent sibling. ``` engine.block.sendBackward(block) ``` ### Rotation ``` getRotation(id: DesignBlockId): number ``` Query a block's rotation in radians. * `id`: The block to query. * Returns The block's rotation around its center in radians. ``` const rad = engine.block.getRotation(block); ``` ``` setRotation(id: DesignBlockId, radians: number): void ``` Update a block's rotation. Required scope: 'layer/rotate' * `id`: The block to update. * `radians`: The new rotation in radians. Rotation is applied around the block's center. ``` engine.block.setRotation(block, Math.PI / 2); ``` ### Flipping ``` getFlipHorizontal(id: DesignBlockId): boolean ``` Query a block's horizontal flip state. * `id`: The block to query. * Returns A boolean indicating for whether the block is flipped in the queried direction ``` const flipHorizontal = engine.block.getFlipHorizontal(block); ``` ``` getFlipVertical(id: DesignBlockId): boolean ``` Query a block's vertical flip state. * `id`: The block to query. * Returns A boolean indicating for whether the block is flipped in the queried direction ``` const flipVertical = engine.block.getFlipVertical(block); ``` ``` setFlipHorizontal(id: DesignBlockId, flip: boolean): void ``` Update a block's horizontal flip. Required scope: 'layer/flip' * `id`: The block to update. * `flip`: If the flip should be enabled. ``` engine.block.setFlipHorizontal(block, true); ``` ``` setFlipVertical(id: DesignBlockId, flip: boolean): void ``` Update a block's vertical flip. Required scope: 'layer/flip' * `id`: The block to update. * `flip`: If the flip should be enabled. ``` engine.block.setFlipVertical(block, false); ``` ### Scaling ``` scale(id: DesignBlockId, scale: number, anchorX?: number, anchorY?: number): void ``` Scales the block and all of its children proportionally around the specified relative anchor point. This updates the position, size and style properties (e.g. stroke width) of the block and its children. Required scope: 'layer/resize' * `id`: The block that should be scaled. * `scale`: The scale factor to be applied to the current properties of the block. * `anchorX`: The relative position along the width of the block around which the scaling should occur. (0 = left edge, 0.5 = center, 1 = right edge) * `anchorY`: The relative position along the height of the block around which the scaling should occur. (0 = top edge, 0.5 = center, 1 = bottom edge) ``` engine.block.scale(block, 2.0, 0.5, 0.5); ``` ### Fill a Block's Parent ``` fillParent(id: DesignBlockId): void ``` Resize and position a block to entirely fill its parent block. Required scope: 'layer/move' - 'layer/resize' * `id`: The block that should fill its parent. ``` engine.block.fillParent(block); ``` ### Resize Blocks Content-aware ``` resizeContentAware(ids: DesignBlockId[], width: number, height: number): void ``` Resize all blocks to the given size. The content of the blocks is automatically adjusted to fit the new dimensions. Required scope: 'layer/resize' * `ids`: The blocks to resize. * `width`: The new width of the blocks. * `height`: The new height of the blocks. ``` const pages = engine.scene.getPages(); engine.block.resizeContentAware(pages, width: 100.0, 100.0); ``` ### Even Distribution Multiple blocks can be distributed horizontally or vertically within their bounding box. The blocks are moved to have the remaining space divided evenly between the blocks. Blocks without a position and blocks that are not allowed to be moved will not be distributed. ``` isDistributable(ids: DesignBlockId[]): boolean ``` Confirms that a given set of blocks can be distributed. * `ids`: An array of block ids. * Returns Whether the blocks can be distributed. ``` const distributable = engine.block.isDistributable([member1, member2]); ``` ``` distributeHorizontally(ids: DesignBlockId[]): void ``` Distribute multiple blocks horizontally within their bounding box so that the space between them is even. Required scope: 'layer/move' * `ids`: A non-empty array of block ids. ``` engine.block.distributeHorizontally([member1, member2], "Left"); ``` ``` distributeVertically(ids: DesignBlockId[]): void ``` Distribute multiple blocks vertically within their bounding box so that the space between them is even. Required scope: 'layer/move' * `ids`: A non-empty array of block ids. ``` engine.block.distributeVertically([member1, member2], "Top"); ``` ### Alignment Multiple blocks can be aligned horizontally or vertically within their bounding box. When a group is given, the elements within the group are aligned. If a single block is given, it gets aligned within its parent. Blocks without a position and blocks that are not allowed to be moved will not be aligned. ``` isAlignable(ids: DesignBlockId[]): boolean ``` Confirms that a given set of blocks can be aligned. * `ids`: An array of block ids. * Returns Whether the blocks can be aligned. ``` const alignable = engine.block.isAlignable([member1, member2]); ``` ``` alignHorizontally(ids: DesignBlockId[], horizontalBlockAlignment: HorizontalBlockAlignment): void ``` Align multiple blocks horizontally within their bounding box or a single block to its parent. Required scope: 'layer/move' * `ids`: A non-empty array of block ids. * `alignment`: How they should be aligned: left, right, or center ``` engine.block.alignHorizontally([member1, member2], "Left"); ``` ``` alignVertically(ids: DesignBlockId[], verticalBlockAlignment: VerticalBlockAlignment): void ``` Align multiple blocks vertically within their bounding box or a single block to its parent. Required scope: 'layer/move' * `ids`: A non-empty array of block ids. * `alignment`: How they should be aligned: top, bottom, or center ``` engine.block.alignVertically([member1, member2], "Top"); ``` ### Computed Dimensions ``` getFrameX(id: DesignBlockId): number ``` Get a block's layout position on the x-axis. The position is only available after an internal update loop which only occurs if the `features/implicitUpdatesEnabled` setting is set. * `id`: The block to query. * Returns The layout position on the x-axis. ``` const frameX = engine.block.getFrameX(block); ``` ``` getFrameY(id: DesignBlockId): number ``` Get a block's layout position on the y-axis. The position is only available after an internal update loop which only occurs if the `features/implicitUpdatesEnabled` setting is set. * `id`: The block to query. * Returns The layout position on the y-axis. ``` const frameY = engine.block.getFrameY(block); ``` ``` getFrameWidth(id: DesignBlockId): number ``` Get a block's layout width. The width is only available after an internal update loop which only occurs if the `features/implicitUpdatesEnabled` setting is set. * `id`: The block to query. * Returns The layout width. ``` const frameWidth = engine.block.getFrameWidth(block); ``` ``` getFrameHeight(id: DesignBlockId): number ``` Get a block's layout height. The height is only available after an internal update loop which only occurs if the `features/implicitUpdatesEnabled` setting is set. * `id`: The block to query. * Returns The layout height. ``` const frameHeight = engine.block.getFrameHeight(block); ``` ``` getGlobalBoundingBoxX(id: DesignBlockId): number ``` Get the x position of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `id`: The block whose bounding box should be calculated. * Returns float The x coordinate of the position of the axis-aligned bounding box. ``` const globalX = engine.block.getGlobalBoundingBoxX(block); ``` ``` getGlobalBoundingBoxY(id: DesignBlockId): number ``` Get the y position of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `id`: The block whose bounding box should be calculated. * Returns float The y coordinate of the position of the axis-aligned bounding box. ``` const globalY = engine.block.getGlobalBoundingBoxY(block); ``` ``` getGlobalBoundingBoxWidth(id: DesignBlockId): number ``` Get the width of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `id`: The block whose bounding box should be calculated. * Returns float The width of the axis-aligned bounding box. ``` const globalWidth = engine.block.getGlobalBoundingBoxWidth(block); ``` ``` getGlobalBoundingBoxHeight(id: DesignBlockId): number ``` Get the height of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `id`: The block whose bounding box should be calculated. * Returns float The height of the axis-aligned bounding box. ``` const globalHeight = engine.block.getGlobalBoundingBoxHeight(block); ``` ``` getScreenSpaceBoundingBoxXYWH(ids: DesignBlockId[]): XYWH ``` Get the position and size of the axis-aligned bounding box for the given blocks in screen space. * `ids`: The block to query. * Returns The position and size of the bounding box. ``` const screenSpaceRect = engine.block.getScreenSpaceBoundingBoxXYWH([block]); ``` ### Transform Locking You can lock the transform of a block to prevent changes to any of its transformations. That is the block's position, rotation, scale, and sizing. ``` isTransformLocked(id: DesignBlockId): boolean ``` Query a block's transform locked state. If true, the block's transform can't be changed. * `id`: The block to query. * Returns True if transform locked, false otherwise. ``` const isTransformLocked = engine.block.isTransformLocked(block); ``` ``` setTransformLocked(id: DesignBlockId, locked: boolean): void ``` Update a block's transform locked state. * `id`: The block to update. * `locked`: Whether the block's transform should be locked. ``` engine.block.setTransformLocked(block, true); ``` State - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/api/block-state/?platform=web&language=javascript # State In this example, we will show you how to use [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to retrieve's a block's state and to manually set a block in a pending state, an error state or back to a ready state. Blocks can perform operations that take some time or that can end in bad results. When that happens, blocks put themselves in a pending state or an error state and visual feedback is shown pertaining to the state. When an external operation is done to blocks, for example with a plugin, you may want to manually set the block's state to pending (if that external operation takes time) or to error (if that operation resulted in an error). The possible states of a block are: When calling `getState`, the returned state reflects the combined state of a block, the block's fill, the block's shape and the block's effects. If any of these blocks is in an `Error` state, the returned state will reflect that error. If none of these blocks is in error state but any is in `Pending` state, the returned state will reflect the aggregate progress of the block's progress. If none of the blocks are in error state or pending state, the returned state is `Ready`. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` getState(id: DesignBlockId): BlockState ``` Returns the block's state. If this block is in error state or this block has a `Shape` block, `Fill` block or `Effect` block(s), that is in error state, the returned state will be `Error`. Else, if this block is in pending state or this block has a `Shape` block, `Fill` block or `Effect` block(s), that is in pending state, the returned state will be `Pending`. Else, the returned state will be `Ready`. * `id`: The block to query. * Returns The block's state. ``` const state = engine.block.getState(block); ``` ``` setState(id: DesignBlockId, state: BlockState): void ``` Set the state of a block. * `id`: The block whose state should be set. * `state`: The new state to set. ``` engine.block.setState(block, {type: 'Pending', progress: 0.5}) ``` ``` onStateChanged: (ids: DesignBlockId[], callback: (ids: DesignBlockId[]) => void) => (() => void) ``` Subscribe to changes to the state of a block. Like `getState`, the state of a block is determined by the state of itself and its `Shape`, `Fill` and `Effect` block(s). * `blocks`: A list of blocks to filter events by. If the list is empty, events for every block are sent. * `callback`: The event callback. * Returns Subscription A handle to the subscription. Use it to unsubscribe when done. ``` const subscription = engine.block.onStateChanged([], (blocks) => { blocks.forEach(block => console.log(block)) }); ``` Creating a Scene From a Blob - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/engine/guides/create-scene-from-image-blob/?platform=web&language=javascript # Creating a Scene From a Blob In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image provided from a blob. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-blob?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Image+Blob&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-blob). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-blob?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Create+Scene+From+Image+Blob&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-create-scene-from-image-blob) Starting from an existing image allows you to use the editor for customizing individual assets. This is done by using `engine.scene.createFromImage(url: string, dpi = 300, pixelScaleFactor = 1): Promise` and passing a URL as argument. The `dpi` argument sets the dots per inch of the scene. The `pixelScaleFactor` sets the display's pixel scale factor. First, get hold of a `blob` by fetching an image from the web. This is just for demonstration purposes and your `blob` object may come from a different source. ``` const blob = await fetch('https://img.ly/static/ubq_samples/sample_4.jpg').then( (response) => response.blob() ); ``` Afterward, create a URL pointing to the blob via `URL.createObjectURL`. ``` const objectURL = URL.createObjectURL(blob); ``` Use the created URL as a source for the initial image. ``` let scene = await engine.scene.createFromImage(objectURL); ``` We can retrieve the graphic block id of this initial image using `cesdk.engine.block.findByType(type: ObjectType): number[]`. Note that that function returns an array. Since there's only a single graphic block in the scene, the block is at index `0`. We can then manipulate and modify this block. Here we modify its opacity with `cesdk.engine.block.setOpacity(id: number, opacity: number): void`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. engine.block.setOpacity(block, 0.5); ``` When starting with an initial image, the scenes page dimensions match the given image, and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Angular Video Editor SDK - CE.SDK | IMG.LY Docs [web/undefined/javascript] https://img.ly/docs/cesdk/ui/solutions/video-editor/angular/ CESDK/CE.SDK/Web Editor/Solutions/Video Editor # Angular Video Editor SDK CreativeEditor SDK provides a powerful Angular library designed for creating and editing videos directly in the browser. This CE.SDK configuration is highly customizable and extendible, offering a full suite of video editing features such as splitting, cropping, and composing clips on a timeline. [Explore Demo](https://img.ly/showcases/cesdk/video-ui/web) ## Key Capabilities of the Angular Video Editor SDK ![images](/docs/cesdk/static/Transform-7671d1d6ca658f4a127f6bd009fda88d.png) ### Transform Perform operations like video cropping, flipping, and rotating. ![images](/docs/cesdk/static/TrimSplit-9f679312338d9c81b5863f540fb13f39.png) ### Trim & Split Easily set start and end times, and split videos as needed. ![images](/docs/cesdk/static/MergeVideos-9f08894a0142a48861b0bf595804d314.png) ### Merge Videos Edit and combine multiple video clips into a single sequence. ![images](/docs/cesdk/static/VideoCollage-ed31587fdccad3fe7f335c6e70216355.png) ### Video Collage Arrange multiple clips on one canvas. ![images](/docs/cesdk/static/ClientSide-568a9b293bc26d2f158bd35c5cf6973d.png) ### Client-Side Processing Execute all video editing operations directly in the browser, with no need for server dependencies. ![images](/docs/cesdk/static/Headless-ae10154da3ed47406d8d08f72a896f11.png) ### Headless & Automation Programmatically edit videos within your Angular application. ![images](/docs/cesdk/static/Extendible-88d415d492ab62a3cc763ec674368df8.png) ### Extendible Add new functionalities seamlessly using the plugins and engine API. ![images](/docs/cesdk/static/CustomizableUI-7ccca682243d789ae9f7f94e27d0aa0b.png) ### Customizable UI Design and integrate custom UIs tailored to your application. ![images](/docs/cesdk/static/AssetLibraries-74b06d4fe6e09e3dc98b561af613a4cf.png) ### Asset Libraries Incorporate custom assets like filters, stickers, images, and videos. ![images](/docs/cesdk/static/GreenScreen-64b3fc376d1b9333c4896aa954bd08fc.png) ### Green Screen Support Apply chroma keying for background removal. ![images](/docs/cesdk/static/Templating-5fc837e60dafb163c824ed6684fc7b0c.png) ### Templating Create design templates with placeholders and text variables for dynamic content. ## Browser Support Video editing mode relies on modern web codecs, supported only in the latest versions of Google Chrome, Microsoft Edge, or other Chromium-based browsers. ## Prerequisites [Ensure you have the latest stable version of **Node.js & NPM** installed](https://www.npmjs.com/get-npm) ## Supported File Types Creative Editor SDK supports loading, editing, and saving **MP4 files** directly in the browser. Additionally, the following file types can be imported for use as assets during video editing: * MP3 * MP4 (with MP3 audio) * M4A * AAC * JPG * PNG * WEBP Scenes or individual assets from the video can be exported as JPG, PNG, Binary, or PDF files. ## Getting Started If you're ready to start integrating CE.SDK into your Vue.js application, check out the CE.SDK [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/). In order to configure the editor for a video editing use case consult our [video editor UI showcase](https://img.ly/showcases/cesdk/video-ui/web) and its [reference implementation](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-video-ui/src/components/case/CaseComponent.jsx). ## Understanding CE.SDK Architecture & API The sections below provide an overview of the key components of the CE.SDK video editor UI and its API architecture. If you're ready to start integrating CE.SDK into your Angular application, check out our [Getting Started guide](https://img.ly/docs/cesdk/engine/quickstart/) or explore the [Essential Guides](https://img.ly/docs/cesdk/ui/guides/video). ### CreativeEditor Video UI The CE.SDK video UI is designed for intuitive video creation and editing. Below are the main components and customizable elements within the UI: ![](/docs/cesdk/7a1d859e523b71cad4257ca45524e689/Simple-Timeline-Mono.png) * **Canvas:** The main interaction area for video content. * **Dock:** Entry point for interactions not directly related to the selected video block, often used for accessing asset libraries. * **Canvas Menu:** Access block-specific settings such as duplication or deletion. * **Inspector Bar:** Manage block-specific functionalities, like adjusting properties of the selected block. * **Navigation Bar:** Handles global scene actions like undo/redo and zoom. * **Canvas Bar:** Provides tools for managing the overall canvas, such as adding pages or controlling zoom. * **Timeline:** The core video editing control, where clips and audio are arranged over time. Learn more about interacting with and manipulating video controls in our [video editor UI guide](https://img.ly/docs/cesdk/ui/guides/video). ### CreativeEngine CreativeEngine is the core of CE.SDK, responsible for managing the rendering and manipulation of video scenes. It can be used in headless mode or integrated with the CreativeEditor UI. Below are key features and APIs provided by the CreativeEngine: * **Scene Management:** Programmatically create, load, save, and modify video scenes. * **Block Manipulation:** Create and manage video elements such as shapes, text, and images. * **Asset Management:** Load assets like videos and images from URLs or local sources. * **Variable Management:** Define and manipulate variables within scenes for dynamic content. * **Event Handling:** Subscribe to events such as block creation or updates for dynamic interaction. ## API Overview The APIs of CE.SDK are grouped into several categories, reflecting different aspects of scene management and manipulation. [**Scene API:**](https://img.ly/docs/cesdk/engine/api/scene/) * **Creating and Loading Scenes**[:](https://img.ly/docs/cesdk/engine/guides/create-scene/) ``` engine.scene.create(); engine.scene.loadFromURL(url); ``` * **Zoom Control**[:](https://img.ly/docs/cesdk/engine/api/scene-zoom/#sceneapisetzoomlevel) ``` engine.scene.setZoomLevel(1.0); engine.scene.zoomToBlock(blockId); ``` [**Block API:**](https://img.ly/docs/cesdk/engine/api/block/) * **Creating Blocks**: ``` const block = engine.block.create('shapes/star'); ``` * **Setting Properties**: ``` engine.block.setColor(blockId, 'fill/color', { r: 1, g: 0, b: 0, a: 1 }); engine.block.setString(blockId, 'text/content', 'Hello World'); ``` * **Querying Properties**: ``` const color = engine.block.getColor(blockId, 'fill/color'); const text = engine.block.getString(blockId, 'text/content'); ``` [**Variable API:**](https://img.ly/docs/cesdk/engine/api/variables/) Variables allow dynamic content within scenes to programmatically create variations of a design. * **Managing Variables**: ``` engine.variable.setString('myVariable', 'value'); const value = engine.variable.getString('myVariable'); ``` [**Asset API:**](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source) * **Managing Assets**: ``` engine.asset.add('image', 'https://example.com/image.png'); ``` [**Event API**](https://img.ly/docs/cesdk/engine/api/events/) * **Subscribing to Events**: ``` // Subscribe to scene changes engine.scene.onActiveChanged(() => { const newActiveScene = engine.scene.get(); }); ``` ## Customizing the Angular Video Editor CE.SDK provides extensive customization options to adapt the UI to various use cases. These options range from simple configuration changes to more advanced customizations involving callbacks and custom elements. ### Basic Customizations * **Configuration Object**: When initializing the CreativeEditor, you can pass a configuration object that defines basic settings such as the base URL for assets, the language, theme, and license key. ``` const config = { baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/1.18.0/assets', license: 'your-license-key', locale: 'en', theme: 'light' }; ``` * **Localization**: Customize the language and labels used in the editor to support different locales. ``` const config = { i18n: { en: { variables: { my_custom_variable: { label: 'Custom Label' } } } } }; ``` * [Custom Asset Sources](https://img.ly/docs/cesdk/engine/api/assets/#defining-a-custom-asset-source): Serve custom video or image assets from a remote URL. ### UI Customization Options * **Theme**: Choose between predefined themes such as 'dark' or 'light'. ``` const config = { theme: 'dark' }; ``` * **UI Components**: Enable or disable specific UI components based on your requirements. ``` const config = { ui: { elements: { toolbar: true, inspector: false } } }; ``` ## Advanced Customizations Learn more about extending editor functionality and customizing its UI to your use case by consulting our in-depth [customization guide](https://img.ly/docs/cesdk/ui/customization/guides/). Here is an overview of the APIs and components available to you. ### Order APIs Customization of the web editor's components and their order within these locations is managed through specific Order APIs, allowing the addition, removal, or reordering of elements. Each location has its own Order API, e.g., `setDockOrder`, `setCanvasMenuOrder`, `setInspectorBarOrder`, `setNavigationBarOrder`, and `setCanvasBarOrder`. ### Layout Components CE.SDK provides special components for layout control, such as `ly.img.separator` for separating groups of components and `ly.img.spacer` for adding space between components. ### Registration of New Components Custom components can be registered and integrated into the web editor using builder components like buttons, dropdowns, and inputs. These components can replace default ones or introduce new functionalities, deeply integrating custom logic into the editor. ### Feature API The Feature API enables conditional display and functionality of components based on the current context, allowing for dynamic customization. For example, you can hide certain buttons for specific block types. ## Plugins Customize the CE.SDK web editor during initialization using the outlined APIs. For many use cases, this is sufficient, but for more advanced scenarios, plugins are useful. Follow our [guide on building plugins](https://img.ly/docs/cesdk/ui/customization/plugins/) or explore existing plugins like: [**Background Removal**](https://img.ly/docs/cesdk/ui/customization/plugins/backgroundRemoval/): Adds a button to the canvas menu to remove image backgrounds. [**Vectorizer**](https://img.ly/docs/cesdk/ui/customization/plugins/vectorizer/): Adds a button to the canvas menu to quickly vectorize a graphic. ## Framework Support CreativeEditor SDK’s video editing library is compatible with any Javascript including, React, Angular, Vue.js, Svelte, Blazor, Next.js, Typescript. It is also compatible with desktop and server-side technologies such as electron, PHP, Laravel and Rails. [ Headless ](/docs/cesdk/engine/quickstart/)[ React ](/docs/cesdk/ui/solutions/video-editor/react/)[ Angular ](/docs/cesdk/ui/solutions/video-editor/angular/)[ Vue ](/docs/cesdk/ui/solutions/video-editor/vuejs/)[ Svelte ](/docs/cesdk/ui/quickstart?framework=svelte)[.electron\_svg\_\_cls-1{fill:#2b2e3a;fill-rule:evenodd}.electron\_svg\_\_cls-2{fill:#9feaf9} Electron ](/docs/cesdk/ui/quickstart?framework=electron)[ Next.js ](/docs/cesdk/ui/quickstart?framework=nextjs)[ Node.js ](/docs/cesdk/ui/quickstart?platform=node) Ready to get started? With a free trial and pricing that fits your needs, it's easy to find the best solution for your product. [Free Trial](https://img.ly/forms/free-trial) ### 500M+ video and photo creations are powered by IMG.LY every month ![HP logo](data:image/png;base64,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)![Shopify logo](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVAAAAB4CAMAAACTvloEAAACK1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVv0cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVv0cAAAAAAAAAAAAAAACVv0cAAACZwkiVv0cAAACVv0cAAAAAAAAAAACEsEEAAAAAAAAAAAAAAACWwUYAAAAAAACYwUyVwVIAAAAAAAAAAACVv0eWv0eXwEmWv0eXv0eVv0eWwEeWwEeVv0eWwEeYwEhejj6VwEeVwEcAAABfjj6Vv0eVv0Zejj5ejz6Vv0eVwEeVv0Zejj6UvkeWwEeVwEeWwEeXwEdikkNejz4AAACWwEgAAABfjz6XwUVijT2Vv0dejj5ejj+WwEdfjz6WwEeVv0Zfjz9ikEFejz9ejj5ejj6Xv0eVwEZejj6WwEdejz6VwEdgkD+Vv0dgjj9fjj5fjz5hjz5ejj5fjz5fjz5djz9ekD9smT8AAACVv0dejj7///+ItEX9/vqXwUuszXCZwk7v9uP6/Pb4+/Lg7cq61Yeoy2n0+ezs9N3R467D25edxFTo8dbK4KS/2I+204CkyGChx12fxVnl79Da6b/V5rbH3Z2wz3alyWTY57rc314sAAAAl3RSTlMA+/cJ8sd46+QSBPAPB88w5jQbDBjWgO3acJ+/VUhDm5KIOxb9r444UCneYCDo06M/4bosI8OFTCbqs6uLaO6nEvf0z8x9XgiWdWtSJGIdDQW3e1rXxxawMeSRinVtG/r58ryAfFP04UpCOuzBtquXKQ3OyaRlLh4W27umno1oWUwfeG5nYzXFu6+CP183dV8l2JpWlpUqU/DWcAAAE4hJREFUeNrs1s1q20AUhuFzMXMT2giJWXhjkBb6/+sIy8Y2cqWCwQiMTdMmtCaELj+Tm+2MQ6AQSmWnq3Ce1dH2ZY5miDHGGGOMMcYYYx+Ms2y6ff4YEfsP/D4fbCUg6uHgEHuv6D6A5roCUJskmU6IvUN1SgEls6LIJYC2jU9rTno7b+sCVmmOZSJxoYKMf6a3imKBeuuYsqUFQ9UCCHfEbuEUNeyuIk33FOmPGlYXu8C+InaDQwq3i0ibW4BcVi3UocpsuLlH7Gp9CvHZIW016KknKoG95zQK6ZLY9RcSEExJc2IhZK+HNRBG+hPo+El6Lf9owT74ZiptpHMzeYBV6a4Kli7t895fYxoL9bLwzgDkEzJsCHNSJVD2xaa94yfpaH4DyAcyTkAQ0YUE1kSTAnABPC+euOhYOwnkZHgDVEkvWmCZHJsQF8/nxdMXYqPMXdg7Mh5f7yZtA4TSFngNel7MuOg4GyCjiy3Q+GRM7y38wQQ9f5rx1o/xYCNNyOgDWCua7FZFKIA3QXXRr8T+qXGxdcg4WmhXx6w1i/42qCn6c6bdffv+i9jfRPFv7uyzR4koCgPwGYo0aQKidEFw0UVW1sWCvWLv3Wg0GnvsXRN7Lx80nomxu9bY+89TlHvvgRkkMySY+HzZZPeG2bz3PXBnkCdUP4iuDJbHLJ0gCzRQYf36HTfgf2Xzzhk2bFjEawO9ti6Vl62sHN3nnp8qC8pAqdvQmtS8/sz0JLRZ35Q/ZVT9Q3Z8aUDO4nA4grkRhRjos3bD4BML5m5eu2KDzDQP9PIRaIl7KDK5edBWtkjcHNifmKOI1NhVtiBxEHRZsFsevHP1pTH0bbN5oBcPQUtiBmRcMWinzoQVKzz9oVa/6S6sMR90WbdLHjy5ci+kKdB9V6El45HbOBrayJhhWxnsAspYNmGNAaDdpO3nT7AstQW65Sa0JIRcyAlt1IFcgQ69P4114pqruXXTCjbomgNdfwZaIqbLMB3aieTm6ibzHjdgnRnaurlt7YqlrJwaAuVOLoRWOJAxzYA2MgaQCySBcQ4MYh1LEjRYuXMMS1NnoMf3QguyyOW80EYpK3LDxZWTQ7Fez2jQYI3M6A302GlowTzkhvqgjfqS5MJ+YCYiIbl6ely5hA80WNZyoPvuQAsSyIWhrTYiVxQDYyJxBgbF3O7YsC7QovG8f/zW+/WX3gcP/x7ojuvQgh7kEtBWB5EJioJmSJ6LhjhBs+WymofP3n5+cpe59+a+IlDqFOg30oNcBNrKVnZU51pUcH4OuWl9QYcLanE+ePPo3l3ii7Kh1FnQr8siGjEE2qtz0HCHyTE8FBVFXCIm3jAD9NgsK7xncXK9fw/0JOg33oSMtR+0mbG7I9LhJUW0JyRkzCnQY5Nc5/6DFyxO5t6Hvwd6DPQrS8gUbPCvzd8o5mWWHfTYXZ/nu0d3672Q/x7oFtDNWEIu5IN/bcgAZHIdoMuKujx7WZ7EyyaBrl8Ieo3sQY5V4h+a4xAT7wU9Ji2re/9keVK9aoFSp6G5VGxg//LEiZlBsyIjnWr3JcE5UGFMzgqVxuaLUWPDVg85WMyXxpbG9Ykp19j8VTb2i1HFfCGfGZ/VdpLCfD/QY+7U2oI+vav04pkiUI3nJmPn7DAS1nHdTkUlhncB2PsV+dB5lqhFaovlrcjl4kPsDe4nh7orF3bH2QWkQpQu9WfM3GIfgN1s/SWInMP624jxdm+PmZvtAyEWNjPhjuqLbz8qU72P7yp9+dgs0HPwN86uhAfrBIZAhX0WcumR0G+2CwVHUXEQ7LckbcAalkQWiO4cVu0fDTB6uoMuHUR2KFv3kMudQ1WmCLjJDsZTZG9DyE3rZqemVTLxoaag9548/v3jleJOSdO5KTXLhQrmPwPliyOX8I1Om5Ay9TdCjWjaggo9EbIqxhcU5kN0Y236hoGiX0NEGa0zAKAjiKoGJKGTTFdpPtk8coQex9p/bYJMvBV3R89fPvjw8cOz3pcvnn+TmwV6GRrrm5mCSpnqJodFerP7hSWsZYgCNcwjoQrrEjHL8/iW5G3eofXLTUW+cgZpVxIAZtVmT6tnm4hceDRwg5DzuPkxdLAsvP6hMub3379uGujFI43zDKGaIdURtpCBDKNCAQR/XEJ1nhgw0/mactSCCoGYShpjK9MSR3VjbQBFUlgvML7hyKWhauZqmfj+guX5qSbEpoHuOwQN+IqqOz/CCb95kZuiVj/PSGBSEw3YiMvP7nTE9i1yoZKUMSoeNUllO4B/EaoyJGrv5hxJYJaQJ+N8nyYdkIkH/CPprawp0C23oIGuHKopsyHGvwtGgElL2Fgf9jmRFhukun4EG1mLyGwWALhdqCo4EABGWUX6UagyxtVuSQ7vkoneu8w3bYHuOAvqOumYOjwBs2tEziGhtAT+iCsqVBvDlIH8iwkD7U3QYzEh4ao2ORvGJiLV5MV1HB0AEPWgqgEdlbQHIMf+c7oFuS5g9kyViVfiKK8t0PUnQd0ok4hq//isE8DZHRkXXsSOOgGsYSkkMnmrhOTTszrKESsKPYtjWe8wWllDNfio4gXzabMBiVD1H0POUyltMh4KhTaiUAr9Nmh+pYxkmxazgo7nLyvFbcCsXFUbqJb3UOr4zKaP44cn7by33Ub4zS/V1mFJPwBfxKEMdDQ9AITdUOGfiMJEG1TMyyEhFeZ0On3ZBC1zwAkV48hVjaB8AzL0BSKvfAg+siR2bZ4dmO0b1AN9/OW1hkAbf61kRq5sBwUvUpZRv5cYx9JAlQNvZgOWdJGcuqFisUTzrA5CZ4n81jofKkrILQKmSF4PqP7I5ZWz5+ps9DT07c/2zrs5iSAM43uAdAIJXSEaUCAJwURibLFF7L333nXsvfeuY5lxzrH3Fnv/eAqRu2d37/DUm8Fx+P0nk+Pk4d23X4L15pt76oJqDvOQLU8mPBMUU5rJIjupdwfB0t1SWAjLQhkKFzspnzy8aGW1IZHxd07wIAGpouuHngEZAMeD9/6NRCL/GALw5ibw4MU77YJeVRkriTgH4+mO9jnBCBsdEIHppLHDbJQ/pwV2EfLWXdMHA7qUMnr74TEoHA2LwuBlSk6UCBMkDe9KChg98ivgHaYuuIE8eUpVno++aBZ049FfCup3ExY7RoHpcYKC4upDawO8TQ14DEitRxvpAl3sGK/4xQULYXqM7KcNWWiGSgwgiD0o//jPJFR5M2/EZnrMyfSaHj6/pyKo1vaIBxWrZr1oGkxCmEgUqsJg4SSbDcqmg8ljnTcvSROczXrF2OgofEUB+R39PikmWWSrSxIKMPF4QWE5IGZqcOTJrNW+eHiT5uMTjYKqPAs2RASCjWla0mawvCoiEYaUJu/vbRClgtlSgkbAWQRcRGk2bIkyYXuwE8pWbg2QD1f1hbMhGbMwAz/TnmFMe7nLRPHYP9Em6LVtyiM4QQQMfVJeAjQ6wPKUcy173u780D5zEjzyKCgdjUNJoihoUyzva8Hi2o3FGAf3HRdnOvmiRP7MuMZ3QDcKmMdq9eU+q+iHt5oE3a/cHqlfISJCQ3cbZiOy3Fb4fw0G8ynoDt9KSs3CxxupfTqhjxEPCpPGu+GbGKq0xRh2EYqBBqpUqhkuJ/Wu0jPkN1yH+dEdLYKuBUGRiR62ERqTPqkNDl532RN5W+i6xIZJuJsAGOUb8x9MFrgjgB2a/rg/RV9olcKPO4SpAI0vQ7UNJkrXe6oJspgT9M7jTkbQB6+1CLpGJbM3hk1sB62xqGirXNEJYUlmLqXB+ryPj3IZAt20cOLJVnEN3cAT0bO4Zgd2lWmcsvW2E+JNyC6ImyHzY3nWj8KhB0FZ1PbCne1s160hyptEMAVGzaQ01Q1Q+BnR8GYwm5DVIiaHMmOsdOlorIPsv9iCtzdChpBm7aIdS6W45NQdTC64WXGv6dttOnl6o0VQ1b1w5wQHO7Ow/TQJAUdMEtA7z/joHxNmECCdd40wQSJDVRb5ehnobpFziEJMciZAZSNX02HPOypI/2AyQcya8NjTuyOvXmoQdCVRwxXtwYx2uoZlLig8+0Ey1xMKZyeTlo4hQBIMrxDsxmHDVWZKf7h3Xnif7KUdUloOyZmQICwpi+QjbHLSZWJ87VTsNSF3O6mw9FaDoAeIOq0Bv4hUddXTvRRNwjcdQzeTlqYoq8Ga306Is4dy5wB7nT3ywg+E7CIqDVQ9ckQbSgDGDVelfX506cikQTfUFH2qZS6PbCcl8CaLDR9oecS7ySYRUM7Wo0ye31JLgMGM0lm4EoKvqx3uHbDTtW1GyuBjcBAgJnEB1O+OmKRKhdCsh7ye5t4rEPTpXQ2Cri29aO9LCCJzlFuD8sGbqWhPnmQpQVs7mKn0RDnImdLgav0iY5AJsFhohmKLj8Urud2WAX2li+OE5hTVDVXtkzzUIujG3aQ03QVwmVPonpglppgLdRvInOymiWB4baJMXaFOkKNfDj6qWYR03wdL03TaMw5TKx5p2OhJ5IpmECYMi+h18K/8AFS7hW68TEpT3cCkJePBJJyKudDwOPRKWOfmzsEZzStv7ylgK7RIEu5rLVxvVKx4uylGNH7gb8oUT0bORhgW9qaO+UdYEXkCNeh9LT50zUrNe3b8JLw/LNvAYCPhYp4FFepcUmY4wwDKM2FaTEgn2UudjHShjhRlJB/ipOoznloLN0YcTRjGbqYD0f2b9188udOVOX3+3Si/9MCvBO0GB80LT9DRJuHOsCmNG3xgrl5quwShVMgy0UyYKe+tGNhFFDR5wSYZsgVUVn0IBwlxBjpnE9e7e9D56s3XO3e+vsI+3nPVPBTZzzXwshOw0E056CjvszJJDhaAWGbXdMcZk1cab8LbuTCaYS3uGm3Bjiy3NNIiu26T7ECmqD2tghjMLm6VcTaVzn8qZPO373/4+PED6nn79R0tgm5nq3n7+GAuMMBZNNAVIr2dFbPQizl8sM1ku95ngoAbeXlFjakcVl6tXVcaYN+jq1DwtaGeLQMg3WIHdOBBcj7Cgx4KH75DTs9Sbdcj9zEmqQu6lh0r2fqLohAM9TVHmmORnri4NSSeD94d8uGpgTIRhHLymaloqEoEeu0I4q7Yz0XdOvypUPfJqcmJDOZqTaOdBPJf2g0acVLsVHniD3G0uwjLukGMC1XkNs5BeEHVx0rVBlEZS21BOQEXsiR6YKjpwl7HvxNGftzCUcfQ9jNQVcveQojA0woSjXb1Z1LxWHAcGVmiE4pJkyZB17BjpRmiCn2MdENMTHgVp1CjIeNSxVDs8MZzYkmmFxWINInYzQSVYSLN4zIx21Q8hykXirUR8lxTg5lvj7jwoCIhNwR91iRqRJkoTJnU9awpXmkQS9GtXnozg8JjxlGLCLvkiuREIGgjHCMW0tOPD7dv8jzAA19a0Ot08Rl1KCvQv5md2TqScBVEdNmzOqEIomgIu+BKdYJdUQqWIZjR0VCmjFNeIsBdKp4lB5lq8/ltXs9P7zQN6fjfjuNqg4CAxV/SzmZR/iz4CTqlwXSSR8hFsPekjiUgGxQuQ0hzAhyz4NgLMYvUIjPPpLNsR+R1JyPpw8/vb2gW9MwVStChVqXjHmgtjkaYuol/Wn0IAeJjgvyuZ3vWjgtdahgG4/Mk2Qw+kPCTNIz32lxEEdDcM9ROeObN4qR6+ebj04c/Rb394Omnu+qLDjxrdxPEVR/YYQG7Ejo8O8Kt3qJJ1JkkIE1xTpdfHs1E2bqQCXr3lqrEQKOKy87I9xUMnsEpG378ZJN0h0xMUjknvWgdQxSpb5Bvjs+CENy847nz9fHzR52dzzo7P77WsNuErOHGSt5Yot8Of4vVY20IVfVra7bDGY6YJWKQu06WX84ShnS4T1XI2mTxtPhz3Sf6mM/rgTqheVw3v9Xi8FhDPdrzd0WmmCUabZIfCEsvTmDzIT5R9SiHrXU31Lj3/t3Le9rWGRHFvfC4u3ZAKhqrrnGRv8eVjqUikVSt21ZiRiEavPnbRiNjUtFqL9EH1y4Tt6/HZ02a0C7oSlJGZnTAbqf+1Lawj0jwLNRb0AOkjMCMpS/RG3uzlSt1eTbrLegZUj5s3bBprDNG3PB3BJxEmdl6C7qRlA9smg4g+hI3h0R+4s0zTBdBkW2kbESgGZokupKd3oG9GDdRY/3iBWdHzRq0XD9BL5FygQV6Vb2ersQdMIiAw0xKsuT06sULNs+e31sXQU+ScmHshd1W/eSc2Dck0D1AG/klY+dMmrf+8IJNoOofCnqIlAtfH2q0pxcxpmMmDNb8ZU2bOmfJvCMLN80aNGwkLaz2SmnjNVIuBspB3hTWNXdAhOlp8ttMWn9q4c6zswaN/C1Bl649c+D40b2kXCShnRkh+hGgzvvwNPkzpi05ve7I+c2zB/XWJOia7YdOXrgylpSRCMwm3EQ/mlHPIX8X7abu2bph3aqDs4eVFnTN/uNHd++dRsrLeKoZqB/eICSgWaIL0+atXrVz9qh9w5ahoIVjvv3cid3/xp8EoXY79QKHsILVTPRk6qT1FxdtmTtqWO+fgq7df+jEhWPTyD9CCw5B9SQidB33vrV2ojsjJm34kbXOHXXr6oETR3f/U3//p62uSEBHFyo97FgFTVK9GTtizp69/5SYeYwyRFe8bYam/pPT5f8lcv8NSXPMSCpUqFChQoUKFSpUqFChQoUK/yHfAYTLavSW/S0uAAAAAElFTkSuQmCC)![Reuters logo](data:image/png;base64,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)![Hootsuite logo](data:image/png;base64,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)![Semrush logo](/docs/cesdk/static/Semrush-36d06eaa4f0e2685e6a82e5bd2aa995b.png)![Shutterfly logo](data:image/png;base64,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)![Sprout Social logo](/docs/cesdk/static/Sprout-Social-a66193af3d27d2e6d168b935ed893317.png)![One.com logo](data:image/png;base64,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)![Constant Contact logo](/docs/cesdk/static/Constant-Contact-cb87ff190563a61f7bc5df595954ac93.png) [ Previous React ](/docs/cesdk/ui/solutions/video-editor/react/)[ Next Vue.js ](/docs/cesdk/ui/solutions/video-editor/vuejs/) Theming in CreativeEditor SDK - CE.SDK | IMG.LY Docs [web/undefined/js] https://img.ly/docs/cesdk/ui/guides/theming/?platform=web&language=js # Theming in CreativeEditor SDK In this example, we will show you how to theme [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-ui-theming?title=IMG.LY%27s+CE.SDK%3A+Configure+Ui+Theming&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-ui-theming). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/configure-ui-theming?title=IMG.LY%27s+CE.SDK%3A+Configure+Ui+Theming&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/configure-ui-theming) ## Prerequisites Theming involves adjusting the colors, fonts, and sizes of the Editor UI. We have provisioned three levels of support for themes: 1. [**Our built-in themes**](#using-the-built-in-themes-and-scale) can be used out-of-the-box. 2. [**Our theme generator**](#using-the-theme-generator) can be used for quickly generating new themes based on three main colors 3. [**A custom theme**](#using-a-custom-theme) can be added by leveraging our theming API for the most detailed customization of our UI. ## Using the built-in Themes and Scale We provide two built-in themes, which can be configured via * `theme: string` can be set to either `dark` or `light`. Both styles cover popular defaults offered by many current operating systems and apps. By default the 'dark' theme is active. * `ui.scale: "normal" | "large" | ({ containerWidth, isTouch }: { containerWidth?: number, isTouch?: boolean }) => "normal" | "large"` In `large` mode, many UI elements, margins and fonts are increased in size. In addition to the stylistic choice, this improves readability and can aid users with slight visual or motor impairments, and users with small screens. By default, the `normal` scale is applied. For more flexibility, a callback can be passed as a value that needs to return either of the two string values mentioned above. Inside that callback, two parameters are available. `containerWidth` returns the width of the HTML element into which the CE.SDK editor is being rendered. A potential use case is rendering different scalings on devices with a small width, like mobile phones. `isTouch` returns a boolean which indicates if the user agent supports touch input or not. For example, this can be used to render the `large` UI scaling if a device has touch input to make it easier to target the UI elements with a finger. ``` theme: 'light', // 'light' or 'dark' ui: { scale: ({ containerWidth, isTouch }) => { if (containerWidth < 600 || isTouch) { return 'large'; } else { return 'normal'; } }, // or 'normal' or 'large' ``` ## Using the Theme Generator In order to get started quickly with building a custom theme, use our built-in theme generator found in the 'Settings' panel. Using only three base colors, this will generate a theme file that you can directly use. See [Theming API](#theming-api) on how to provide your theme. ``` elements: { panels: { settings: true } } ``` ## Using a Custom Theme To provide a theme to the editor, link your themes `index.css` files or populate a `