Skip to content

Commit

Permalink
[playground] Add sessionStorage and some examples (#754)
Browse files Browse the repository at this point in the history
  • Loading branch information
wkazmierczak authored Sep 16, 2024
1 parent 6c9d606 commit 9de5b58
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 13 deletions.
37 changes: 31 additions & 6 deletions docs/src/components/PlaygroundCodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import componentTypesJsonSchema from '../../component_types.schema.json';
import JSONEditor from '../jsonEditor';
import { jsonrepair } from 'jsonrepair';
import Ajv from 'ajv';
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';

interface PlaygroundCodeEditorProps {
onChange: (content: object | Error) => void;
initialCodeEditorContent: object;
codeEditorOverrideContent: object;
}

function ajvInitialization(): Ajv.Ajv {
Expand All @@ -28,9 +29,27 @@ function ajvInitialization(): Ajv.Ajv {
return ajv;
}

function PlaygroundCodeEditor({ onChange, initialCodeEditorContent }: PlaygroundCodeEditorProps) {
function saveToLocalAndSessionStorage(jsonContent: object) {
if (ExecutionEnvironment.canUseDOM) {
const jsonContentStringify = JSON.stringify(jsonContent);
sessionStorage.setItem('playgroundCodeEditorContent', jsonContentStringify);
localStorage.setItem('playgroundCodeEditorContent', jsonContentStringify);
}
}

function PlaygroundCodeEditor({ onChange, codeEditorOverrideContent }: PlaygroundCodeEditorProps) {
const [jsonEditor, setJsonEditor] = useState<JSONEditor | null>(null);

const loadFromSessionStorage = () => {
const savedContent = ExecutionEnvironment.canUseDOM
? sessionStorage.getItem('playgroundCodeEditorContent')
: null;
if (savedContent) {
return JSON.parse(savedContent);
}
return codeEditorOverrideContent;
};

const editorContainer = useCallback((node: HTMLElement) => {
if (node === null) {
return;
Expand All @@ -47,11 +66,12 @@ function PlaygroundCodeEditor({ onChange, initialCodeEditorContent }: Playground
ajv,
onChange: () => {
try {
const jsonContent = editor.get();
const jsonContent = JSON.parse(jsonrepair(editor.getText()));
onChange(jsonContent);
if (!validate(jsonContent)) {
throw new Error('Invalid JSON!');
}
saveToLocalAndSessionStorage(jsonContent);
} catch (error) {
onChange(error);
}
Expand All @@ -71,13 +91,18 @@ function PlaygroundCodeEditor({ onChange, initialCodeEditorContent }: Playground
}
},
});

editor.setSchema(componentTypesJsonSchema);
editor.set(initialCodeEditorContent);

editor.set(loadFromSessionStorage());
setJsonEditor(editor);
}, []);

useEffect(() => {
saveToLocalAndSessionStorage(codeEditorOverrideContent);
if (jsonEditor && codeEditorOverrideContent) {
jsonEditor.update(codeEditorOverrideContent);
}
}, [jsonEditor, codeEditorOverrideContent]);

useEffect(() => {
return () => {
if (jsonEditor) {
Expand Down
17 changes: 16 additions & 1 deletion docs/src/components/PlaygroundSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import PlaygroundSettingsImages from './PlaygroundSettingsImages';
import SettingsInputs from './PlaygroundSettingsInputs';
import OutputResolution from './PlaygroundSettingsOutput';
import PlaygroundSettingsShaders from './PlaygroundSettingsShaders';
import PlaygroundSettingsExamples from './PlaygroundSettingsExamples';

type ModalContent = 'inputs' | 'images' | 'shaders';
type ModalContent = 'inputs' | 'images' | 'shaders' | 'examples';

interface PlaygroundSettingsProps {
onSubmit: () => Promise<void>;
onInputResolutionChange: (input_id: string, resolution: InputResolution) => void;
onOutputResolutionChange: (resolution: Resolution) => void;
populateEditorWithExample: (content: object | Error) => void;
inputsSettings: InputsSettings;
sceneValidity: boolean;
outputResolution: Resolution;
Expand All @@ -25,6 +27,7 @@ export default function PlaygroundSettings({
onSubmit,
onInputResolutionChange,
onOutputResolutionChange,
populateEditorWithExample,
inputsSettings,
sceneValidity,
outputResolution,
Expand All @@ -41,6 +44,13 @@ export default function PlaygroundSettings({
/>
) : modalContent === 'images' ? (
<PlaygroundSettingsImages />
) : modalContent === 'examples' ? (
<PlaygroundSettingsExamples
closeModal={() => {
setModalContent(null);
}}
populateEditorWithExample={populateEditorWithExample}
/>
) : (
<PlaygroundSettingsShaders />
);
Expand All @@ -64,6 +74,11 @@ export default function PlaygroundSettings({
subtitle="Check out available shaders and how to use them"
onClick={() => setModalContent('shaders')}
/>
<Card
title="Examples"
subtitle="Select and run one of the examples"
onClick={() => setModalContent('examples')}
/>
</div>
</div>

Expand Down
75 changes: 75 additions & 0 deletions docs/src/components/PlaygroundSettingsExamples.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { defaultJsonExample } from '@site/src/scene/defaultJsonExample';
import { videoCallExample } from '@site/src/scene/videoCallExample';
import { removeGreenScreenExample } from '@site/src/scene/removeGreenScreenExample';
import { Api } from 'live-compositor';

export default function PlaygroundSettingsExamples({
populateEditorWithExample,
closeModal,
}: {
populateEditorWithExample: (content: object | Error) => void;
closeModal: () => void;
}) {
return (
<div className="flex flex-nowrap flex-col p-[2px_4px_2px_4px] divide-y divide-x-0 divide-solid divide-[var(--ifm-color-emphasis-300)]">
<ExampleInfo
exampleName="Default example"
description="Showcase of various components and layout mechanism combined with custom shaders."
jsonExample={defaultJsonExample}
populateEditorWithExample={populateEditorWithExample}
closeModal={closeModal}
/>
<ExampleInfo
exampleName="Video call"
description="Grid layout commonly used to display participants of a video call. One tile has a border rendered with a shader. One tile renders an image instead of a stream."
jsonExample={videoCallExample}
populateEditorWithExample={populateEditorWithExample}
closeModal={closeModal}
/>
<ExampleInfo
exampleName="Greenscreen"
description="Image of a man (with shader replacing greenscreen) with live-compositor logo in the top-left corner."
jsonExample={removeGreenScreenExample}
populateEditorWithExample={populateEditorWithExample}
closeModal={closeModal}
/>
</div>
);
}

interface ExampleInfoProps {
exampleName: string;
description: string;
jsonExample: () => Api.Component;
populateEditorWithExample: (content: object | Error) => void;
closeModal: () => void;
}

function ExampleInfo({
exampleName,
description,
jsonExample,
populateEditorWithExample,
closeModal,
}: ExampleInfoProps) {
return (
<div>
<div className=" flex">
<div className="flex-[0_0_20%] flex font-semibold text-center py-3 min-w-5 justify-center items-center">
{exampleName}
</div>
<div className="flex-[0_0_70%] flex text-left py-3 px-5 items-center">{description}</div>
<div className="flex-[0_0_10%] flex py-3 justify-center items-center">
<button
className="rounded-md px-2 font-bold bg-[var(--docsearch-primary-color)] text-lg text-[var(--ifm-color-emphasis-0)] border-solid border border-[var(--ifm-color-primary)]"
onClick={() => {
populateEditorWithExample(jsonExample());
closeModal();
}}>
Run
</button>
</div>
</div>
</div>
);
}
43 changes: 38 additions & 5 deletions docs/src/pages/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ import {
InputsSettings,
Resolution,
} from '../resolution';
import { defaultJsonScene } from '@site/src/scene/defaultJson';
import { defaultJsonExample } from '@site/src/scene/defaultJsonExample';
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';

const INITIAL_SCENE = defaultJsonScene();
const STORED_CODE_EDITOR_CONTENT = ExecutionEnvironment.canUseDOM ? getStoredEditorContent() : null;

const INITIAL_SCENE =
STORED_CODE_EDITOR_CONTENT !== null
? JSON.parse(STORED_CODE_EDITOR_CONTENT)
: defaultJsonExample();

const INITIAL_REACT_CODE = [
"import React from 'react';\n",
Expand All @@ -33,8 +39,20 @@ const INITIAL_REACT_CODE = [
'console.log("Hello");',
].join('\n');

function getStoredEditorContent() {
const sessionStorageContent = sessionStorage.getItem('playgroundCodeEditorContent');
if (sessionStorageContent) {
return sessionStorageContent;
} else {
return localStorage.getItem('playgroundCodeEditorContent');
}
}

function Homepage() {
// Is updated when code editor content changes, but changes here are not passed back to the editor.
const [scene, setScene] = useState<object | Error>(INITIAL_SCENE);
// Code editor will be updated if this state changes.
const [codeEditorOverrideContent, setCodeEditorOverrideContent] = useState<object>(INITIAL_SCENE);
const [code, setCode] = useState<string>(INITIAL_REACT_CODE);
const [showReactEditor, setShowReactEditor] = useState<boolean>(false);
const [inputResolutions, setInputResolutions] = useState<InputsSettings>({
Expand All @@ -52,6 +70,7 @@ function Homepage() {
[inputId]: resolution,
});
}

const [outputResolution, setOutputResolution] = useState<Resolution>({
width: 1920,
height: 1080,
Expand All @@ -73,12 +92,13 @@ function Homepage() {

const handleSubmit = async (): Promise<void> => {
let loadingToastTimer;
let loadingToast;
try {
if (showReactEditor) {
await executeTypescriptCode(code);
} else {
loadingToastTimer = setTimeout(() => {
toast.loading('Rendering... It can take a while');
loadingToast = toast.loading('Rendering... It can take a while');
}, 3000);

setResponseData({ imageUrl: '', errorMessage: '', loading: true });
Expand All @@ -92,7 +112,7 @@ function Homepage() {
};
const blob = await renderImage({ ...request });
const imageObjectURL = URL.createObjectURL(blob);
toast.dismiss();
if (loadingToast) toast.dismiss(loadingToast);
clearTimeout(loadingToastTimer);

setResponseData({ imageUrl: imageObjectURL, errorMessage: '', loading: false });
Expand All @@ -118,13 +138,25 @@ function Homepage() {
setShowReactEditor(ifReactMode);
}, []);

function populateEditorWithExample(content: object) {
setScene(content);
setCodeEditorOverrideContent(content);
}

useEffect(() => {
handleSubmit();

Check failure on line 147 in docs/src/pages/playground.tsx

View workflow job for this annotation

GitHub Actions / check

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}, [codeEditorOverrideContent]);

return (
<div className="flex flex-row flex-wrap p-8 lg:h-[calc(100vh-110px)]">
<div className="flex-1 m-2 border-2 border-gray-400 border-solid rounded-md max-h-full min-w-[300px] min-h-[500px]">
{showReactEditor ? (
<PlaygroundReactEditor code={code} onCodeChange={setCode} />
) : (
<PlaygroundCodeEditor onChange={setScene} initialCodeEditorContent={INITIAL_SCENE} />
<PlaygroundCodeEditor
onChange={setScene}
codeEditorOverrideContent={codeEditorOverrideContent}
/>
)}
</div>
<div className="flex flex-col flex-1 max-h-full">
Expand All @@ -142,6 +174,7 @@ function Homepage() {
}}
inputsSettings={inputResolutions}
outputResolution={outputResolution}
populateEditorWithExample={populateEditorWithExample}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Api } from 'live-compositor';

export function defaultJsonScene(): Api.Component {
export function defaultJsonExample(): Api.Component {
return {
type: 'view',
background_color_rgba: '#4d4d4dff',
Expand Down
38 changes: 38 additions & 0 deletions docs/src/scene/removeGreenScreenExample.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Api } from 'live-compositor';

export function removeGreenScreenExample(): Api.Component {
return {
type: 'view',
background_color_rgba: '#ffc89ad9',
children: [
{
type: 'rescaler',
child: {
type: 'shader',
shader_id: 'remove_greenscreen',
children: [
{
type: 'image',
image_id: 'greenscreen',
},
],
resolution: {
width: 2160,
height: 2880,
},
},
},
{
type: 'rescaler',
top: 20,
left: 20,
width: 350,
height: 200,
child: {
type: 'image',
image_id: 'compositor_icon',
},
},
],
};
}
Loading

0 comments on commit 9de5b58

Please sign in to comment.