Skip to content

Instantly share code, notes, and snippets.

@ljmotta
Last active October 13, 2020 20:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ljmotta/b25f53e0967252d1e37f709a264128dd to your computer and use it in GitHub Desktop.
Save ljmotta/b25f53e0967252d1e37f709a264128dd to your computer and use it in GitHub Desktop.
Base64Png Editor Final Example
import * as React from "react";
import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import { EditorApi, KogitoEditorEnvelopeContextType } from "@kogito-tooling/editor/dist/api";
import { DEFAULT_RECT } from "@kogito-tooling/guided-tour/dist/api";
import { EmptyState, EmptyStateIcon, Nav, NavItem, NavList, Page, Switch, Title } from "@patternfly/react-core";
import CubesIcon from "@patternfly/react-icons/dist/js/icons/cubes-icon";
import { Base64PngEdit, Base64PngStateControl } from "./Base64PngStateControl";
import "./styles.scss";
const INITIAL_INVERT = "0";
interface Props {
envelopeContext: KogitoEditorEnvelopeContextType;
}
export const Base64PngEditor = React.forwardRef<EditorApi, Props>((props, forwardedRef) => {
const [editorContent, setEditorContent] = useState("");
const [originalContent, setOriginalContent] = useState("");
const stateControl = useMemo(() => new Base64PngStateControl(), [originalContent]);
const getContent = useCallback(() => {
return editorContent;
}, [editorContent]);
const getPreview = useCallback(() => {
const width = imageRef.current!.width;
const height = imageRef.current!.height;
return `
<svg version="1.1" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="${width}" height="${height}" xlink:href="data:image/png;base64,${editorContent}" />
</svg>`;
}, [editorContent]);
const setContent = useCallback((path: string, content: string) => {
setOriginalContent(content);
stateControl.clearStateControl();
updateEditorToInitialState();
}, [stateControl]);
const undo = useCallback(() => {
stateControl.undo();
updateEditorStateWithCurrentEdit(stateControl.getCurrentBase64PngEdit());
}, [stateControl]);
const redo = useCallback(() => {
stateControl.redo();
updateEditorStateWithCurrentEdit(stateControl.getCurrentBase64PngEdit());
}, [stateControl]);
const updateEditorStateWithCurrentEdit = useCallback((edit?: Base64PngEdit) => {
if (edit) {
setInvert(edit.invert);
} else {
updateEditorToInitialState();
}
}, []);
const updateEditorToInitialState = useCallback(() => {
setInvert(INITIAL_INVERT);
}, []);
useEffect(() => {
props.envelopeContext.channelApi.notifications.receive_ready();
}, []);
useImperativeHandle(forwardedRef, () => {
return {
getContent: () => Promise.resolve(getContent()),
setContent: (path: string, content: string) => Promise.resolve(setContent(path, content)),
getPreview: () => Promise.resolve(getPreview()),
undo: () => Promise.resolve(undo()),
redo: () => Promise.resolve(redo()),
getElementPosition: (selector: string) => Promise.resolve(DEFAULT_RECT),
};
});
const imageRef = useRef<HTMLImageElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const [disabled, setDisabled] = useState(true);
const [invert, setInvert] = useState(INITIAL_INVERT);
const tweakInvert = useCallback((value: string) => {
setInvert(value);
const command: Base64PngEdit = {
id: new Date().getTime().toString(),
filter: `invert(${value})`,
invert: value,
};
stateControl.updateCommandStack(JSON.stringify(command));
props.envelopeContext.channelApi.notifications.receive_newEdit(command);
}, [invert, stateControl]);
useEffect(() => {
const invertId = props.envelopeContext.services.keyboardShortcuts.registerKeyPress(
"i",
`Edit | Invert Image`,
async () => {
if (!disabled && invert === "100") {
tweakInvert("0");
} else if (!disabled && invert === "0") {
tweakInvert("100");
}
}
);
return () => {
props.envelopeContext.services.keyboardShortcuts.deregister(invertId);
};
}, [disabled, invert]);
useEffect(() => {
const ctx = canvasRef.current?.getContext("2d")!;
ctx.filter = stateControl.getCurrentBase64PngEdit()?.filter ?? `invert(${invert}%)`;
ctx.drawImage(imageRef.current!, 0, 0);
setEditorContent(canvasRef.current!.toDataURL().split(",")[1]);
}, [invert, stateControl]);
useEffect(() => {
const ctx = canvasRef.current?.getContext("2d")!;
canvasRef.current!.width = 0;
canvasRef.current!.height = 0;
imageRef.current!.onload = () => {
canvasRef.current!.width = imageRef.current!.width;
canvasRef.current!.height = imageRef.current!.height;
ctx.drawImage(imageRef.current!, 0, 0);
setEditorContent(canvasRef.current!.toDataURL().split(",")[1]);
setDisabled(false);
};
return () => {
imageRef.current!.onload = null;
};
}, []);
return (
<div className={"base64png-editor--main"}>
<div className={"base64png-editor--viewport"}>
<img
ref={imageRef}
className={"base64png-editor--image"}
src={`data:image/png;base64,${originalContent}`}
alt={"Original"}
/>
{disabled && (
<EmptyState>
<EmptyStateIcon icon={CubesIcon} />
<Title headingLevel="h5" size="4xl">
Empty image
</Title>
</EmptyState>
)}
<canvas ref={canvasRef} className={"base64png-editor--canvas"} />
</div>
<div className={"base64png-editor--tweaks"}>
<Nav aria-label="Image tweaker">
<NavList>
<NavItem itemId={0}>
<div className={"base64png-editor--tweaks-nav-item"}>
<p>Invert</p>
<Switch
id="invert-switch"
isDisabled={disabled}
isChecked={invert === "100"}
onChange={(value) => tweakInvert(value ? "100" : "0")}
/>
</div>
</NavItem>
{stateControl.isDirty() && (
<div style={{ display: "flex", alignItems: "center", padding: "20px" }}>
<p style={{ color: "red" }}>Image was Edited!</p>
</div>
)}
</NavList>
</Nav>
</div>
</div>
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment