Created
July 22, 2026 11:59
-
-
Save bgrgicak/ede998b6ac75705d5bef3834a3c211c4 to your computer and use it in GitHub Desktop.
WP Doom Block Playground blueprint mirror with local jsdos asset bootstrap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "$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