Skip to content

Instantly share code, notes, and snippets.

@bgrgicak
Created July 22, 2026 11:59
Show Gist options
  • Select an option

  • Save bgrgicak/ede998b6ac75705d5bef3834a3c211c4 to your computer and use it in GitHub Desktop.

Select an option

Save bgrgicak/ede998b6ac75705d5bef3834a3c211c4 to your computer and use it in GitHub Desktop.
WP Doom Block Playground blueprint mirror with local jsdos asset bootstrap
{
"$schema": "https://playground.wordpress.net/blueprint-schema.json",
"preferredVersions": {
"php": "8.3",
"wp": "latest"
},
"features": {
"networking": true
},
"landingPage": "/doom-block/",
"meta": {
"title": "Try WP Doom Block in Playground",
"author": "Bero / Gil",
"description": "Installs WP Doom Block, downloads the local Doom bundle asset, and loads a page with the block already inserted."
},
"steps": [
{
"step": "installPlugin",
"pluginData": {
"resource": "literal:directory",
"name": "wp-doom-block",
"files": {
"wp-doom-block.php": "<?php\n/**\n * Plugin Name: WP Doom Block\n * Description: Adds a Gutenberg block that embeds playable Doom in a post or page.\n * Version: 0.1.1\n * Requires at least: 6.5\n * Requires PHP: 8.0\n * Author: Gil / Hermes\n * License: GPL-2.0-or-later\n * License URI: https://www.gnu.org/licenses/gpl-2.0.html\n * Text Domain: wp-doom-block\n *\n * @package WPDoomBlock\n */\n\ndeclare(strict_types=1);\n\nif (! defined('ABSPATH')) {\n exit;\n}\n\nif (! defined('WP_DOOM_BLOCK_URL')) {\n define('WP_DOOM_BLOCK_URL', plugin_dir_url(__FILE__));\n}\n\nif (! defined('WP_DOOM_BLOCK_VERSION')) {\n define('WP_DOOM_BLOCK_VERSION', '0.1.1');\n}\n\nrequire_once __DIR__ . '/includes/render.php';\n\nfunction wp_doom_block_register(): void {\n $dir = __DIR__ . '/build';\n\n register_block_type(\n $dir,\n [\n 'render_callback' => 'wp_doom_block_render',\n ]\n );\n}\nadd_action('init', 'wp_doom_block_register');\n",
"README.md": "# WP Doom Block\n\nA WordPress plugin that adds a Gutenberg block for embedding playable DOOM in a post or page.\n\n## Try it in WordPress Playground\n\n> ## [Launch the WP Doom Block demo in WordPress Playground](https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fgist.githubusercontent.com%2Fbgrgicak%2Fa271c24c9d8a9e3eaa229335bac92cb4%2Fraw%2Fb908e69c62ed5bdf3550669987ba0bfd5309e936%2Ftry-wp-doom-block-in-playground.json)\n>\n> The source blueprint lives in `blueprints/try-wp-doom-block-in-playground.json` inside this repo.\n>\n> The repo is currently private, so the link above uses a tested public mirror of that blueprint for Playground to fetch.\n\n## Blueprints\n\n- `blueprints/try-wp-doom-block-in-playground.json` \u2014 self-contained demo blueprint for browser Playground links. It installs the plugin and opens a page with the DOOM block already inserted.\n- `blueprints/use-mounted-local-plugin-in-playground-cli.json` \u2014 local development blueprint for Playground CLI. It assumes the plugin is mounted from your working tree, activates it, and seeds the demo page content.\n\n## Development with WordPress Playground CLI\n\nUse the local-development blueprint when you want Playground to run your **mounted local plugin**, not the self-contained browser demo copy.\n\n### Prerequisites\n\n- Node.js 20.18+\n- `npm` / `npx`\n\n### Start a local Playground with the mounted plugin\n\nRun this from the plugin root:\n\n```bash\ncd /path/to/wp-doom-block\nnode -v\nnpx @wp-playground/cli@latest server \\\n --port=9400 \\\n --mount-before-install=\"$(pwd):/wordpress/wp-content/plugins/wp-doom-block\" \\\n --blueprint=./blueprints/use-mounted-local-plugin-in-playground-cli.json\n```\n\nWhy `--mount-before-install` matters:\n\n- the blueprint activates `wp-doom-block/wp-doom-block.php`\n- the plugin must already exist inside `/wordpress/wp-content/plugins/wp-doom-block`\n- mounting before install makes the plugin available when the blueprint runs\n\nOnce Playground boots, it should open `/doom-block/` with the DOOM block already inserted on the page.\n\n### Useful resets\n\nIf you want a fresh Playground state, rerun the same command. If you hit CLI issues, check the latest help output:\n\n```bash\nnpx @wp-playground/cli@latest server --help\nnpx @wp-playground/cli@latest run-blueprint --help\n```\n\n## Inspiration\n\nThis block was inspired by Rhys Wynne\u2019s work on bringing DOOM into WordPress and WordPress Playground.\n\n- Author: Rhys Wynne\n- Blog post: [Can WordPress run Doom? (of course it can, here's how)](https://dwinrhys.com/2024/05/08/can-wordpress-run-doom-of-course-it-can-heres-how/)\n- Related original plugin/repo: [rhyswynne/wpdoom](https://github.com/rhyswynne/wpdoom)\n\nThis plugin is a separate Gutenberg block implementation and does not reuse the original shortcode-based plugin architecture.\n\n## What this plugin does\n\n- Registers a Gutenberg block named `create-block/wp-doom-block`\n- Renders a start button that lazy-loads a DOOM iframe on the front end\n- Lets the editor configure the game URL, frame height, and button text\n\n## Local structure\n\n- `wp-doom-block.php` \u2014 plugin bootstrap and block registration\n- `includes/render.php` \u2014 server-side render helpers\n- `build/` \u2014 block metadata and compiled assets\n- `blueprints/` \u2014 Playground blueprints for browser demos and local CLI development\n- `tests/run.php` \u2014 lightweight verification script\n\n## Verification\n\nRun:\n\n```bash\nphp tests/run.php\n```\n\nExpected output:\n\n```text\nAll tests passed\n```\n",
"includes": {
"render.php": "<?php\n\ndeclare(strict_types=1);\n\nfunction wp_doom_block_default_game_url(): string {\n if (defined('WP_DOOM_BLOCK_URL')) {\n return WP_DOOM_BLOCK_URL . 'assets/doom.jsdos';\n }\n\n return 'https://cdn.dos.zone/custom/dos/doom.jsdos';\n}\n\nfunction wp_doom_block_is_bundle_url(string $value): bool {\n $path = parse_url($value, PHP_URL_PATH);\n\n return is_string($path) && str_ends_with(strtolower($path), '.jsdos');\n}\n\nfunction wp_doom_block_escape_url(string $value): string {\n return function_exists('esc_url') ? esc_url($value) : htmlspecialchars($value, ENT_QUOTES, 'UTF-8');\n}\n\nfunction wp_doom_block_escape_attr(string $value): string {\n return function_exists('esc_attr') ? esc_attr($value) : htmlspecialchars($value, ENT_QUOTES, 'UTF-8');\n}\n\nfunction wp_doom_block_escape_html(string $value): string {\n return function_exists('esc_html') ? esc_html($value) : htmlspecialchars($value, ENT_QUOTES, 'UTF-8');\n}\n\nfunction wp_doom_block_render_markup(array $attributes, string $wrapperAttributes = ''): string {\n $defaults = [\n 'gameUrl' => wp_doom_block_default_game_url(),\n 'height' => 600,\n 'startButtonText' => 'Launch DOOM',\n ];\n\n $attributes = array_merge($defaults, $attributes);\n\n if (! is_string($attributes['gameUrl']) || trim((string) $attributes['gameUrl']) === '') {\n $attributes['gameUrl'] = wp_doom_block_default_game_url();\n }\n\n $height = is_numeric($attributes['height']) ? max(240, (int) $attributes['height']) : 600;\n $gameUrl = wp_doom_block_escape_url((string) $attributes['gameUrl']);\n $buttonText = wp_doom_block_escape_html((string) $attributes['startButtonText']);\n $mode = wp_doom_block_is_bundle_url((string) $attributes['gameUrl']) ? 'bundle' : 'iframe';\n $baseWidth = 800;\n $statusText = $mode === 'bundle'\n ? 'Ready to launch. On touch devices, the DOOM mobile control overlay appears after the game starts.'\n : 'Ready to launch. This classic iframe mode works best with a keyboard and mouse.';\n $hintPrimary = $mode === 'bundle'\n ? 'Touch support: built-in on mobile-friendly .jsdos bundles.'\n : 'Touch support is limited in classic iframe mode.';\n $hintSecondary = 'Tip: go fullscreen for the cleanest HUD and easier aiming.';\n\n $wrapperAttributes = trim($wrapperAttributes);\n if ($wrapperAttributes === '') {\n $wrapperAttributes = 'class=\"wp-block-create-block-wp-doom-block wp-doom-block\"';\n }\n\n return sprintf(\n '<div %1$s data-doom-root data-doom-mode=\"%2$s\" data-game-url=\"%3$s\">'\n . '<div class=\"wp-doom-block__shell\" style=\"--wp-doom-block-height:%4$dpx\" data-doom-shell>'\n . '<div class=\"wp-doom-block__panel\">'\n . '<div class=\"wp-doom-block__header\">'\n . '<p class=\"wp-doom-block__eyebrow\">Classic FPS \u00b7 WordPress block</p>'\n . '<h2 class=\"wp-doom-block__title\">DOOM</h2>'\n . '<p class=\"wp-doom-block__lede\">Drop a clean, playable DOOM experience into any post or page.</p>'\n . '</div>'\n . '<div class=\"wp-doom-block__actions\">'\n . '<button type=\"button\" class=\"wp-doom-block__start-button\" data-doom-start data-doom-default-text=\"%5$s\">%5$s</button>'\n . '<button type=\"button\" class=\"wp-doom-block__secondary-button\" data-doom-fullscreen hidden disabled>Fullscreen</button>'\n . '</div>'\n . '<p class=\"wp-doom-block__status\" data-doom-status>%6$s</p>'\n . '<div class=\"wp-doom-block__tips\">'\n . '<span class=\"wp-doom-block__tip\">%7$s</span>'\n . '<span class=\"wp-doom-block__tip\">%8$s</span>'\n . '</div>'\n . '</div>'\n . '<div class=\"wp-doom-block__viewport\" data-doom-viewport hidden>'\n . '<div class=\"wp-doom-block__game\" data-doom-game hidden></div>'\n . '<iframe class=\"wp-doom-block__iframe\" title=\"DOOM\" loading=\"lazy\" allowfullscreen data-doom-frame data-src=\"%3$s\" data-base-width=\"%9$d\" data-base-height=\"%4$d\" hidden></iframe>'\n . '</div>'\n . '</div>'\n . '</div>',\n $wrapperAttributes,\n wp_doom_block_escape_attr($mode),\n $gameUrl,\n $height,\n $buttonText,\n wp_doom_block_escape_html($statusText),\n wp_doom_block_escape_html($hintPrimary),\n wp_doom_block_escape_html($hintSecondary),\n $baseWidth\n );\n}\n\nfunction wp_doom_block_render(array $attributes, string $content = '', $block = null): string {\n $wrapperAttributes = function_exists('get_block_wrapper_attributes')\n ? get_block_wrapper_attributes([\n 'class' => 'wp-doom-block',\n ])\n : 'class=\"wp-block-create-block-wp-doom-block wp-doom-block\"';\n\n return wp_doom_block_render_markup($attributes, $wrapperAttributes);\n}\n"
},
"build": {
"block.json": "{\n \"$schema\": \"https://schemas.wp.org/trunk/block.json\",\n \"apiVersion\": 3,\n \"name\": \"create-block/wp-doom-block\",\n \"version\": \"0.1.1\",\n \"title\": \"DOOM\",\n \"category\": \"widgets\",\n \"icon\": \"games\",\n \"description\": \"Embed playable DOOM in a post or page.\",\n \"keywords\": [\"doom\", \"game\", \"retro\", \"embed\"],\n \"textdomain\": \"wp-doom-block\",\n \"attributes\": {\n \"gameUrl\": {\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"height\": {\n \"type\": \"number\",\n \"default\": 600\n },\n \"startButtonText\": {\n \"type\": \"string\",\n \"default\": \"Launch DOOM\"\n }\n },\n \"editorScript\": \"file:./index.js\",\n \"viewScript\": \"file:./view.js\",\n \"style\": \"file:./style.css\"\n}\n",
"index.asset.php": "<?php\nreturn [\n 'dependencies' => [\n 'wp-blocks',\n 'wp-block-editor',\n 'wp-components',\n 'wp-element',\n 'wp-i18n',\n ],\n 'version' => '0.1.0',\n];\n",
"index.js": "( function ( blocks, blockEditor, components, element, i18n ) {\n var el = element.createElement;\n var useBlockProps = blockEditor.useBlockProps;\n var InspectorControls = blockEditor.InspectorControls;\n var PanelBody = components.PanelBody;\n var RangeControl = components.RangeControl;\n var TextControl = components.TextControl;\n var __ = i18n.__;\n\n blocks.registerBlockType( 'create-block/wp-doom-block', {\n edit: function ( props ) {\n var attributes = props.attributes;\n var setAttributes = props.setAttributes;\n var blockProps = useBlockProps( {\n className: 'wp-doom-block-editor',\n } );\n var gameUrl = attributes.gameUrl || '';\n var displaySource = gameUrl || 'Bundled DOOM asset (assets/doom.jsdos)';\n var buttonText = attributes.startButtonText || 'Launch DOOM';\n var modeLabel = gameUrl && !/\\.jsdos($|\\?)/.test( gameUrl )\n ? __( 'Classic iframe mode for legacy external pages', 'wp-doom-block' )\n : __( 'Enhanced local player mode with mobile-friendly controls', 'wp-doom-block' );\n\n return [\n el(\n InspectorControls,\n { key: 'inspector' },\n el(\n PanelBody,\n { title: __( 'DOOM settings', 'wp-doom-block' ), initialOpen: true },\n el( TextControl, {\n label: __( 'Game bundle or page URL', 'wp-doom-block' ),\n value: gameUrl,\n onChange: function ( value ) {\n setAttributes( { gameUrl: value } );\n },\n help: __( 'Leave this blank to use the bundled local DOOM asset. Use a .jsdos bundle URL for the enhanced player, or a classic external page URL to keep iframe mode.', 'wp-doom-block' ),\n } ),\n el( RangeControl, {\n label: __( 'Frame height', 'wp-doom-block' ),\n value: attributes.height || 600,\n onChange: function ( value ) {\n setAttributes( { height: value } );\n },\n min: 240,\n max: 1000,\n } ),\n el( TextControl, {\n label: __( 'Start button text', 'wp-doom-block' ),\n value: buttonText,\n onChange: function ( value ) {\n setAttributes( { startButtonText: value } );\n },\n } )\n )\n ),\n el(\n 'div',\n Object.assign( { key: 'preview' }, blockProps ),\n el(\n 'div',\n { className: 'wp-doom-block__shell', style: { '--wp-doom-block-height': ( attributes.height || 600 ) + 'px' } },\n el(\n 'div',\n { className: 'wp-doom-block__panel' },\n el(\n 'div',\n { className: 'wp-doom-block__header' },\n el( 'p', { className: 'wp-doom-block__eyebrow' }, __( 'Classic FPS \u00b7 WordPress block', 'wp-doom-block' ) ),\n el( 'h2', { className: 'wp-doom-block__title' }, 'DOOM' ),\n el( 'p', { className: 'wp-doom-block__lede' }, __( 'Front-end playback is loaded on demand so the page stays clean until someone launches the game.', 'wp-doom-block' ) )\n ),\n el(\n 'div',\n { className: 'wp-doom-block__actions' },\n el( 'button', { type: 'button', className: 'wp-doom-block__start-button', disabled: true }, buttonText ),\n el( 'button', { type: 'button', className: 'wp-doom-block__secondary-button', disabled: true }, __( 'Fullscreen', 'wp-doom-block' ) )\n ),\n el( 'p', { className: 'wp-doom-block__status' }, modeLabel ),\n el(\n 'div',\n { className: 'wp-doom-block__tips' },\n el( 'span', { className: 'wp-doom-block__tip' }, __( 'Recommended: use the bundled .jsdos mode for touch-friendly play.', 'wp-doom-block' ) ),\n el( 'span', { className: 'wp-doom-block__tip' }, __( 'Source: ', 'wp-doom-block' ) + displaySource )\n )\n )\n )\n )\n ];\n },\n save: function () {\n return null;\n },\n } );\n} )( window.wp.blocks, window.wp.blockEditor, window.wp.components, window.wp.element, window.wp.i18n );\n",
"style.css": ".wp-block-create-block-wp-doom-block,\n.wp-doom-block-editor {\n box-sizing: border-box;\n width: 100%;\n max-width: 100%;\n}\n\n.wp-block-create-block-wp-doom-block *,\n.wp-doom-block-editor * {\n box-sizing: border-box;\n}\n\n.wp-doom-block__shell {\n display: flex;\n width: 100%;\n max-width: 100%;\n min-height: var(--wp-doom-block-height, 600px);\n flex-direction: column;\n gap: 1rem;\n padding: 1rem;\n border-radius: 20px;\n overflow: hidden;\n background:\n radial-gradient(circle at top, rgba(255, 132, 0, 0.18), transparent 28%),\n linear-gradient(180deg, #22140b 0%, #120b08 45%, #060606 100%);\n color: #f7f0d0;\n box-shadow:\n inset 0 0 0 1px rgba(255, 255, 255, 0.08),\n 0 20px 48px rgba(0, 0, 0, 0.28);\n}\n\n.wp-doom-block__panel {\n display: grid;\n gap: 1rem;\n align-items: start;\n}\n\n.wp-doom-block__header {\n display: grid;\n gap: 0.4rem;\n}\n\n.wp-doom-block__eyebrow {\n margin: 0;\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: #ffb55c;\n}\n\n.wp-doom-block__title {\n margin: 0;\n font-size: clamp(1.8rem, 4vw, 3rem);\n line-height: 0.95;\n letter-spacing: 0.04em;\n}\n\n.wp-doom-block__lede {\n max-width: 48rem;\n margin: 0;\n color: rgba(247, 240, 208, 0.8);\n}\n\n.wp-doom-block__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 0.75rem;\n}\n\n.wp-doom-block__start-button,\n.wp-doom-block__secondary-button {\n appearance: none;\n border: 0;\n border-radius: 999px;\n padding: 0.9rem 1.35rem;\n font-weight: 700;\n cursor: pointer;\n transition: transform 120ms ease, opacity 120ms ease, background-color 120ms ease;\n}\n\n.wp-doom-block__start-button:hover,\n.wp-doom-block__secondary-button:hover {\n transform: translateY(-1px);\n}\n\n.wp-doom-block__start-button {\n background: linear-gradient(180deg, #ff7b2f 0%, #c93c00 100%);\n color: #fff;\n box-shadow: 0 12px 24px rgba(201, 60, 0, 0.24);\n}\n\n.wp-doom-block__secondary-button {\n background: rgba(255, 255, 255, 0.08);\n color: #f7f0d0;\n}\n\n.wp-doom-block__start-button[disabled],\n.wp-doom-block__secondary-button[disabled] {\n cursor: default;\n opacity: 0.65;\n transform: none;\n}\n\n.wp-doom-block__status {\n margin: 0;\n color: rgba(247, 240, 208, 0.82);\n}\n\n.wp-doom-block__tips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.wp-doom-block__tip {\n display: inline-flex;\n align-items: center;\n min-height: 2rem;\n padding: 0.45rem 0.75rem;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.06);\n color: rgba(247, 240, 208, 0.9);\n font-size: 0.9rem;\n}\n\n.wp-doom-block__viewport {\n width: 100%;\n max-width: 100%;\n min-height: min(var(--wp-doom-block-height, 600px), 72vh);\n overflow: hidden;\n border-radius: 16px;\n background: #000;\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);\n}\n\n.wp-doom-block__game,\n.wp-doom-block__iframe {\n width: 100%;\n max-width: 100%;\n height: min(var(--wp-doom-block-height, 600px), 72vh);\n min-height: 240px;\n background: #000;\n}\n\n.wp-doom-block__game {\n position: relative;\n overflow: hidden;\n}\n\n.wp-doom-block__game.jsdos-rso,\n.wp-doom-block__game .jsdos-rso,\n.wp-doom-block__game [data-theme],\n.wp-doom-block__game .window {\n width: 100%;\n height: 100%;\n}\n\n.wp-doom-block__game canvas {\n display: block;\n max-width: 100%;\n}\n\n.wp-doom-block__iframe {\n display: block;\n width: 800px;\n max-width: none;\n height: var(--wp-doom-block-height, 600px);\n border: 0;\n transform-origin: top left;\n}\n\n.wp-doom-block__shell.is-active .wp-doom-block__panel {\n gap: 0.85rem;\n}\n\n@media (max-width: 782px) {\n .wp-doom-block__shell {\n padding: 0.85rem;\n border-radius: 16px;\n }\n\n .wp-doom-block__actions {\n flex-direction: column;\n }\n\n .wp-doom-block__start-button,\n .wp-doom-block__secondary-button {\n width: 100%;\n justify-content: center;\n }\n\n .wp-doom-block__tips {\n display: grid;\n }\n}\n",
"view.asset.php": "<?php\nreturn [\n 'dependencies' => [],\n 'version' => '0.1.0',\n];\n",
"view.js": "var wpDoomBlockAssetsPromise;\n\nfunction wpDoomBlockIsTouchDevice() {\n return window.matchMedia('(pointer: coarse)').matches || navigator.maxTouchPoints > 0;\n}\n\nfunction wpDoomBlockSetStatus(shell, message) {\n var status = shell ? shell.querySelector('[data-doom-status]') : null;\n if (status) {\n status.textContent = message;\n }\n}\n\nfunction wpDoomBlockLoadStylesheet(url) {\n return new Promise(function (resolve, reject) {\n var existing = document.querySelector('link[href=\"' + url + '\"]');\n if (existing) {\n resolve();\n return;\n }\n\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = url;\n link.onload = function () {\n resolve();\n };\n link.onerror = function () {\n reject(new Error('Could not load ' + url));\n };\n document.head.appendChild(link);\n });\n}\n\nfunction wpDoomBlockLoadScript(url) {\n return new Promise(function (resolve, reject) {\n if (window.Dos) {\n resolve(window.Dos);\n return;\n }\n\n var existing = document.querySelector('script[src=\"' + url + '\"]');\n if (existing) {\n existing.addEventListener('load', function () {\n resolve(window.Dos);\n }, { once: true });\n existing.addEventListener('error', function () {\n reject(new Error('Could not load ' + url));\n }, { once: true });\n return;\n }\n\n var script = document.createElement('script');\n script.src = url;\n script.onload = function () {\n if (window.Dos) {\n resolve(window.Dos);\n return;\n }\n\n reject(new Error('js-dos loaded, but Dos() is unavailable'));\n };\n script.onerror = function () {\n reject(new Error('Could not load ' + url));\n };\n document.head.appendChild(script);\n });\n}\n\nfunction wpDoomBlockLoadAssets() {\n if (!wpDoomBlockAssetsPromise) {\n wpDoomBlockAssetsPromise = Promise.all([\n wpDoomBlockLoadStylesheet('https://br.cdn.dos.zone/js-dos/latest/js-dos.css'),\n wpDoomBlockLoadScript('https://br.cdn.dos.zone/js-dos/latest/js-dos.js.ea'),\n ]).then(function () {\n return window.Dos;\n });\n }\n\n return wpDoomBlockAssetsPromise;\n}\n\nfunction wpDoomBlockRequestFullscreen(shell) {\n if (!shell) {\n return;\n }\n\n var viewport = shell.querySelector('[data-doom-viewport]');\n if (!viewport) {\n return;\n }\n\n if (viewport.requestFullscreen) {\n viewport.requestFullscreen().catch(function () {\n wpDoomBlockSetStatus(shell, 'Fullscreen was blocked by the browser.');\n });\n }\n}\n\nfunction wpDoomBlockResizeIframe(shell) {\n if (!shell) {\n return;\n }\n\n var viewport = shell.querySelector('[data-doom-viewport]');\n var iframe = shell.querySelector('[data-doom-frame]');\n if (!viewport || !iframe) {\n return;\n }\n\n var baseWidth = parseInt(iframe.dataset.baseWidth || '800', 10);\n var baseHeight = parseInt(iframe.dataset.baseHeight || '600', 10);\n var availableWidth = viewport.clientWidth;\n\n if (!availableWidth || !baseWidth || !baseHeight) {\n return;\n }\n\n var scale = Math.min(1, availableWidth / baseWidth);\n\n iframe.style.width = baseWidth + 'px';\n iframe.style.height = baseHeight + 'px';\n iframe.style.transform = 'scale(' + scale + ')';\n viewport.style.height = baseHeight * scale + 'px';\n}\n\nfunction wpDoomBlockResize(shell) {\n if (!shell) {\n return;\n }\n\n var root = shell.closest('[data-doom-root]');\n if (!root) {\n return;\n }\n\n if ((root.dataset.doomMode || 'iframe') === 'iframe') {\n wpDoomBlockResizeIframe(shell);\n }\n}\n\nfunction wpDoomBlockBind(shell) {\n if (!shell || shell.dataset.doomBound === 'true') {\n return;\n }\n\n shell.dataset.doomBound = 'true';\n\n if (typeof ResizeObserver !== 'undefined') {\n var observer = new ResizeObserver(function () {\n wpDoomBlockResize(shell);\n });\n observer.observe(shell);\n } else {\n window.addEventListener('resize', function () {\n wpDoomBlockResize(shell);\n });\n }\n\n var fullscreen = shell.querySelector('[data-doom-fullscreen]');\n if (fullscreen) {\n fullscreen.addEventListener('click', function () {\n wpDoomBlockRequestFullscreen(shell);\n });\n }\n\n wpDoomBlockResize(shell);\n}\n\nfunction wpDoomBlockStartBundle(shell, button) {\n var root = shell.closest('[data-doom-root]');\n var viewport = shell.querySelector('[data-doom-viewport]');\n var game = shell.querySelector('[data-doom-game]');\n var fullscreen = shell.querySelector('[data-doom-fullscreen]');\n var defaultText = button.dataset.doomDefaultText || button.textContent;\n\n if (!root || !viewport || !game) {\n return;\n }\n\n if (shell.dataset.doomStarted === 'true') {\n viewport.hidden = false;\n game.hidden = false;\n if (fullscreen) {\n fullscreen.hidden = false;\n }\n return;\n }\n\n button.disabled = true;\n button.textContent = 'Loading DOOM\u2026';\n viewport.hidden = false;\n game.hidden = false;\n shell.classList.add('is-loading');\n wpDoomBlockSetStatus(shell, 'Loading the built-in js-dos player\u2026');\n\n var shellHeight = parseInt(window.getComputedStyle(shell).getPropertyValue('--wp-doom-block-height') || '600', 10);\n var availableWidth = viewport.clientWidth || game.clientWidth || shell.clientWidth || 800;\n var targetHeight = Math.max(240, Math.min(shellHeight || 600, Math.round(availableWidth * 0.75)));\n viewport.style.height = targetHeight + 'px';\n game.style.width = '100%';\n game.style.height = targetHeight + 'px';\n game.style.minHeight = targetHeight + 'px';\n\n Promise.resolve().then(function () {\n return new Promise(function (resolve) {\n window.requestAnimationFrame(function () {\n window.requestAnimationFrame(resolve);\n });\n });\n }).then(function () {\n return wpDoomBlockLoadAssets();\n }).then(function (Dos) {\n return Dos(game, {\n url: root.dataset.gameUrl,\n autoStart: true,\n kiosk: true,\n theme: 'dark',\n renderBackend: 'canvas',\n });\n }).then(function () {\n shell.dataset.doomStarted = 'true';\n shell.classList.remove('is-loading');\n shell.classList.add('is-active', 'is-bundle-active');\n button.hidden = true;\n button.disabled = false;\n button.textContent = defaultText;\n\n if (fullscreen) {\n fullscreen.hidden = false;\n fullscreen.disabled = false;\n }\n\n window.dispatchEvent(new Event('resize'));\n\n if (wpDoomBlockIsTouchDevice()) {\n wpDoomBlockSetStatus(shell, 'Touch controls are active. Use the on-screen pad and action buttons to move, fire, open doors, and switch actions.');\n } else {\n wpDoomBlockSetStatus(shell, 'DOOM is live. Click the game view to capture the mouse, press Esc to release it, and use Fullscreen for the cleanest HUD.');\n }\n }).catch(function (error) {\n shell.classList.remove('is-loading');\n viewport.hidden = true;\n game.hidden = true;\n button.disabled = false;\n button.textContent = defaultText;\n wpDoomBlockSetStatus(shell, 'Could not load the built-in player: ' + error.message);\n console.error(error);\n });\n}\n\nfunction wpDoomBlockStartIframe(shell, button) {\n var viewport = shell.querySelector('[data-doom-viewport]');\n var iframe = shell.querySelector('[data-doom-frame]');\n var fullscreen = shell.querySelector('[data-doom-fullscreen]');\n\n if (!viewport || !iframe) {\n return;\n }\n\n if (!iframe.getAttribute('src')) {\n iframe.setAttribute('src', iframe.dataset.src || 'https://js-dos.com/games/doom.exe.html');\n }\n\n viewport.hidden = false;\n iframe.hidden = false;\n button.hidden = true;\n shell.classList.add('is-active', 'is-iframe-active');\n\n if (fullscreen) {\n fullscreen.hidden = false;\n fullscreen.disabled = false;\n }\n\n wpDoomBlockSetStatus(shell, 'Classic mode is live. Keyboard and mouse work best here.');\n wpDoomBlockResize(shell);\n}\n\ndocument.querySelectorAll('[data-doom-shell]').forEach(wpDoomBlockBind);\n\ndocument.addEventListener('click', function (event) {\n var button = event.target.closest('[data-doom-start]');\n if (!button) {\n return;\n }\n\n var shell = button.closest('[data-doom-shell]');\n var root = button.closest('[data-doom-root]');\n if (!shell || !root) {\n return;\n }\n\n if ((root.dataset.doomMode || 'iframe') === 'bundle') {\n wpDoomBlockStartBundle(shell, button);\n return;\n }\n\n wpDoomBlockStartIframe(shell, button);\n} );\n"
},
"tests": {
"run.php": "<?php\n\ndeclare(strict_types=1);\n\nfunction assert_true(bool $condition, string $message): void {\n if (! $condition) {\n fwrite(STDERR, \"FAIL: {$message}\\n\");\n exit(1);\n }\n}\n\n$root = dirname(__DIR__);\n$pluginFile = $root . '/wp-doom-block.php';\n$blockJson = $root . '/build/block.json';\n$renderFile = $root . '/includes/render.php';\n\nassert_true(file_exists($pluginFile), 'Expected main plugin file to exist');\nassert_true(file_exists($blockJson), 'Expected block.json to exist');\nassert_true(file_exists($renderFile), 'Expected render helper to exist');\n\nrequire_once $renderFile;\n\nassert_true(function_exists('wp_doom_block_render_markup'), 'Expected render helper function to exist');\nassert_true(function_exists('wp_doom_block_default_game_url'), 'Expected default game URL helper to exist');\n\n$html = wp_doom_block_render_markup([]);\n\nassert_true(str_contains($html, 'data-doom-root'), 'Expected rendered markup to include the DOOM root data attribute');\nassert_true(str_contains($html, 'data-doom-mode=\"bundle\"'), 'Expected rendered markup to default to bundle mode');\nassert_true(str_contains($html, 'assets/doom.jsdos') || str_contains($html, 'cdn.dos.zone/custom/dos/doom.jsdos'), 'Expected rendered markup to point to the Doom bundle source');\nassert_true(str_contains($html, 'data-doom-game'), 'Expected rendered markup to include the js-dos player container');\nassert_true(str_contains($html, 'data-doom-viewport'), 'Expected rendered markup to include the responsive viewport wrapper');\nassert_true(str_contains($html, 'data-doom-fullscreen'), 'Expected rendered markup to include the fullscreen control');\nassert_true(str_contains($html, 'data-base-width=\"800\"'), 'Expected rendered markup to include the base iframe width for responsive scaling');\n\nprint \"All tests passed\\n\";\n"
}
}
},
"options": {
"activate": true
},
"ifAlreadyInstalled": "overwrite"
},
{
"step": "runPHP",
"code": "<?php\nrequire '/wordpress/wp-load.php';\n\n$plugin_dir = '/wordpress/wp-content/plugins/wp-doom-block';\n$asset_dir = $plugin_dir . '/assets';\n$asset_file = $asset_dir . '/doom.jsdos';\n$asset_url = 'https://cdn.dos.zone/custom/dos/doom.jsdos';\n\nif ( ! is_dir( $asset_dir ) && ! mkdir( $asset_dir, 0777, true ) && ! is_dir( $asset_dir ) ) {\n throw new Exception( 'Could not create asset directory.' );\n}\n\nif ( ! file_exists( $asset_file ) ) {\n $asset = @file_get_contents( $asset_url );\n if ( false === $asset ) {\n throw new Exception( 'Could not download doom.jsdos asset.' );\n }\n\n if ( false === file_put_contents( $asset_file, $asset ) ) {\n throw new Exception( 'Could not write doom.jsdos asset.' );\n }\n}\n\nupdate_option( 'blogname', 'WP Doom Block Demo' );\nupdate_option( 'blogdescription', 'Playable DOOM inside a Gutenberg block' );\nupdate_option( 'permalink_structure', '/%postname%/' );\n\n$existing = get_page_by_path( 'doom-block', OBJECT, 'page' );\nif ( $existing ) {\n wp_delete_post( $existing->ID, true );\n}\n\n$page_id = wp_insert_post( [\n 'post_title' => 'DOOM Block Demo',\n 'post_name' => 'doom-block',\n 'post_status' => 'publish',\n 'post_type' => 'page',\n 'post_content' => '<!-- wp:create-block/wp-doom-block {\"height\":600} /-->',\n] );\n\nif ( is_wp_error( $page_id ) ) {\n throw new Exception( $page_id->get_error_message() );\n}\n\nupdate_option( 'show_on_front', 'page' );\nupdate_option( 'page_on_front', (int) $page_id );\nflush_rewrite_rules();\n"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment