- Notifications
You must be signed in to change notification settings - Fork 369
[Website] File browser and code editor #2813
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
Conversation
| * just as the web request layer would. | ||
| */ | ||
| HTTPS: (await playground.absoluteUrl).startsWith('https://') ? 'on' : '', | ||
| HTTPS: (await playground.absoluteUrl).startsWith('https://') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is just reformatting
| This will be such a great addition to the website, great work @adamziel! I found a few quirks, but otherwise it works really well. Type to find file interferes with renamingTry creating a folder 123 and after that rename a file to include 1 in the name. As soon as you press 1, the rename modal closes and the folder with the name 123 is highlighted. Type to find should be disabled during renaming to avoid issues like this. Screen.Recording.2025-10-24.at.06.57.28.movDeleted files keep coming back after recreating the same directoryI had a directory 123 with a file test.231.php and deleted it (this part isn't in the video). In this video, I created a directory 123 with a test.php file in it, after reloading the 123 directory contained only the test231.php file which I deleted in the past. Screen.Recording.2025-10-24.at.07.01.18.mov |
| Thanks @bgrgicak! I've just addressed these two issues. |
brandonpayton left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read over the changes, and everything seems nicely, simply put together. Nice work!
Will test this next.
| mkdir(path: string) { | ||
| return FSHelpers.mkdir(this[__private__dont__use].FS, path); | ||
| const result = FSHelpers.mkdir(this[__private__dont__use].FS, path); | ||
| this.dispatchEvent({ type: 'filesystem.write' }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would there be any value in including the path with the event? Same question for the other FS event dispatches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potentially, once something needs that path. Today we only need a way of telling the journal "hey, something changed in the filesystem, please synchronize that with OPFS".
| '.git': { | ||
| type: 'boolean', | ||
| description: | ||
| 'When true, include a `.git` directory with Git metadata (experimental).', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's weird this wasn't committed before! See #2787
| focusTarget.scrollIntoView({ | ||
| behavior: 'smooth', | ||
| block: 'nearest', | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice.
| renameHandledRef.current = false; | ||
| if (typeof window !== 'undefined' && window.requestAnimationFrame) { | ||
| window.requestAnimationFrame(() => { | ||
| if (typeof window !== 'undefined' && requestAnimationFrame) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous version of this code which explicitly referenced the window property made more sense to me. But mostly, this change made me pause and wonder:
Is there a reason for this change that I'm missing?
🤔 If we're trying to reduce references to window for some reason, the following would go further:
| if (typeof window !== 'undefined' && requestAnimationFrame) { | |
| if (typeof requestAnimationFrame === 'function') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah good note, let's explore restoring that
| return; | ||
| } | ||
| | ||
| const wasWaitingForDoubleClick = clickTimeoutRef.current !== null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This surprised me, though I bet there is a good reason for it. Doesn't the underlying browser platform decide whether a click is a single or double click? Is there a case where you get individual click events that make up a single double-click intent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an onDoubleClick event, but I couldn't get consistent results with it. Also, we want to open the file right away without any visible delays – and we'd get a delay in a data model where we handle either a "singular click" event or a "double click" event. In here, we're getting each specific click event and open the file right away. Then, we still need to decide if it's a double click. If yes, we also move the focus into the code editor – once that second click arrives.
| * | ||
| * Optional chunks are placed in assets/optional/ via vite.config.ts manualChunks configuration. | ||
| */ | ||
| /^\/assets\/optional\/.*/, // All optional assets (CodeMirror, language extensions, etc.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
| | ||
| const seemsLikeBinary = (buffer: Uint8Array) => { | ||
| // Assume that anything with a null byte in the first 4096 bytes is binary. | ||
| // This isn't a perfect test, but it catches a lot of binary files. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This made me happy. Nice heuristic.
| setTimeout(() => { | ||
| const savedPos = cursorPositionsRef.current.get(path); | ||
| if (savedPos !== undefined) { | ||
| editorRef.current?.setCursorPosition(savedPos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if this is out of bounds due to a shortened file?
EDIT: It turns out that setCursorPosition() clamps the position to the length of the content.
| const clampedPos = Math.min( | ||
| pos, | ||
| viewRef.current.state.doc.length | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be too paranoid, but perhaps we should clamp this on the lower end as well.
The position shouldn't normally become negative, but faulty math involving substring length and a text index could do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! Let's do it in a follow-up
| if (!view) { | ||
| return; | ||
| } | ||
| // Save focus state before reconfiguring editable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, perhaps we don't need that. I did use LLMs with this one and was a bit lenient on these new components, I'm not sure what to think about that kind of workflow yet. It seems to be prone to introducing those kind of strange code segments. I'll explore removing it in a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, perhaps we don't need that. I did use LLMs with this one and was a bit lenient on these new components, I'm not sure what to think about that kind of workflow yet. It seems to be prone to introducing those kind of strange code segments. I'll explore removing it in a follow-up.
@adamziel Thanks for explaining. Honestly, it may be a reasonable tradeoff if we can mostly identify most of these weird bits. It would also be really cool if git blame could somehow tell us particular lines were generated by AI. Then we might feel more confident about questioning and changing an unexplained detail in the future.
brandonpayton left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tests really well for me. Excellent work! This makes Playground even more accessible and useful.
One idea for an enhancement:
For binary files like images and videos that can be read by the browser, maybe we could consider making those viewable, in place of the editor, when they are selected.
Thank you so much for this, @adamziel!
Great idea! Let's explore it in a follow up |
Equips the Playground file browser with a preview feature for images, videos, and audio. Follows up on #2813. <img width="700" alt="CleanShot 2025-10-26 at 23 47 51@2x" src="https://github.com/user-attachments/assets/f15ff15d-db39-4370-8924-3fa98dfe824c" /> ## Testing instructions Open the file explorer and open any image, video or audio file. Confirm you can see the preview. <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Preview images and binary files in the file browser</issue_title> > <issue_description>For binary files like images and videos that can be read by the browser, maybe we could consider making those viewable, in place of the editor, when they are selected. > > Follows up on #2813</issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> - Fixes #2818 <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/WordPress/wordpress-playground/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: adamziel <205419+adamziel@users.noreply.github.com>
Changed --site-manager-site-list-width from 320px to 300px to make the sidebar with saved sites more compact. This improves the #2813 experience. ## Testing Instructions (or ideally a Blueprint) Go to local Playground confirm the left sidebar is more narrow and yet still useful --------- Co-authored-by: Claude <noreply@anthropic.com>
Motivation for the change, related issues
Adds a "File browser" tab to Playground details view with a file explorer tree and a code editor:
CleanShot.2025-10-23.at.23.31.22.mp4
When working on Beta PHP Playground I realized it's just a different UI for WordPress Playground. A UI that would be highly useful in Playground core. Therefore, instead of expanding on the PHP Playground idea, let's make the website just as useful for developers.
Notable features:
Remaining work
Testing Instructions (or ideally a Blueprint)
Play with the file explorer and the code editor, confirm it works as expected without too rough edges.