Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(demo): add demo of emoji-based game engine #440

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
94 changes: 71 additions & 23 deletions examples/kitchen-sink/app/host.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ const components = new Map([
['remote-fragment', RemoteFragmentRenderer],
]);

const sandboxes = {
iframe: iframeSandbox,
worker: workerSandbox,
};

// We offload most of the complex state logic to this `createState()` function. We’re
// just leaving the key bit in this file: when the example or sandbox changes, we render
// the example in the chosen sandbox. The `createState()` passes us a fresh `receiver`
Expand All @@ -59,29 +64,72 @@ const components = new Map([

const {receiver, example, sandbox} = createState(
async ({receiver, example, sandbox}) => {
if (sandbox === 'iframe') {
await iframeSandbox.imports.render(receiver.connection, {
sandbox,
example,
async alert(content) {
console.log(
`Alert API used by example ${example} in the iframe sandbox`,
);
window.alert(content);
},
});
} else {
await workerSandbox.imports.render(receiver.connection, {
sandbox,
example,
async alert(content) {
console.log(
`Alert API used by example ${example} in the worker sandbox`,
);
window.alert(content);
},
});
}
const sandboxImpl = sandboxes[sandbox];
await sandboxImpl.imports.render(receiver.connection, {
sandbox,
example,
async alert(content) {
console.log(
`Alert API used by example ${example} in the iframe sandbox`,
);
window.alert(content);
},
async enumerateDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices
.filter((d) => d.kind === 'videoinput')
.map((d) => d.toJSON());
},
async getUserMedia(
callback: (frame: ImageBitmap) => void | Promise<void>,
{deviceId}: {deviceId?: string} = {},
) {
const getStream = (deviceId?: string) =>
navigator.mediaDevices.getUserMedia({
audio: false,
video: {
deviceId,
aspectRatio: {ideal: 1},
facingMode: {ideal: 'user'},
},
});
let stream = await getStream(deviceId);
const video = document.createElement('video');
video.autoplay = true;
video.srcObject = stream;
async function frame() {
if (video.videoWidth) {
const size = Math.max(video.videoWidth, video.videoHeight);
const scale = 500 / size;
try {
await callback(
await createImageBitmap(video, {
resizeQuality: 'pixelated',
// resizeQuality: 'low',
resizeHeight: (video.videoHeight * scale) | 0,
resizeWidth: (video.videoWidth * scale) | 0,
}),
);
} catch (error) {
console.error(error);
}
}
requestAnimationFrame(frame);
}
frame();
return {
async switchDevice(deviceId: string) {
stream?.getVideoTracks()[0]?.stop();
stream = await getStream(deviceId);
video.srcObject = stream;
},
stop() {
stream?.getVideoTracks()[0]?.stop();
video.remove();
},
};
},
});
},
);

Expand Down
22 changes: 19 additions & 3 deletions examples/kitchen-sink/app/host/components.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {type ComponentChildren} from 'preact';
import {forwardRef} from 'preact/compat';
import {useRef, useImperativeHandle} from 'preact/hooks';
import {useRef, useImperativeHandle, useEffect} from 'preact/hooks';
import type {Signal} from '@preact/signals';

import type {
Expand All @@ -15,11 +15,18 @@ import type {

export function Text({
emphasis,
monospace,
children,
}: {children?: ComponentChildren} & TextProperties) {
return (
<span
class={['Text', emphasis && 'Text--emphasis'].filter(Boolean).join(' ')}
class={[
'Text',
emphasis && 'Text--emphasis',
monospace && 'Text--monospace',
]
.filter(Boolean)
.join(' ')}
>
{children}
</span>
Expand Down Expand Up @@ -68,8 +75,9 @@ export const Modal = forwardRef<
{
children?: ComponentChildren;
primaryAction?: ComponentChildren;
open?: boolean;
} & ModalProperties
>(function Modal({children, primaryAction, onClose}, ref) {
>(function Modal({children, primaryAction, open, onClose}, ref) {
const dialogRef = useRef<HTMLDialogElement>(null);

useImperativeHandle(ref, () => ({
Expand All @@ -81,6 +89,12 @@ export const Modal = forwardRef<
},
}));

useEffect(() => {
if (typeof open === 'boolean') {
dialogRef.current?.[open ? 'showModal' : 'close']();
}
}, [open]);

return (
<dialog ref={dialogRef} class="Modal" onClose={() => onClose?.()}>
<div class="Modal-Content">{children}</div>
Expand Down Expand Up @@ -159,6 +173,7 @@ export function ControlPanel({
<option value="svelte">Svelte</option>
<option value="vue">Vue</option>
<option value="htm">htm</option>
<option value="game">GAME</option>
</Select>
</section>

Expand All @@ -182,6 +197,7 @@ export function ControlPanel({
>
<option value="worker">Web Worker</option>
<option value="iframe">iFrame</option>
<option value="remote-ui-worker">Remote-UI Web Worker</option>
</Select>
</section>
</div>
Expand Down
1 change: 1 addition & 0 deletions examples/kitchen-sink/app/host/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ALLOWED_EXAMPLE_VALUES = new Set<RenderExample>([
'react',
'svelte',
'vue',
'game',
]);

export function createState(
Expand Down
1 change: 1 addition & 0 deletions examples/kitchen-sink/app/remote/elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
export const Text = createRemoteElement<TextProperties>({
properties: {
emphasis: {type: Boolean},
monospace: {type: Boolean},
},
});

Expand Down
Loading
Loading