View and edit archive contents (ZIP, TAR, TAR.GZ, 7Z, RAR) in the browser. Files are extracted to in-memory storage, editable with the built-in file manager, and can be saved back.
<iframe id="av-demo" src="/online/webapp/archive-viewer" width="100%" height="500" frameborder="0" style="border:1px solid #ccc; border-radius:4px;"></iframe>
<script>window._wsConnect('av-demo', 'avSocket');</script>
<iframe src="https://sgapps.io/online/webapp/archive-viewer"
width="100%" height="600" frameborder="0"></iframe>var archiveUrl = "https://example.com/project.zip";
var src = "https://sgapps.io/online/webapp/archive-viewer/url/" + btoa(archiveUrl);input:// protocol)| Format | Read | Write |
|---|---|---|
| ZIP | Yes | Yes |
| TAR | Yes | Yes |
| TAR.GZ | Yes | Yes |
| 7Z | Yes | Yes |
| RAR | Yes | No (proprietary) |
| GZIP | Yes | Yes |
The archive viewer inherits all File Manager filesystem events. Since the archive is extracted to input://, you can use these events to programmatically add, modify, or remove files in the archive.
// Write a new file into the extracted archive
socket.fire("webapp::instance::request", "fs:write",
"input://.tmp/archive-viewer/SESSION_ID/new-file.txt",
"This file was added via the API!",
function (err) { console.log(err || "File added to archive"); }
);
// Refresh the view to see the new file
socket.fire("webapp::instance::request", "fs:refresh");<button onclick="window._ws('avSocket')&&window.avSocket.fire('webapp::instance::request','fs:cwd',function(e,cwd){if(cwd){window.avSocket.fire('webapp::instance::request','fs:write',cwd+'api-test.txt','Created via Archive Viewer API at '+new Date().toISOString(),function(e2){if(!e2){window.avSocket.fire('webapp::instance::request','fs:refresh');alert('File api-test.txt added to archive!')}else alert('Error: '+e2)})}else alert('Navigate to archive first')})">Try: Add api-test.txt to Current Archive</button>
socket.fire("webapp::instance::request", "fs:read",
"input://.tmp/archive-viewer/SESSION_ID/some-file.txt",
function (err, content) { console.log(content); }
);<button onclick="window.ws('avSocket')&&window.avSocket.fire('webapp::instance::request','fs:cwd',function(e,cwd){if(cwd){window.avSocket.fire('webapp::instance::request','fs:ls',cwd,function(e2,files){if(files&&files.length){var first=files.find(function(f){return !f.metadata.isDirectory});if(first){window.avSocket.fire('webapp::instance::request','fs:read',first.filename,function(e3,content){alert('File: '+first.filename.replace(/^.*\//,'')+'\n\nContent:\n'+(content||'(binary)').substring(0,500))})}else alert('No files found')}else alert('Empty directory')})}})">Try: Read First File in Archive</button>
socket.fire("webapp::instance::request", "fs:ls", "input://.tmp/archive-viewer/SESSION_ID/",
function (err, files) {
files.forEach(function (f) {
console.log(f.metadata._isDirectory ? "[DIR]" : "[FILE]", f.filename);
});
}
);<button onclick="window.ws('avSocket')&&window.avSocket.fire('webapp::instance::request','fs:cwd',function(e,cwd){if(cwd){window.avSocket.fire('webapp::instance::request','fs:ls',cwd,function(e2,files){if(files){alert('Archive contents ('+files.length+' items):\n\n'+files.map(function(f){return (f.metadata.isDirectory?'[DIR] ':'[FILE] ')+f.filename.replace(/^.*\/(?=[^\/]+\/?$)/,'')}).join('\n'))}else alert('Error: '+(e2||'empty'))})}})">Try: List Archive Contents</button>
socket.fire("webapp::instance::request", "fs:mkdirp",
"input://.tmp/archive-viewer/SESSION_ID/new-folder/sub-folder/",
function (err) {
socket.fire("webapp::instance::request", "fs:refresh");
}
);<button onclick="window._ws('avSocket')&&window.avSocket.fire('webapp::instance::request','fs:cwd',function(e,cwd){if(cwd){window.avSocket.fire('webapp::instance::request','fs:mkdirp',cwd+'new-folder/sub-folder/',function(e2){if(!e2){window.avSocket.fire('webapp::instance::request','fs:refresh');alert('Created new-folder/sub-folder/')}else alert('Error: '+e2)})}})">Try: Create new-folder/sub-folder/</button>
The footer has a split Download button. The main button downloads in the current archive's format; the ▾ caret opens a dropdown with all writable formats.
| Format | Extension | Notes |
|---|---|---|
| ZIP | .zip | DEFLATE compressed |
| TAR | .tar | Uncompressed POSIX ustar |
| TAR.GZ | .tar.gz | TAR + GZIP |
| GZIP | .gz | Single-file format — uses only the first entry |
| 7Z | .7z | LZMA compressed |
project.zip → project.tar.gz when downloaded as TAR.GZ). A newly-created empty archive defaults to archive.<ext>.<!DOCTYPE html>
<html>
<head><title>Archive Editor Widget</title></head>
<body>
<div style="display:flex;gap:8px;padding:10px;flex-wrap:wrap;">
<button id="add-file">Add README.md</button>
<button id="list">List Contents</button>
</div>
<iframe id="viewer" src="https://sgapps.io/online/webapp/archive-viewer"
width="100%" height="600" frameborder="0"></iframe>
<pre id="output" style="background:#f6f8fa;padding:10px;"></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;
socket.fire("webapp::instance::embed-mode", true);
});
document.getElementById('add-file').onclick = function () {
if (!ready) return;
// Get the current archive extraction path
socket.fire("webapp::instance::request", "fs:cwd", function (err, cwd) {
// Write a file into the archive
socket.fire("webapp::instance::request", "fs:write",
cwd + "README.md",
"# Archive Contents\n\nThis file was added programmatically.",
function (err) {
socket.fire("webapp::instance::request", "fs:refresh");
document.getElementById('output').textContent = err || "README.md added!";
}
);
});
};
document.getElementById('list').onclick = function () {
if (!ready) return;
socket.fire("webapp::instance::request", "fs:cwd", function (err, cwd) {
socket.fire("webapp::instance::request", "fs:ls", cwd, function (err, files) {
document.getElementById('output').textContent = files.map(function (f) {
return (f.metadata._isDirectory ? "[DIR] " : "[FILE] ") +
f.filename.replace(/^.*\/(?=[^\/]+\/?$)/, '');
}).join("\n");
});
});
};
</script>
</body>
</html>