Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const SAMPLE_IMAGES = [
'https://picsum.photos/id/1040/1200/800',
];

const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;

const allToolsEnabled = () =>
Object.fromEntries(TOOL_NAMES.map((tool) => [tool, true])) as Record<
ToolName,
Expand All @@ -32,12 +34,46 @@ export default function App() {
const [saved, setSaved] = useState<ImageEditorSaveResult | null>(null);
const [status, setStatus] = useState('Loading editor…');

// Invalidates in-flight file reads so a slow read can never overwrite a
// newer image choice (upload or sample) after the fact.
const readTokenRef = useRef(0);
const readerRef = useRef<FileReader | null>(null);

const nextImage = () => {
readTokenRef.current++;
readerRef.current?.abort();
const index = SAMPLE_IMAGES.indexOf(image);
setImage(SAMPLE_IMAGES[(index + 1) % SAMPLE_IMAGES.length]);
setStatus('Image changed (reset)');
};

const uploadImage = (file: File) => {
// accept="image/*" is only a picker hint — enforce type and size here.
if (!file.type.startsWith('image/')) {
setStatus(`"${file.name}" is not an image`);
return;
}
if (file.size > MAX_UPLOAD_BYTES) {
setStatus(`"${file.name}" is too large (max 20 MB)`);
return;
}
readerRef.current?.abort();
const token = ++readTokenRef.current;
const reader = new FileReader();
readerRef.current = reader;
reader.onload = () => {
if (token !== readTokenRef.current) return;
setImage(reader.result as string);
setStatus(`Loaded "${file.name}" (reset)`);
};
reader.onerror = () => {
if (token !== readTokenRef.current) return;
console.error('[demo] file read failed', reader.error);
setStatus(`Could not read "${file.name}"`);
};
reader.readAsDataURL(file);
};

const checkChanges = () => {
const editor = editorRef.current?.editor;
if (!editor) return;
Expand Down Expand Up @@ -82,6 +118,7 @@ export default function App() {
tools={tools}
onToolToggle={toggleTool}
onChangeImage={nextImage}
onUploadImage={uploadImage}
onCheckChanges={checkChanges}
onSnapshot={snapshot}
/>
Expand Down
21 changes: 20 additions & 1 deletion demo/src/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef } from 'react';

import type { UnlayerLocale } from '@unlayer/types';

Expand Down Expand Up @@ -31,16 +31,35 @@ interface SidebarProps {
tools: Record<ToolName, boolean>;
onToolToggle(tool: ToolName): void;
onChangeImage(): void;
onUploadImage(file: File): void;
onCheckChanges(): void;
onSnapshot(): void;
}

export default function Sidebar(props: SidebarProps) {
const fileInputRef = useRef<HTMLInputElement>(null);

return (
<aside className="sidebar">
<section className="section">
<h2 className="section-label">Actions</h2>
<button onClick={props.onChangeImage}>Change image</button>
<button onClick={() => fileInputRef.current?.click()}>
Upload image…
</button>
<input
ref={fileInputRef}
type="file"
accept="image/*"
hidden
onChange={(event) => {
const file = event.target.files?.[0];
// Clear before the callback so re-selecting the same file always
// fires onChange, even if the handler throws.
event.target.value = '';
if (file) props.onUploadImage(file);
}}
/>
<button onClick={props.onCheckChanges}>Has changes?</button>
<button onClick={props.onSnapshot}>Snapshot</button>
</section>
Expand Down
Loading