Full-featured file browser with dual-pane layout, upload, clipboard operations, view modes, and a complete filesystem API for programmatic control.
<iframe id="fm-demo" src="/online/webapp/file-manager" width="100%" height="500" frameborder="0" style="border:1px solid #ccc; border-radius:4px;"></iframe>
<script>window._wsConnect('fm-demo', 'fmSocket');</script>
<iframe src="https://sgapps.io/online/webapp/file-manager"
width="100%" height="600" frameborder="0"></iframe>External API filesystem operations are sandboxed to input:// only. Any attempt to read, write, navigate, or delete outside this path via the API returns an access denied error.
input:// -- sandboxed area for API operations (no auth, browser-memory-only)input:// is browser-memory-only: isolated per tab, destroyed on refresh, never touches the server/home/user/) require authentication and are not accessible via the APIThese events let you create, read, write, delete, rename files and folders programmatically — perfect for building custom UIs on top of the embedded file manager.
fs:cwd -- Get or Set Current DirectoryNavigate the file manager to a different directory, or get the current path.
| Arg | Type | Description |
|---|---|---|
path | string (optional) | Directory path to navigate to |
callback | function | (err, currentPath: string) |
// Navigate to input://
socket.fire("webapp::instance::request", "fs:cwd", "input://", function (err, cwd) {
console.log("Now at:", cwd);
});
// Get current directory
socket.fire("webapp::instance::request", "fs:cwd", function (err, cwd) {
console.log("Current:", cwd);
});<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','fs:cwd','input://',function(e,p){if(e)alert('Error: '+e);else console.log('Navigated to:',p)})">Try: Navigate to input://</button><button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','fs:cwd',function(e,p){alert('Current path: '+p)})">Try: Get Current Path</button>
fs:mkdirp -- Create Directory (with parents)Creates a directory and any missing parent directories. Works like mkdir -p.
| Arg | Type | Description |
|---|---|---|
path | string | Directory path to create |
callback | function | (err) |
socket.fire("webapp::instance::request", "fs:mkdirp", "input://my-project/src/components/", function (err) {
console.log(err || "Directories created");
});<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','fs:mkdirp','input://demo-project/src/components/',function(e){if(!e){window.fmSocket.fire('webapp::instance::request','fs:cwd','input://demo-project/');alert('Created input://demo-project/src/components/')}else alert('Error: '+e)})">Try: Create demo-project/src/components/</button>
fs:write -- Write a FileCreates or overwrites a file with the given content.
| Arg | Type | Description |
|---|---|---|
path | string | File path |
content | string | File content |
callback | function | (err) |
socket.fire("webapp::instance::request", "fs:write",
"input://demo-project/README.md",
"# My Project\n\nThis is a demo project created via the API.",
function (err) { console.log(err || "File written"); }
);<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','fs:mkdirp','input://demo-project/',function(){window.fmSocket.fire('webapp::instance::request','fs:write','input://demo-project/README.md','# Demo Project\n\nCreated via the File Manager API.\n\n## Features\n- In-memory storage\n- No server required\n- Destroyed on refresh',function(e){if(!e){window.fmSocket.fire('webapp::instance::request','fs:cwd','input://demo-project/');alert('README.md created!')}else alert('Error: '+e)})})">Try: Create README.md</button><button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','fs:write','input://demo-project/src/index.js','console.log('Hello from the API!');\n\nfunction add(a, b) {\n return a + b;\n}\n\nmodule.exports = { add };',function(e){if(!e){window.fmSocket.fire('webapp::instance::request','fs:refresh');alert('index.js created!')}else alert('Error: '+e)})">Try: Create src/index.js</button>
fs:read -- Read a FileReads the content of a file as text.
| Arg | Type | Description |
|---|---|---|
path | string | File path |
callback | function | (err, content: string) |
socket.fire("webapp::instance::request", "fs:read", "input://demo-project/README.md",
function (err, content) {
console.log("Content:", content);
}
);<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','fs:read','input://demo-project/README.md',function(e,c){if(c)alert('File content:\n\n'+c);else alert('Error: '+(e||'file not found'))})">Try: Read README.md</button>
fs:ls -- List Directory ContentsReturns an array of files and folders in the specified directory.
| Arg | Type | Description |
|---|---|---|
path | string | Directory path |
callback | function | (err, files: Array<{filename, length, metadata}>) |
socket.fire("webapp::instance::request", "fs:ls", "input://demo-project/",
function (err, files) {
files.forEach(function (f) {
console.log(f.metadata._isDirectory ? "[DIR]" : "[FILE]", f.filename, f.length + " bytes");
});
}
);<button onclick="window.ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','fs:ls','input://demo-project/',function(e,f){if(f){var msg=f.map(function(x){return (x.metadata.isDirectory?'[DIR] ':'[FILE] ')+x.filename.replace(/^.*\/(?=[^\/]+\/?$)/,'')}).join('\n');alert('Contents:\n\n'+msg)}else alert('Error: '+(e||'not found'))})">Try: List demo-project/</button>
fs:mv -- Move or RenameMoves or renames a file or directory within the same protocol.
| Arg | Type | Description |
|---|---|---|
src | string | Source path |
dst | string | Destination path |
callback | function | (err) |
socket.fire("webapp::instance::request", "fs:mv",
"input://demo-project/README.md",
"input://demo-project/ABOUT.md",
function (err) { console.log(err || "Renamed"); }
);<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','fs:mv','input://demo-project/README.md','input://demo-project/ABOUT.md',function(e){if(!e){window.fmSocket.fire('webapp::instance::request','fs:refresh');alert('Renamed README.md to ABOUT.md')}else alert('Error: '+e)})">Try: Rename README.md to ABOUT.md</button>
fs:remove -- Delete a FileRemoves a single file or empty directory. Lightweight, no UI.
| Arg | Type | Description |
|---|---|---|
path | string | Path to remove |
options | object (optional) | { autoConfirm: true } — accepted for API consistency |
callback | function | (err) |
socket.fire("webapp::instance::request", "fs:remove", "input://demo-project/ABOUT.md",
function (err) { console.log(err || "Deleted"); }
);
// With options
socket.fire("webapp::instance::request", "fs:remove", "input://temp/file.txt",
{ autoConfirm: true },
function (err) { console.log(err || "Deleted"); }
);fs:copy -- Copy a FileCopies a single file. Reads source as blob, writes to destination. Lightweight, no UI.
| Arg | Type | Description |
|---|---|---|
src | string | Source file path |
dst | string | Destination file path |
options | object (optional) | { autoConfirm: true } — accepted for API consistency |
callback | function | (err) |
// Simple copy
socket.fire("webapp::instance::request", "fs:copy",
"input://project/config.json",
"input://backup/config.json",
function (err) { console.log(err || "Copied"); }
);
// With options (accepted, currently same behavior)
socket.fire("webapp::instance::request", "fs:copy",
"input://project/config.json",
"input://backup/config.json",
{ autoConfirm: true },
function (err) { console.log(err || "Copied"); }
);fs:archive -- Create Archive (no UI)Creates an archive from a directory using Packer. No window, no progress UI — runs silently in background.
| Arg | Type | Description |
|---|---|---|
sourcePath | string | Directory to archive |
outputPath | string or null | Where to save (null = returns blob URL in callback) |
format | string | "zip", "tar", "tar.gz", "7z" (default: "zip") |
options | object (optional) | { autoConfirm: true } — accepted for API consistency |
callback | function | (err, result: { size, path?, url? }) |
// Archive and save to filesystem
socket.fire("webapp::instance::request", "fs:archive",
"input://project/", "input://project.zip", "zip",
function (err, result) {
console.log("Archive:", result.size, "bytes");
}
);
// Archive without saving (get blob URL for download)
socket.fire("webapp::instance::request", "fs:archive",
"input://project/", null, "tar.gz",
function (err, result) {
console.log("Blob URL:", result.url);
}
);These open a bin app window with progress bars, error handling, and conflict resolution. Use for batch operations or when you want the user to see progress.
fs:bulkCopy| Arg | Type | Description |
|---|---|---|
sources | string or Array | Source path(s) |
destination | string | Destination directory |
options | object (optional) | { autoConfirm: true } to auto-resolve conflicts |
callback | function | (err) — fires when bin/cp window closes |
socket.fire("webapp::instance::request", "fs:bulkCopy",
["input://src/file1.txt", "input://src/file2.txt"],
"input://backup/",
{ autoConfirm: true },
function (err) { console.log(err || "Bulk copy done"); }
);fs:bulkRemove| Arg | Type | Description |
|---|---|---|
paths | string or Array | Path(s) to delete |
options | object (optional) | { autoConfirm: true } to skip confirmation |
callback | function | (err) |
socket.fire("webapp::instance::request", "fs:bulkRemove",
["input://old-dir/", "input://temp.txt"],
{ autoConfirm: true },
function (err) { console.log(err || "Bulk delete done"); }
);fs:bulkArchive| Arg | Type | Description |
|---|---|---|
sources | string or Array | Path(s) to archive |
outputPath | string or null | Where to save (null = download) |
format | string | "zip", "tar", "tar.gz", "7z" |
options | object (optional) | { autoConfirm: true } to skip format selection |
callback | function | (err) |
socket.fire("webapp::instance::request", "fs:bulkArchive",
"input://project/", "input://project.zip", "zip",
{ autoConfirm: true },
function (err) { console.log(err || "Bulk archive done"); }
);When to use which: Usefs:copy,fs:remove,fs:archivefor single-file operations or scripted pipelines. Usefs:bulkCopy,fs:bulkRemove,fs:bulkArchivewhen you want the user to see progress, resolve conflicts, or when operating on many files at once.
fs:exists -- Check if Path Exists| Arg | Type | Description |
|---|---|---|
path | string | Path to check |
callback | function | (err, exists: boolean) |
socket.fire("webapp::instance::request", "fs:exists", "input://demo-project/",
function (err, exists) { console.log("Exists:", exists); }
);fs:stats -- Get File/Directory Info| Arg | Type | Description |
|---|---|---|
path | string | Path to inspect |
callback | function | (err, stats: object) |
fs:readStreamUrl -- Get Streaming URLReturns a URL that can be used in <img>, <video>, <a href> etc. For input:// files, this returns a blob: URL.
| Arg | Type | Description |
|---|---|---|
path | string | File path |
callback | function | (err, url: string) |
fs:refresh -- Refresh ViewRefreshes the file manager's current directory listing.
socket.fire("webapp::instance::request", "fs:refresh");gui:sidebar:toggle -- Show or Hide SidebarToggle the sidebar panel visibility. Pass false to hide, true to show.
| Arg | Type | Description |
|---|---|---|
visible | boolean | true to show, false to hide |
callback | function | (err) |
socket.fire("webapp::instance::request", "gui:sidebar:toggle", false); // hide
socket.fire("webapp::instance::request", "gui:sidebar:toggle", true); // show<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','gui:sidebar:toggle',false)">Try: Hide Sidebar</button><button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','gui:sidebar:toggle',true)">Try: Show Sidebar</button>
gui:specialPaths:add -- Add Custom Path to SidebarAdds a named bookmark to the path bar / sidebar. Useful for creating labeled shortcuts to directories within your sandbox. These entries are tagged as __apiManaged and can be removed later.
| Arg | Type | Description |
|---|---|---|
entry | object | { path, name, icon?, description?, paths?, bookmark? } |
callback | function | (err) |
The icon field supports three formats:
| Format | Example | Description |
|---|---|---|
| Icon library name | "breeze/icons/places/64/folder-blue" | Resolved via Application.icon() to /scripts/modules/icons/... |
| Absolute URL | "https://example.com/icon.png" | Used as-is (also http://, //, data:) |
| Wildcard match | "paper*folder" | * matches any path segment in the icon library |
socket.fire("webapp::instance::request", "gui:specialPaths:add", {
path: "input://my-project/",
name: "My Project",
icon: "breeze/icons/places/64/folder-blue", // icon library name
description: "Project files",
bookmark: {
category: "personal",
name: "My Project",
icon: "breeze/icons/places/64/folder-blue"
}
}, function (err) { console.log(err || "Path added"); });
// Using an external icon URL
socket.fire("webapp::instance::request", "gui:specialPaths:add", {
path: "input://uploads/",
name: "Uploads",
icon: "https://example.com/my-custom-icon.svg", // absolute URL
bookmark: { category: "personal", name: "Uploads", icon: "https://example.com/my-custom-icon.svg" }
});<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','gui:specialPaths:add',{path:'input://demo-project/',name:'Demo Project',description:'Files created via API',bookmark:{category:'personal',name:'Demo Project'}},function(e){alert(e||'Special path added! Check the sidebar.')})">Try: Add "Demo Project" to Sidebar</button>
gui:specialPaths:remove -- Remove Custom PathRemoves a previously added API-managed special path by its path string.
| Arg | Type | Description |
|---|---|---|
path | string | The path that was added |
callback | function | (err) |
socket.fire("webapp::instance::request", "gui:specialPaths:remove",
"input://my-project/",
function (err) { console.log(err || "Removed"); }
);<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','gui:specialPaths:remove','input://demo-project/',function(e){alert(e||'Special path removed')})">Try: Remove "Demo Project" from Sidebar</button>
gui:specialPaths:clear -- Clear All Sidebar BookmarksRemoves all existing bookmarks from the sidebar (both built-in and API-managed). Useful when you want to set up a clean custom sidebar for your embedded widget.
// Clear everything, then add your own bookmarks
socket.fire("webapp::instance::request", "gui:specialPaths:clear", function () {
socket.fire("webapp::instance::request", "gui:specialPaths:add", {
path: "input://my-app/",
name: "My App Files",
icon: "breeze/icons/places/64/folder-blue",
bookmark: { category: "project", name: "My App Files", icon: "breeze/icons/places/64/folder-blue" }
});
});<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','gui:specialPaths:clear',function(e){alert(e||'Sidebar cleared!')})">Try: Clear All Sidebar Bookmarks</button>
Special paths can include a paths array with sub-rules that group multiple directory segments into a single named breadcrumb. This is how the navigation bar shows "My Project" instead of input:// / demo-project /.
Each sub-rule has:
rule -- regex pattern matching the remaining path after the rootname -- display name (supports {param-1}, {param-2}, etc. from regex capture groups)icon -- optional icon for this breadcrumb segmentsocket.fire("webapp::instance::request", "gui:specialPaths:add", {
path: "input://demo-project/",
name: "Demo Project",
icon: "/scripts/modules/icons/breeze/icons/places/64/folder-red.svg",
// Sub-path grouping rules for the breadcrumb bar
paths: [
{
rule: "src\\/",
name: "Source Code",
icon: "/scripts/modules/icons/breeze/icons/places/64/folder-green.svg"
},
{
rule: "test\\/",
name: "Tests"
},
{
rule: "docs\\/",
name: "Documentation"
}
],
bookmark: {
category: "personal",
name: "Demo Project",
icon: "breeze/icons/places/64/folder-blue"
}
});With this configuration, the breadcrumb bar shows:
input://demo-project/ -> Demo Projectinput://demo-project/src/ -> Demo Project / Source Codeinput://demo-project/src/components/ -> Demo Project / Source Code / componentsinput://demo-project/test/ -> Demo Project / TestsWithout the paths rules, it would show: Demo Project / src / components (each dir is a separate breadcrumb).
<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','gui:specialPaths:clear',function(){window.fmSocket.fire('webapp::instance::request','gui:specialPaths:add',{path:'input://demo-project/',name:'Demo Project',icon:'/scripts/modules/icons/breeze/icons/places/64/folder-red.svg',paths:[{rule:'src\/',name:'Source Code', icon:'/scripts/modules/icons/breeze/icons/places/64/folder-green.svg'},{rule:'test\/',name:'Tests'},{rule:'docs\/',name:'Documentation'}],bookmark:{category:'project',name:'Demo Project',icon:'breeze/icons/places/64/folder-blue'}},function(e){if(!e){window.fmSocket.fire('webapp::instance::request','fs:mkdirp','input://demo-project/src/components/',function(){window.fmSocket.fire('webapp::instance::request','fs:mkdirp','input://demo-project/test/',function(){window.fmSocket.fire('webapp::instance::request','fs:mkdirp','input://demo-project/docs/',function(){window.fmSocket.fire('webapp::instance::request','fs:cwd','input://demo-project/');alert('Project created with grouped breadcrumbs! Navigate to src/, test/, docs/ to see the named breadcrumbs.')})})})}})})">Try: Create Project with Grouped Breadcrumbs</button>
Register your own applications that open files by MIME type. Apps can be loaded from URLs, blob URLs, or inline data URIs. See the full guide: Building Custom Apps.
apps:register -- Register Custom Appssocket.fire("webapp::instance::request", "apps:register", {
"my-viewer": {
path: "https://example.com/apps/viewer.js",
name: "My Viewer",
icon: "paper/icons/apps/accessories-text-editor",
comment: "Custom text viewer",
mimetypes: ["text/plain", "text/csv"],
args: ["%U"]
}
}, function (err, registered) {
console.log("Registered:", registered);
});var appCode = ';((' + (function () {
module.exports = function () {
var app = new ApplicationPrototype();
var node = document.createElement("div");
var win = null;
var ready = new Application.Promise();
app.bind("node", function () { return node; }, "");
app.bind("window", function () { return win; }, "");
app.bind("ready", function () { return ready; });
app.bind("handleWindow", function (w) {
win = w; w.emit("api-request::attached", []);
w.on("api-request::ready", function (cb) { cb(ready); });
});
app.bind("render", function (cb) {
win.title("Hello App");
win.height(300); win.width(400);
node.innerHTML = "<div style='padding:20px;text-align:center'>" +
"<h2>Hello!</h2><p>Args: " + (app.window().env().args || []).join(", ") + "</p></div>";
cb();
});
app.bind("destroy", function () {});
app.bind("init", function () {
app.render(function (err) {
if (err) return ready.reject(err);
ready.resolve(app);
});
return app;
});
return app;
};
}).toString() + ')());';
socket.fire("webapp::instance::request", "apps:register", {
"hello-app": {
path: "data:application/javascript;base64," + btoa(appCode),
name: "Hello App",
icon: "paper/icons/apps/system-software-install",
mimetypes: [],
args: []
}
});apps:unregister -- Remove a Registered App| Arg | Type | Description |
|---|---|---|
appName | string | Name of the app to remove |
callback | function | (err) |
disableDefaultApps | boolean | If true, allows removing built-in apps too (not just API-injected ones) |
__apiInjected: true) can be unregistered. Pass true as the third argument to also remove built-in apps like code-editor, photo-editor, etc. This is useful when you want to fully customize the app environment.// Remove a custom app
socket.fire("webapp::instance::request", "apps:unregister", "my-viewer", function (err) {
console.log(err || "Removed");
});
// Remove a built-in app (e.g., disable the default code editor)
socket.fire("webapp::instance::request", "apps:unregister", "code-editor", function (err) {
console.log(err || "Removed");
}, true);
// Replace a built-in app with your own
socket.fire("webapp::instance::request", "apps:unregister", "code-editor", function () {
socket.fire("webapp::instance::request", "apps:register", {
"code-editor": {
path: "input://apps/my-editor.js",
name: "My Code Editor",
icon: "paper/icons/apps/accessories-text-editor",
mimetypes: ["text/plain", "application/x-javascript", "text/html", "text/css"],
args: ["%U"]
}
});
}, true);apps:list -- List All Registered Appssocket.fire("webapp::instance::request", "apps:list", function (err, apps) {
Object.keys(apps).forEach(function (name) {
var app = apps[name];
console.log(name, app.__apiInjected ? "(custom)" : "(built-in)", app.name);
});
});Create clickable shortcut files that launch any app with predefined arguments. Double-clicking a .lnk file reads its JSON content and opens the specified app.
fs:createShortcut -- Create a Shortcut| Arg | Type | Description |
|---|---|---|
path | string | File path for the shortcut (must end in .lnk, must be input://) |
config | object | { app, args?, name?, icon? } |
callback | function | (err) |
config fields:| Field | Type | Description |
|---|---|---|
app | string | App name from the registry (e.g., "photo-editor", "code-editor", or a custom app name) |
args | array | Arguments to pass to the app (optional, default []) |
name | string | Display name (optional) |
icon | string | Icon name (optional) |
// Create a shortcut to the Photo Editor
socket.fire("webapp::instance::request", "fs:createShortcut",
"input://desktop/Photo Editor.lnk",
{
app: "photo-editor",
args: [],
name: "Photo Editor",
icon: "paper/*/multimedia-photo-viewer.png"
},
function (err) { console.log(err || "Shortcut created"); }
);
// Create a shortcut that opens a specific file in Code Editor
socket.fire("webapp::instance::request", "fs:createShortcut",
"input://desktop/Edit Config.lnk",
{
app: "code-editor",
args: ["input://project/config.json"],
name: "Edit Config"
}
);
// Create a shortcut to a custom registered app
socket.fire("webapp::instance::request", "fs:createShortcut",
"input://desktop/View Logs.lnk",
{
app: "log-viewer",
args: ["input://logs/server.log"],
name: "Server Logs"
}
);fs:createShortcut writes a JSON file at the given path:
`json
{
"__shortcut": true,
"app": "photo-editor",
"args": [],
"name": "Photo Editor",
"icon": "paper/*/multimedia-photo-viewer.png"
}
`.lnk files via file-handlers.lnk file, xdg-open matches it to the shortcut runner, which:
- Reads the JSON content
- Extracts app and args
- Calls windowManager.open({ run: [app].concat(args) })
- Closes itself<button onclick="window._ws('fmSocket')&&(function(s){s.fire('webapp::instance::request','fs:mkdirp','input://shortcuts/',function(){s.fire('webapp::instance::request','fs:createShortcut','input://shortcuts/Open Photo Editor.lnk',{app:'photo-editor',args:[],name:'Photo Editor',icon:'paper//multimedia-photo-viewer.png'},function(){s.fire('webapp::instance::request','fs:createShortcut','input://shortcuts/Open Code Editor.lnk',{app:'code-editor',args:[],name:'Code Editor',icon:'breezebook-edit'},function(){s.fire('webapp::instance::request','fs:createShortcut','input://shortcuts/Open Recorder.lnk',{app:'recorder',args:[],name:'Recorder',icon:'breeze/icons/actions/16/media-record'},function(){s.fire('webapp::instance::request','fs:cwd','input://shortcuts/',function(){alert('Shortcuts created! Double-click any .lnk file to launch the app.')})})})})})})(window.fmSocket)">Try: Create App Shortcuts</button>
This creates 3 shortcuts in input://shortcuts/:
Double-click any of them to launch the corresponding app in a new window.
gui:setHomePath -- Set Default Start DirectorySets the starting directory when the file manager opens or when navigating "home".
| Arg | Type | Description |
|---|---|---|
path | string | Directory path (must be input://) |
callback | function | (err) |
socket.fire("webapp::instance::request", "gui:setHomePath", "input://my-workspace/");<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','gui:setHomePath','input://shortcuts/',function(e){alert(e||'Home path set to input://shortcuts/')})">Try: Set Home to input://shortcuts/</button>
viewport:addFiles)Inject custom clickable icons into the file manager's current view. These are purely visual — they don't create actual files in the filesystem. Each icon can launch an app, open a URL, or show a custom context menu.
viewport:addFiles -- Add Virtual Icons to Current View| Arg | Type | Description |
|---|---|---|
path | string (optional) | Directory path to show the icons in (defaults to current directory) |
files | Array | Array of virtual file definitions |
callback | function | (err, count) |
| Field | Type | Description |
|---|---|---|
name | string | Display name (required) |
icon | string | Icon name or URL |
title | string | Tooltip text |
app | string | App to launch on double-click |
args | array | Arguments for the app |
url | string | URL to open on double-click (alternative to app) |
contextmenu | array | Custom right-click menu items: [{ text, icon, app?, args?, url? }] |
// Add app launcher icons to the current directory
socket.fire("webapp::instance::request", "viewport:addFiles", [
{
name: "Open Photo Editor",
icon: "paper/*/multimedia-photo-viewer.png",
title: "Launch the Photo Editor",
app: "photo-editor",
args: []
},
{
name: "Visit SGApps.IO",
icon: "paper/icons/apps/internet-web-browser",
title: "Open website in new tab",
url: "https://sgapps.io"
},
{
name: "Project Tools",
icon: "breeze/icons/categories/32/applications-development",
title: "Development tools",
app: "code-editor",
contextmenu: [
{ text: "Open Code Editor", icon: "breeze*book-edit", app: "code-editor" },
{ text: "Open Photo Editor", icon: "paper*multimedia-photo-viewer", app: "photo-editor" },
{ text: "Open Recorder", icon: "breeze/icons/actions/16/media-record", app: "recorder" }
]
}
]);
// Add icons to a specific path
socket.fire("webapp::instance::request", "viewport:addFiles",
"input://my-folder/",
[{ name: "My App", icon: "paper/icons/apps/system-software-install", app: "my-custom-app" }]
);Virtual icons are:
<button onclick="window._ws('fmSocket')&&window.fmSocket.fire('webapp::instance::request','fs:mkdirp','input://desktop/',function(){window.fmSocket.fire('webapp::instance::request','viewport:addFiles','input://desktop/',[{name:'Photo Editor',icon:'paper//multimedia-photo-viewer.png',title:'Launch Photo Editor',app:'photo-editor',args:[]},{name:'Code Editor',icon:'breezebook-edit',title:'Launch Code Editor',app:'code-editor',args:[]},{name:'Recorder',icon:'breeze/icons/actions/16/media-record',title:'Launch Recorder',app:'recorder',args:[]},{name:'SGApps Website',icon:'paper/icons/apps/internet-web-browser',title:'Open sgapps.io',url:'https://sgapps.io'},{name:'Tools',icon:'breeze/icons/categories/32/applications-development',title:'Dev tools menu',app:'code-editor',contextmenu:[{text:'Code Editor',icon:'breezebook-edit',app:'code-editor'},{text:'Photo Editor',icon:'papermultimedia-photo-viewer',app:'photo-editor'},{text:'File Manager',icon:'breeze/icons/apps/48/system-file-manager',app:'file-manager'}]}],function(e,n){window.fmSocket.fire('webapp::instance::request','fs:cwd','input://desktop/');alert(e||('Added '+n+' virtual icons to desktop! Double-click any icon to launch the app. Right-click Tools for a menu.'))})})">Try: Add App Icons to Desktop</button>
This example creates a complete project with multiple files and directories using only the API:
<!DOCTYPE html>
<html>
<head><title>File Manager API Demo</title></head>
<body>
<div style="display:flex;gap:8px;padding:10px;flex-wrap:wrap;">
<button id="create-project">Create Project</button>
<button id="list-files">List Files</button>
<button id="read-file">Read package.json</button>
</div>
<iframe id="fm" src="https://sgapps.io/online/webapp/file-manager"
width="100%" height="500" frameborder="0"></iframe>
<pre id="output" style="background:#f6f8fa;padding:10px;max-height:200px;overflow:auto;"></pre>
<script src="https://sgapps.io/components/application-prototype/ApplicationPrototype.js"></script>
<script src="https://sgapps.io/components/window-socket/index.js"></script>
<script>
var socket = new WindowSocket();
socket.start();
var ready = false;
socket.on("webapp::connection::ping", function () { ready = true; });
function req(event) {
var args = Array.prototype.slice.call(arguments, 1);
return new Promise(function (resolve, reject) {
args.push(function (err, result) {
if (err) reject(err); else resolve(result);
});
socket.fire.apply(socket, ["webapp::instance::request", event].concat(args));
});
}
document.getElementById('create-project').onclick = async function () {
if (!ready) return alert("Not ready yet");
await req("fs:mkdirp", "input://my-app/src/");
await req("fs:mkdirp", "input://my-app/test/");
await req("fs:write", "input://my-app/package.json",
JSON.stringify({ name: "my-app", version: "1.0.0", main: "src/index.js" }, null, 2));
await req("fs:write", "input://my-app/src/index.js",
"module.exports = function greet(name) {\n return 'Hello, ' + name + '!';\n};");
await req("fs:write", "input://my-app/test/test.js",
"var greet = require('../src/index');\nconsole.log(greet('World'));");
await req("fs:write", "input://my-app/README.md",
"# my-app\n\nA demo project created via the File Manager API.");
await req("fs:cwd", "input://my-app/");
document.getElementById('output').textContent = "Project created!";
};
document.getElementById('list-files').onclick = async function () {
if (!ready) return;
var files = await req("fs:ls", "input://my-app/");
document.getElementById('output').textContent = files.map(function (f) {
return (f.metadata._isDirectory ? "[DIR] " : "[FILE] ") + f.filename;
}).join("\n");
};
document.getElementById('read-file').onclick = async function () {
if (!ready) return;
var content = await req("fs:read", "input://my-app/package.json");
document.getElementById('output').textContent = content;
};
</script>
</body>
</html>input://) for temporary files