Build a complete project workspace with custom sidebar, project files, and specialized viewers — all running in the browser with no server required.
This demo creates a Node.js project structure with package.json, source files, tests, and documentation. Custom apps handle JSON and Markdown rendering. The sidebar is configured with project-specific bookmarks.
<iframe id="pw-demo" src="/online/webapp/file-manager" width="100%" height="650" frameborder="0" style="border:1px solid #ccc; border-radius:4px;"></iframe>
<script>window._wsConnect('pw-demo', 'pwSocket');
window._pwSeq = function (cmds, done) {
var s = window._ws('pwSocket');
if (!s) return;
var i = 0;
var next = function () {
if (i >= cmds.length) { if (done) done(); return; }
var cmd = cmds[i++];
var args = cmd.concat([function () { setTimeout(next, 5); }]);
s.fire.apply(s, ['webapp::instance::request'].concat(args));
};
next();};
window._pwAppCode = function (renderFnStr) {
return 'module.exports = function () {\n' +
'var app = new ApplicationPrototype();\n' +
'var node = document.createElement("div");\n' +
'var win = null;\n' +
'var ready = new Application.Promise();\n' +
'app.bind("node", function () { return node; }, "");\n' +
'app.bind("window", function () { return win; }, "");\n' +
'app.bind("ready", function () { return ready; });\n' +
'app.bind("handleWindow", function (w) {\n' +
' win = w;\n' +
' w.emit("api-request::attached", []);\n' +
' w.on("api-request::ready", function (cb) { cb(ready); });\n' +
'});\n' +
'app.bind("render", function (cb) {\n' +
' (' + renderFnStr + ')(app, win, node, cb);\n' +
'});\n' +
'app.bind("destroy", function () {});\n' +
'app.bind("init", function () {\n' +
' app.render(function (err) {\n' +
' if (err) return ready.reject(err);\n' +
' ready.resolve(app);\n' +
' });\n' +
' return app;\n' +
'});\n' +
'return app;\n' +
'};';};
window._pwSetup = function () {
// JSON Viewer app
var jsonViewerCode = window._pwAppCode((function (app, win, node, cb) {
win.title('JSON Viewer'); win.icon('breeze/icons/mimetypes/64/application-json');
win.height(400); win.width(600);
var args = app.window().env().args || [];
if (args[0]) {
node.innerHTML = 'Loading...
';
Application.require('/files/api/modules/fs.js').then(function (fsModule) {
fsModule().read(args[0], null, true).response('text').then(function (text) {
try {
var json = JSON.parse(text);
var pretty = JSON.stringify(json, null, 2).replace(/&/g,'&').replace(/"$1":');
pretty = pretty.replace(/: (".*?")/g, ': $1');
pretty = pretty.replace(/: (\d+)/g, ': $1');
node.innerHTML = '' + pretty + '
';
} catch (e) {
node.innerHTML = 'Parse Error
' + e.message + '
';
}
});
});
}
cb();
}).toString());// Markdown Preview app
var mdPreviewCode = window._pwAppCode((function (app, win, node, cb) {
win.title('Markdown Preview'); win.icon('breeze/icons/mimetypes/16/text-x-markdown');
win.height(450); win.width(650);
var args = app.window().env().args || [];
if (args[0]) {
node.innerHTML = 'Rendering...
';
Application.require('/files/api/modules/fs.js').then(function (fsModule) {
fsModule().read(args[0], null, true).response('text').then(function (text) {
var html = text
.replace(/^### (.+)$/gm, '$1
')
.replace(/^## (.+)$/gm, '$1
')
.replace(/^# (.+)$/gm, '$1
')
.replace(/\*\*(.+?)\*\*/g, '$1')
.replace(/\*(.+?)\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
.replace(/^- (.+)$/gm, '$1 ')
.replace(/\n\n/g, '')
.replace(/\n/g, '
');
node.innerHTML = '
' + html + '
';
});
});
}
cb();
}).toString());var pkgJson = JSON.stringify({
name: "my-webapp",
version: "2.1.0",
description: "A demo web application project",
main: "src/index.js",
scripts: { start: "node src/index.js", test: "jest --coverage", build: "webpack --mode production", lint: "eslint src/" },
dependencies: { express: "^4.18.2", lodash: "^4.17.21", cors: "^2.8.5" },
devDependencies: { jest: "^29.7.0", eslint: "^8.56.0", webpack: "^5.90.0" },
license: "MIT"
}, null, 2);var indexJs = [
"const express = require('express');",
"const cors = require('cors');",
"const { greet, sum } = require('./utils');",
"",
"const app = express();",
"app.use(cors());",
"app.use(express.json());",
"",
"app.get('/', (req, res) => {",
" res.json({ message: greet('World'), status: 'running' });",
"});",
"",
"app.get('/api/add/:a/:b', (req, res) => {",
" const result = sum(Number(req.params.a), Number(req.params.b));",
" res.json({ result });",
"});",
"",
"const PORT = process.env.PORT || 3000;",
"app.listen(PORT, () => {",
" console.log(`Server running on port ${PORT}`);",
"});"
].join('\n');var utilsJs = [
"/**",
" * Greet a person by name",
" * @param {string} name",
" * @returns {string}",
" */",
"function greet(name) {",
" return `Hello, ${name}!`;",
"}",
"",
"/**",
" * Sum two numbers",
" * @param {number} a",
" * @param {number} b",
" * @returns {number}",
" */",
"function sum(a, b) {",
" return a + b;",
"}",
"",
"module.exports = { greet, sum };"
].join('\n');var testJs = [
"const { greet, sum } = require('../src/utils');",
"",
"describe('greet', () => {",
" test('returns greeting', () => {",
" expect(greet('Alice')).toBe('Hello, Alice!');",
" });",
"});",
"",
"describe('sum', () => {",
" test('adds numbers', () => {",
" expect(sum(2, 3)).toBe(5);",
" });",
" test('handles negatives', () => {",
" expect(sum(-1, 1)).toBe(0);",
" });",
"});"
].join('\n');var readmeMd = [
"# my-webapp",
"",
"A demo web application built with **Express.js**.",
"",
"## Quick Start",
"",
"```bash",
"npm install",
"npm start",
"```",
"",
"The server starts on `http://localhost:3000`.",
"",
"## API Endpoints",
"",
"- `GET /` — Health check, returns greeting",
"- `GET /api/add/:a/:b` — Sum two numbers",
"",
"## Project Structure",
"",
"- `src/index.js` — Express server entry point",
"- `src/utils.js` — Utility functions",
"- `test/utils.test.js` — Jest test suite",
"",
"## Development",
"",
"Run tests: `npm test`",
"",
"Lint code: `npm run lint`",
"",
"Build for production: `npm run build`",
"",
"## License",
"",
"MIT"
].join('\n');window._pwSeq([
['gui:specialPaths:clear'],
['gui:sidebar:toggle', false],
['fs:mkdirp', 'input://apps/'],
['fs:mkdirp', 'input://my-webapp/src/'],
['fs:mkdirp', 'input://my-webapp/test/'],
['fs:mkdirp', 'input://my-webapp/docs/'],
['fs:write', 'input://apps/json-viewer.js', jsonViewerCode],
['fs:write', 'input://apps/md-preview.js', mdPreviewCode],
['fs:write', 'input://my-webapp/package.json', pkgJson],
['fs:write', 'input://my-webapp/src/index.js', indexJs],
['fs:write', 'input://my-webapp/src/utils.js', utilsJs],
['fs:write', 'input://my-webapp/test/utils.test.js', testJs],
['fs:write', 'input://my-webapp/README.md', readmeMd],
['fs:write', 'input://my-webapp/.gitignore', 'node_modules/\ndist/\ncoverage/\n.env\n'],
['fs:write', 'input://my-webapp/docs/api.md', '# API Documentation\n\n## GET /\n\nReturns a JSON greeting.\n\n**Response:**\n```json\n{ "message": "Hello, World!", "status": "running" }\n```\n\n## GET /api/add/:a/:b\n\nAdds two numbers.\n\n**Example:** `/api/add/5/3` returns `{ "result": 8 }`'],
['apps:register', {
'json-viewer': { path: 'input://apps/json-viewer.js', name: 'JSON Viewer', icon: 'breeze/icons/mimetypes/64/application-json', comment: 'Pretty-print JSON', mimetypes: ['application/json'], args: ['%U'] },
'md-preview': { path: 'input://apps/md-preview.js', name: 'Markdown Preview', icon: 'breeze/icons/mimetypes/16/text-x-markdown', comment: 'Render Markdown', mimetypes: ['text/markdown', 'text/x-markdown'], args: ['%U'] }
}],
['gui:specialPaths:add', { path: 'input://my-webapp/', name: 'my-webapp', icon: 'breeze/icons/places/64/folder-development', paths: [{ rule: 'src\\/', name: 'Source' }, { rule: 'test\\/', name: 'Tests' }, { rule: 'docs\\/', name: 'Docs' }], bookmark: { category: 'project', name: 'my-webapp', icon: 'breeze/icons/places/64/folder-development' } }],
['gui:sidebar:toggle', true],
['fs:cwd', 'input://my-webapp/']
], function () {
alert('Project workspace ready!\n\nTry:\n- Double-click package.json (opens JSON Viewer)\n- Double-click README.md (opens Markdown Preview)\n- Double-click .js files (opens Code Editor)\n- Navigate via sidebar bookmark "my-webapp"\n- Check breadcrumbs: my-webapp > Source > ...');
});};</script>
<button onclick="window._pwSetup()" style="padding:10px 20px;font-size:15px;cursor:pointer">Setup Project Workspace</button>
`
input://my-webapp/
package.json ← opens with JSON Viewer (syntax-highlighted)
README.md ← opens with Markdown Preview (rendered HTML)
.gitignore
src/
index.js ← opens with Code Editor
utils.js ← opens with Code Editor
test/
utils.test.js ← opens with Code Editor
docs/
api.md ← opens with Markdown Preview
`src/ shows as "Source", test/ as "Tests", docs/ as "Docs"After clicking "Setup Project Workspace":
package.json — opens in the JSON Viewer with Monokai-style syntax highlightingREADME.md — opens in the Markdown Preview with rendered headings, code blocks, and lists.js file — opens in the built-in Code Editor with JavaScript syntax highlightingsrc/ — breadcrumb shows "my-webapp / Source" instead of "my-webapp / src"This workspace is built entirely via the API — no server, no uploads, no authentication. Every file exists in browser memory only.
// 1. Create project structure
socket.fire("webapp::instance::request", "fs:mkdirp", "input://my-webapp/src/");
socket.fire("webapp::instance::request", "fs:mkdirp", "input://my-webapp/test/");
// 2. Write files
socket.fire("webapp::instance::request", "fs:write",
"input://my-webapp/package.json", JSON.stringify(pkg, null, 2));
// 3. Register custom apps for specific file types
socket.fire("webapp::instance::request", "apps:register", {
"json-viewer": {
path: "input://apps/json-viewer.js",
mimetypes: ["application/json"],
...
},
"md-preview": {
path: "input://apps/md-preview.js",
mimetypes: ["text/markdown", "text/x-markdown"],
...
}
});
// 4. Configure sidebar with breadcrumb grouping
socket.fire("webapp::instance::request", "gui:specialPaths:add", {
path: "input://my-webapp/",
name: "my-webapp",
paths: [
{ rule: "src\\/", name: "Source" },
{ rule: "test\\/", name: "Tests" },
{ rule: "docs\\/", name: "Docs" }
],
bookmark: { category: "project", name: "my-webapp", icon: "..." }
});