From 6c4b9b15e069ceae54dcebc374436239ca0b5a09 Mon Sep 17 00:00:00 2001 From: sidgaikwad Date: Fri, 4 Sep 2026 12:59:07 +0530 Subject: [PATCH] feat(demo): exercise AI Assistant, dock, corners and custom icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The demo is what people try before adopting the component, but several documented options were never wired into it — including the AI Assistant, which is the README's headline feature. - AI Assistant, gated on VITE_UNLAYER_PROJECT_ID. The controls are replaced by a pointer to demo/.env.example when it is unset, so the demo still works without an Unlayer account. - features.imageEditor.dock, as a Left/Right tool rail selector. - tools.corners, which was missing from TOOL_NAMES entirely — it is configured like any other tool but is the rounded-corners control inside Crop. - A custom tool icon, using raw markup. Not the Font Awesome name form the README shows: that is silently ignored (#64). Also fix snapshot(), which fabricated `blob: new Blob()`. The download link worked because it uses dataUrl, but the object claimed to be an ImageEditorSaveResult while lying about `blob` — misleading in the file people read to learn the API. It now builds the real blob. --- demo/.env.example | 4 +++ demo/src/App.tsx | 55 +++++++++++++++++++++++++++++++++++++----- demo/src/Sidebar.tsx | 43 +++++++++++++++++++++++++++++++++ demo/src/styles.css | 14 +++++++++++ demo/src/vite-env.d.ts | 9 +++++++ 5 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 demo/.env.example diff --git a/demo/.env.example b/demo/.env.example new file mode 100644 index 0000000..5afabdc --- /dev/null +++ b/demo/.env.example @@ -0,0 +1,4 @@ +# Optional. A projectId from https://console.unlayer.com/ with the AI +# Assistant feature enabled. The demo hides the AI controls when this is +# unset, so the rest of the demo works without an account. +VITE_UNLAYER_PROJECT_ID= diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 527f79c..bac6161 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -16,6 +16,15 @@ const SAMPLE_IMAGES = [ const MAX_UPLOAD_BYTES = 20 * 1024 * 1024; +// The AI Assistant needs a projectId with the feature enabled, so the demo +// only offers it when one is configured. See demo/.env.example. +const PROJECT_ID = Number(import.meta.env.VITE_UNLAYER_PROJECT_ID) || undefined; + +// Demonstrates features.imageEditor.tools..icon. Raw markup is +// the form that works today; see #64 for the Font Awesome name form. +const CUSTOM_TEXT_ICON = + ''; + const allToolsEnabled = () => Object.fromEntries(TOOL_NAMES.map((tool) => [tool, true])) as Record< ToolName, @@ -31,6 +40,8 @@ export default function App() { const [tools, setTools] = useState>(allToolsEnabled); const [sidebarOpen, setSidebarOpen] = useState(true); + const [dock, setDock] = useState<'left' | 'right'>('right'); + const [ai, setAi] = useState(false); const [saved, setSaved] = useState(null); const [status, setStatus] = useState('Loading editor…'); @@ -80,12 +91,14 @@ export default function App() { setStatus(editor.hasChanges() ? 'Unsaved changes' : 'No unsaved changes'); }; - const snapshot = () => { + const snapshot = async () => { const dataUrl = editorRef.current?.editor?.getImage(); - if (dataUrl) { - setSaved({ dataUrl, blob: new Blob() }); - setStatus('Snapshot taken via getImage()'); - } + if (!dataUrl) return; + // Build the real blob rather than an empty one: this object is shaped + // like an ImageEditorSaveResult, so it should not lie about `blob`. + const blob = await (await fetch(dataUrl)).blob(); + setSaved({ dataUrl, blob }); + setStatus(`Snapshot taken via getImage() (${blob.size} bytes)`); }; const toggleTool = (tool: ToolName) => { @@ -117,6 +130,19 @@ export default function App() { onLocaleChange={setLocale} tools={tools} onToolToggle={toggleTool} + dock={dock} + onDockChange={(next) => { + setDock(next); + setStatus('Tool rail moved (remount)'); + }} + aiAvailable={PROJECT_ID !== undefined} + ai={ai} + onAiChange={(enabled) => { + setAi(enabled); + setStatus( + `AI Assistant ${enabled ? 'enabled' : 'disabled'} (remount)` + ); + }} onChangeImage={nextImage} onUploadImage={uploadImage} onCheckChanges={checkChanges} @@ -131,7 +157,24 @@ export default function App() { options={{ theme, locale, - features: { imageEditor: { tools } }, + projectId: PROJECT_ID, + features: { + ai: { enabled: ai, assistant: ai }, + imageEditor: { + dock, + tools: { + ...tools, + // A tool entry can also be an object, which is how a + // custom icon is supplied. Raw markup, not a Font + // Awesome name — the name form is silently ignored + // (see #64). + text: { + enabled: tools.text, + icon: CUSTOM_TEXT_ICON, + }, + }, + }, + }, }} onLoad={() => setStatus('Editor ready')} onSave={(result) => { diff --git a/demo/src/Sidebar.tsx b/demo/src/Sidebar.tsx index 6babf7e..2f7523f 100644 --- a/demo/src/Sidebar.tsx +++ b/demo/src/Sidebar.tsx @@ -4,6 +4,9 @@ import type { UnlayerLocale } from '@unlayer/types'; export const TOOL_NAMES = [ 'crop', + // Configured through features.imageEditor.tools like any other tool, but + // it is the rounded-corners control inside Crop rather than its own tab. + 'corners', 'resize', 'filter', 'draw', @@ -28,6 +31,11 @@ interface SidebarProps { onThemeChange(theme: 'light' | 'dark'): void; locale: UnlayerLocale; onLocaleChange(locale: UnlayerLocale): void; + dock: 'left' | 'right'; + onDockChange(dock: 'left' | 'right'): void; + aiAvailable: boolean; + ai: boolean; + onAiChange(enabled: boolean): void; tools: Record; onToolToggle(tool: ToolName): void; onChangeImage(): void; @@ -95,6 +103,41 @@ export default function Sidebar(props: SidebarProps) { +
+

Layout (remounts editor)

+ + + +
+

AI Assistant

+ {props.aiAvailable ? ( + + ) : ( +

+ Set VITE_UNLAYER_PROJECT_ID in demo/.env{' '} + to try the AI Assistant. See demo/.env.example. +

+ )} +
+

Tools (remounts editor)

diff --git a/demo/src/styles.css b/demo/src/styles.css index 7403a95..ea7f81f 100644 --- a/demo/src/styles.css +++ b/demo/src/styles.css @@ -192,3 +192,17 @@ body, margin-top: 8px; border-radius: 4px; } + +.hint { + margin: 0; + font-size: 12px; + line-height: 1.5; + color: #8f96a3; +} + +.hint code { + font-size: 11px; + padding: 1px 4px; + border-radius: 4px; + background: #232732; +} diff --git a/demo/src/vite-env.d.ts b/demo/src/vite-env.d.ts index 11f02fe..fff2fb5 100644 --- a/demo/src/vite-env.d.ts +++ b/demo/src/vite-env.d.ts @@ -1 +1,10 @@ /// + +interface ImportMetaEnv { + /** Optional Unlayer projectId; enables the AI Assistant controls. */ + readonly VITE_UNLAYER_PROJECT_ID?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +}