Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
44b712c
Blueprint bundle editor
adamziel Nov 25, 2025
f09a1ed
Support recreating the temporary Playground from the edited Blueprint…
adamziel Nov 26, 2025
0a259c8
Store the edited Blueprint bundle in OPFS
adamziel Nov 26, 2025
8a639aa
Tweak UX
adamziel Nov 26, 2025
de36471
Consistent icons UI
adamziel Nov 26, 2025
ae4b685
Display filename in the preview area even for binary files
adamziel Nov 26, 2025
19073f8
Add play icon to the recreate button
adamziel Nov 26, 2025
3f46226
Reuse BinaryFilePreview component
adamziel Nov 26, 2025
326688d
Move the bundle execution logic into the editor component
adamziel Nov 26, 2025
75a21b0
remove save-state.ts
adamziel Nov 26, 2025
d097add
lint
adamziel Nov 26, 2025
93e81e6
lint
adamziel Nov 26, 2025
cd60e91
lint
adamziel Nov 26, 2025
f36d676
Offer to restore the last autosaved bundle
adamziel Nov 27, 2025
64d9409
Clean up the blueprint bundle editor
adamziel Nov 27, 2025
66361f3
lint
adamziel Nov 27, 2025
6839c13
Export writable filesystem types from the in memory class
adamziel Nov 27, 2025
a9ba0be
Add missing file
adamziel Nov 27, 2025
12f4ab5
Simplify writable filesystem implementations
adamziel Nov 28, 2025
67829bb
Reorganize the blueprint editor code
adamziel Nov 28, 2025
c801d39
Split the bundle editor component in two files
adamziel Nov 28, 2025
0481948
Simplify the code further
adamziel Nov 28, 2025
78c92ca
Inline filesystem creation logic
adamziel Nov 28, 2025
b51304b
Use refs to avoid stale onChange callback
adamziel Nov 28, 2025
475612a
Adjust the E2E test
adamziel Nov 28, 2025
02c04b7
Merge branch 'trunk' into blueprint-bundle-editor-website
adamziel Nov 28, 2025
3b8eaed
Undo dev changes
adamziel Nov 28, 2025
303960b
Lint
adamziel Nov 28, 2025
ae30ad6
Lint
adamziel Nov 28, 2025
5319963
Fix InMemoryFilesystem to extend EventTarget
adamziel Nov 28, 2025
21ca979
Move Download/Run buttons to file path bar in Blueprint editor
adamziel Nov 28, 2025
e4cdb8f
Cleanup blueprint editor initialization logic
adamziel Nov 28, 2025
5148e72
Cleanup blueprint editor initialization logic
adamziel Nov 28, 2025
8ba1bdb
Only create an autosave after the first change
adamziel Nov 28, 2025
b58d755
Remember the answer to "should restore from autosave" prompt
adamziel Nov 28, 2025
c0e5bca
Persist blueprint bundle and set originalBlueprintSource to bundle-di…
adamziel Nov 28, 2025
76b3e56
Fix blueprint bundle persistence: pass originalBlueprintSource direct…
adamziel Nov 29, 2025
a0b291b
Make PersistedBlueprintBundle implement FilesystemBackend for editor …
adamziel Nov 29, 2025
44c89d9
persist bundles to OPFS
adamziel Nov 29, 2025
3be136b
simplify the code
adamziel Nov 29, 2025
9ff919e
harmonize the filesystem implementations
adamziel Nov 29, 2025
3977a1f
remove unnecessary export
adamziel Nov 29, 2025
9fb8a4c
Generic copyFilesystem helper
adamziel Nov 29, 2025
b4b333b
throw errors on failure
adamziel Nov 29, 2025
0f9f3bf
Move filesystem definitions to the storage package
adamziel Nov 29, 2025
4a30acb
Move AsyncWritableFilesystem definitions to the storage package
adamziel Nov 29, 2025
d1548e6
accept a path argument in OpfsFilesystemBackend
adamziel Nov 29, 2025
49cd822
Do not re-export asyncwritablefilesystem
adamziel Nov 29, 2025
0cc7f19
small style improvement
adamziel Nov 29, 2025
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
Prev Previous commit
Next Next commit
Lint
  • Loading branch information
adamziel committed Nov 28, 2025
commit ae30ad6fcdea33a69380e874c4ba2e30dd1895c2
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import type { AsyncWritableFilesystem } from '@wp-playground/components';
import clsx from 'clsx';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import type { ImperativePanelHandle } from 'react-resizable-panels';
Expand All @@ -16,6 +17,53 @@ import { TerminalWrapper } from './terminal/Terminal';
import { useAppSelector } from '../hooks';
import AddressBar from '../../components/address-bar';
import { DEFAULT_WORKSPACE_DIR } from '../constants';
import type { PlaygroundClient } from '@wp-playground/remote';

/**
* Wraps a PlaygroundClient to satisfy AsyncWritableFilesystem interface
* which requires EventTarget methods.
*/
class ClientFilesystemWrapper
extends EventTarget
implements AsyncWritableFilesystem
{
private client: PlaygroundClient;

constructor(client: PlaygroundClient) {
super();
this.client = client;
}
isDir(path: string) {
return this.client.isDir(path);
}
fileExists(path: string) {
return this.client.fileExists(path);
}
readFileAsBuffer(path: string) {
return this.client.readFileAsBuffer(path);
}
readFileAsText(path: string) {
return this.client.readFileAsText(path);
}
listFiles(path: string) {
return this.client.listFiles(path);
}
writeFile(path: string, data: string | Uint8Array) {
return this.client.writeFile(path, data);
}
mkdir(path: string) {
return this.client.mkdir(path);
}
rmdir(path: string, options?: { recursive?: boolean }) {
return this.client.rmdir(path, options);
}
mv(source: string, destination: string) {
return this.client.mv(source, destination);
}
unlink(path: string) {
return this.client.unlink(path);
}
}

export const Layout = () => {
const [isHelpOpen, setHelpOpen] = useState(false);
Expand All @@ -36,6 +84,13 @@ export const Layout = () => {
);
const editorHostRef = useRef<EditorHostHandle | null>(null);

const filesystem = useMemo(() => {
if (!playgroundClient) {
return null;
}
return new ClientFilesystemWrapper(playgroundClient);
}, [playgroundClient]);

useEffect(() => {
const previousTitle = document.title;
document.title = 'WordPress PHP Playground';
Expand Down Expand Up @@ -75,9 +130,9 @@ export const Layout = () => {
<PanelGroup direction="horizontal" id="app" className={styles.app}>
<Panel minSize={16} defaultSize={16} collapsible>
<div className={styles.editorPane}>
{bootStatus === 'ready' && playgroundClient ? (
{bootStatus === 'ready' && filesystem ? (
<FileExplorerSidebar
filesystem={playgroundClient}
filesystem={filesystem}
currentPath={currentPath}
selectedDirPath={selectedDirPath}
setSelectedDirPath={setSelectedDirPath}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,28 +502,60 @@ export function SiteFileBrowser({
);
}

/**
* Wraps a PlaygroundClient to satisfy AsyncWritableFilesystem interface
* which requires EventTarget methods.
*/
class ClientFilesystemWrapper
extends EventTarget
implements AsyncWritableFilesystem
{
private client: PlaygroundClient;

constructor(client: PlaygroundClient) {
super();
this.client = client;
}
isDir(path: string) {
return this.client.isDir(path);
}
fileExists(path: string) {
return this.client.fileExists(path);
}
readFileAsBuffer(path: string) {
return this.client.readFileAsBuffer(path);
}
readFileAsText(path: string) {
return this.client.readFileAsText(path);
}
listFiles(path: string) {
return this.client.listFiles(path);
}
writeFile(path: string, data: string | Uint8Array) {
return this.client.writeFile(path, data);
}
mkdir(path: string) {
return this.client.mkdir(path);
}
rmdir(path: string, options?: { recursive?: boolean }) {
return this.client.rmdir(path, options);
}
mv(source: string, destination: string) {
return this.client.mv(source, destination);
}
unlink(path: string) {
return this.client.unlink(path);
}
}

function useFilesystem(
client: PlaygroundClient | null
): AsyncWritableFilesystem | null {
return useMemo(() => {
if (!client) {
return null;
}
return {
isDir: (path: string) => client.isDir(path),
fileExists: (path: string) => client.fileExists(path),
readFileAsBuffer: (path: string) => client.readFileAsBuffer(path),
readFileAsText: (path: string) => client.readFileAsText(path),
listFiles: (path: string) => client.listFiles(path),
writeFile: (path: string, data: string | Uint8Array) =>
client.writeFile(path, data),
mkdir: (path: string) => client.mkdir(path),
rmdir: (path: string, options?: { recursive?: boolean }) =>
client.rmdir(path, options),
mv: (source: string, destination: string) =>
client.mv(source, destination),
unlink: (path: string) => client.unlink(path),
};
return new ClientFilesystemWrapper(client);
}, [client]);
}

Expand Down
207 changes: 88 additions & 119 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
@@ -1,120 +1,89 @@
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "bundler",
"esModuleInterop": true,
"importHelpers": true,
"resolveJsonModule": true,
"jsx": "react",
"target": "ES2021",
"module": "esnext",
"lib": [
"ES2022",
"esnext.disposable",
"dom"
],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"noPropertyAccessFromIndexSignature": false,
"baseUrl": ".",
"paths": {
"@php-wasm/cli": [
"packages/php-wasm/cli/src/main.ts"
],
"@php-wasm/fs-journal": [
"packages/php-wasm/fs-journal/src/index.ts"
],
"@php-wasm/logger": [
"packages/php-wasm/logger/src/index.ts"
],
"@php-wasm/node": [
"packages/php-wasm/node/src/index.ts"
],
"@php-wasm/node-polyfills": [
"packages/php-wasm/node-polyfills/src/index.ts"
],
"@php-wasm/private": [
"packages/php-wasm/private/src/index.ts"
],
"@php-wasm/progress": [
"packages/php-wasm/progress/src/index.ts"
],
"@php-wasm/scopes": [
"packages/php-wasm/scopes/src/index.ts"
],
"@php-wasm/stream-compression": [
"packages/php-wasm/stream-compression/src/index.ts"
],
"@php-wasm/universal": [
"packages/php-wasm/universal/src/index.ts"
],
"@php-wasm/universal/mime-types": [
"packages/php-wasm/universal/src/lib/mime-types.json"
],
"@php-wasm/util": [
"packages/php-wasm/util/src/index.ts"
],
"@php-wasm/web": [
"packages/php-wasm/web/src/index.ts"
],
"@php-wasm/web-service-worker": [
"packages/php-wasm/web-service-worker/src/index.ts"
],
"@php-wasm/xdebug-bridge": [
"packages/php-wasm/xdebug-bridge/src/index.ts"
],
"@wp-playground/blueprints": [
"packages/playground/blueprints/src/index.ts"
],
"@wp-playground/cli": [
"packages/playground/cli/src/cli.ts"
],
"@wp-playground/client": [
"packages/playground/client/src/index.ts"
],
"@wp-playground/common": [
"packages/playground/common/src/index.ts"
],
"@wp-playground/components": [
"packages/playground/components/src/index.ts"
],
"@wp-playground/nx-extensions": [
"packages/nx-extensions/src/index.ts"
],
"@wp-playground/remote": [
"packages/playground/remote/src/index.ts"
],
"@wp-playground/storage": [
"packages/playground/storage/src/index.ts"
],
"@wp-playground/sync": [
"packages/playground/sync/src/index.ts"
],
"@wp-playground/unit-test-utils": [
"packages/playground/unit-test-utils/src/index.ts"
],
"@wp-playground/website": [
"packages/playground/website/src/index.ts"
],
"@wp-playground/website-extras": [
"packages/playground/website-extras/src/php-playground/main.tsx"
],
"@wp-playground/wordpress": [
"packages/playground/wordpress/src/index.ts"
],
"@wp-playground/wordpress-builds": [
"packages/playground/wordpress-builds/src/index.ts"
],
"isomorphic-git": [
"./isomorphic-git"
]
}
},
"exclude": [
"node_modules",
"tmp"
]
}
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "bundler",
"esModuleInterop": true,
"importHelpers": true,
"resolveJsonModule": true,
"jsx": "react",
"target": "ES2021",
"module": "esnext",
"lib": ["ES2022", "esnext.disposable", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"noPropertyAccessFromIndexSignature": false,
"baseUrl": ".",
"paths": {
"@php-wasm/cli": ["packages/php-wasm/cli/src/main.ts"],
"@php-wasm/fs-journal": [
"packages/php-wasm/fs-journal/src/index.ts"
],
"@php-wasm/logger": ["packages/php-wasm/logger/src/index.ts"],
"@php-wasm/node": ["packages/php-wasm/node/src/index.ts"],
"@php-wasm/node-polyfills": [
"packages/php-wasm/node-polyfills/src/index.ts"
],
"@php-wasm/private": ["packages/php-wasm/private/src/index.ts"],
"@php-wasm/progress": ["packages/php-wasm/progress/src/index.ts"],
"@php-wasm/scopes": ["packages/php-wasm/scopes/src/index.ts"],
"@php-wasm/stream-compression": [
"packages/php-wasm/stream-compression/src/index.ts"
],
"@php-wasm/universal": ["packages/php-wasm/universal/src/index.ts"],
"@php-wasm/universal/mime-types": [
"packages/php-wasm/universal/src/lib/mime-types.json"
],
"@php-wasm/util": ["packages/php-wasm/util/src/index.ts"],
"@php-wasm/web": ["packages/php-wasm/web/src/index.ts"],
"@php-wasm/web-service-worker": [
"packages/php-wasm/web-service-worker/src/index.ts"
],
"@php-wasm/xdebug-bridge": [
"packages/php-wasm/xdebug-bridge/src/index.ts"
],
"@wp-playground/blueprints": [
"packages/playground/blueprints/src/index.ts"
],
"@wp-playground/cli": ["packages/playground/cli/src/cli.ts"],
"@wp-playground/client": [
"packages/playground/client/src/index.ts"
],
"@wp-playground/common": [
"packages/playground/common/src/index.ts"
],
"@wp-playground/components": [
"packages/playground/components/src/index.ts"
],
"@wp-playground/nx-extensions": [
"packages/nx-extensions/src/index.ts"
],
"@wp-playground/remote": [
"packages/playground/remote/src/index.ts"
],
"@wp-playground/storage": [
"packages/playground/storage/src/index.ts"
],
"@wp-playground/sync": ["packages/playground/sync/src/index.ts"],
"@wp-playground/unit-test-utils": [
"packages/playground/unit-test-utils/src/index.ts"
],
"@wp-playground/website": [
"packages/playground/website/src/index.ts"
],
"@wp-playground/website-extras": [
"packages/playground/website-extras/src/php-playground/main.tsx"
],
"@wp-playground/wordpress": [
"packages/playground/wordpress/src/index.ts"
],
"@wp-playground/wordpress-builds": [
"packages/playground/wordpress-builds/src/index.ts"
],
"isomorphic-git": ["./isomorphic-git"]
}
},
"exclude": ["node_modules", "tmp"]
}
Loading