Render a Game Boy player for GB/GBC attachments

This commit is contained in:
Alex Gleason
2023-11-22 22:20:19 -06:00
parent c08ac1eeff
commit 2e1282bc2d
4 changed files with 92 additions and 6 deletions

View File

@ -0,0 +1,47 @@
import React, { useEffect, useRef } from 'react';
// @ts-ignore No types available
import { WasmBoy } from 'wasmboy';
interface IGameboy extends React.CanvasHTMLAttributes<HTMLCanvasElement> {
/** URL to the ROM. */
src: string;
}
/** Component to display a playable Gameboy emulator. */
const Gameboy: React.FC<IGameboy> = ({ src, ...rest }) => {
const canvas = useRef<HTMLCanvasElement>(null);
async function init() {
await WasmBoy.config(WasmBoyOptions, canvas.current!);
await WasmBoy.loadROM(src);
await WasmBoy.play();
}
useEffect(() => {
init();
}, []);
return (
<canvas ref={canvas} {...rest} />
);
};
const WasmBoyOptions = {
headless: false,
useGbcWhenOptional: true,
isAudioEnabled: false,
frameSkip: 1,
audioBatchProcessing: true,
timersBatchProcessing: false,
audioAccumulateSamples: true,
graphicsBatchProcessing: false,
graphicsDisableScanlineRendering: false,
tileRendering: true,
tileCaching: true,
gameboyFPSCap: 60,
updateGraphicsCallback: false,
updateAudioCallback: false,
saveStateCallback: false,
};
export { Gameboy };

View File

@ -12,6 +12,8 @@ import { truncateFilename } from 'soapbox/utils/media';
import { isIOS } from '../is-mobile';
import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../utils/media-aspect-ratio';
import { Gameboy } from './gameboy';
import type { Property } from 'csstype';
import type { List as ImmutableList } from 'immutable';
@ -141,8 +143,22 @@ const Item: React.FC<IItem> = ({
}
let thumbnail: React.ReactNode = '';
const ext = attachment.url.split('.').pop()?.toLowerCase();
if (attachment.type === 'unknown') {
if (attachment.type === 'unknown' && ['gb', 'gbc'].includes(ext!)) {
return (
<div
className={clsx('media-gallery__item', {
standalone,
'rounded-md': total > 1,
})}
key={attachment.id}
style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}
>
<Gameboy className='media-gallery__item-thumbnail object-contain' src={attachment.url} />
</div>
);
} else if (attachment.type === 'unknown') {
const filename = truncateFilename(attachment.url, MAX_FILENAME_LENGTH);
const attachmentIcon = (
<Icon
@ -215,7 +231,6 @@ const Item: React.FC<IItem> = ({
</div>
);
} else if (attachment.type === 'audio') {
const ext = attachment.url.split('.').pop()?.toUpperCase();
thumbnail = (
<a
className={clsx('media-gallery__item-thumbnail')}
@ -225,11 +240,10 @@ const Item: React.FC<IItem> = ({
title={attachment.description}
>
<span className='media-gallery__item__icons'><Icon src={require('@tabler/icons/volume.svg')} /></span>
<span className='media-gallery__file-extension__label'>{ext}</span>
<span className='media-gallery__file-extension__label uppercase'>{ext}</span>
</a>
);
} else if (attachment.type === 'video') {
const ext = attachment.url.split('.').pop()?.toUpperCase();
thumbnail = (
<a
className={clsx('media-gallery__item-thumbnail')}
@ -246,7 +260,7 @@ const Item: React.FC<IItem> = ({
>
<source src={attachment.url} />
</video>
<span className='media-gallery__file-extension__label'>{ext}</span>
<span className='media-gallery__file-extension__label uppercase'>{ext}</span>
</a>
);
}