Skip to content

Commit

Permalink
feat: download zip (#137)
Browse files Browse the repository at this point in the history
* feat: download zip

* fix: disable data descriptor

Some applications like Dolphin fail to open it otherwise
  • Loading branch information
j4k0xb authored Feb 19, 2025
1 parent ed12b19 commit bf7e16f
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 5 deletions.
1 change: 0 additions & 1 deletion apps/docs/src/guide/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,5 @@ to resemble the original source code as much as possible.
- Smarter variable renaming, possibly with LLMs like GPT-3.5
- Support older obfuscator.io versions
- Unpack multi-chunk bundles
- Download zip of all unpacked modules in the playground
- Decompile [@babel/preset-env](https://babeljs.io/docs/babel-preset-env) helpers
- Decompile TypeScript helpers, modules and enums
1 change: 1 addition & 0 deletions apps/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"dependencies": {
"@babel/generator": "^7.26.0",
"@babel/types": "^7.26.0",
"@zip.js/zip.js": "^2.7.57",
"idb": "^8.0.0",
"monaco-editor": "^0.52.0",
"sandybox": "^1.1.2",
Expand Down
9 changes: 7 additions & 2 deletions apps/playground/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function App() {
monaco.editor.createModel(
'',
'javascript',
monaco.Uri.from({ scheme: 'untitled', path: 'Untitled-1' }),
monaco.Uri.from({ scheme: 'untitled', path: 'Untitled-1.js' }),
),
]);
const [tabs, setTabs] = createSignal<monaco.editor.ITextModel[]>(models());
Expand Down Expand Up @@ -131,7 +131,7 @@ function App() {
'javascript',
monaco.Uri.from({
scheme: 'untitled',
path: `Untitled-${untitledCounter()}`,
path: `Untitled-${untitledCounter()}.js`,
}),
);
setModels([...models(), model]);
Expand Down Expand Up @@ -226,6 +226,11 @@ function App() {
onSave={() => {
if (activeTab()) downloadFile(activeTab()!);
}}
onSaveAll={() => {
import('./utils/zip.js')
.then((module) => module.downloadModelsZIP(models()))
.catch(console.error);
}}
onRestore={(workspace) => {
restoreWorkspace(workspace).catch(console.error);
}}
Expand Down
2 changes: 2 additions & 0 deletions apps/playground/src/components/menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface Props {
onFileOpen?: (content: string) => void;
onLoadFromURL?: (url: string) => void;
onSave?: () => void;
onSaveAll?: () => void;
onRestore?: (workspace: Workspace) => void;
}

Expand Down Expand Up @@ -77,6 +78,7 @@ export default function Menu(props: Props) {
<MenuButton shortcut={[ctrlCmdIcon, 'S']} onClick={props.onSave}>
Save
</MenuButton>
<MenuButton onClick={props.onSaveAll}>Save All (.zip)</MenuButton>
</MenuHeader>
<MenuHeader
title="Settings"
Expand Down
6 changes: 5 additions & 1 deletion apps/playground/src/utils/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ export function downloadFile(model: monaco.editor.ITextModel) {
const blob = new Blob([model.getValue()], {
type: 'application/javascript;charset=utf-8',
});
downloadBlob(blob, basename(model.uri.fsPath));
}

export function downloadBlob(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');

link.setAttribute('href', url);
link.setAttribute('download', basename(model.uri.fsPath));
link.setAttribute('download', filename);
link.style.display = 'none';
document.body.appendChild(link);
link.click();
Expand Down
20 changes: 20 additions & 0 deletions apps/playground/src/utils/zip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { BlobWriter, TextReader, ZipWriter } from '@zip.js/zip.js';
import type * as monaco from 'monaco-editor';
import { downloadBlob } from './files.js';

export async function downloadModelsZIP(models: monaco.editor.ITextModel[]) {
const zipWriter = new ZipWriter(new BlobWriter('application/zip'), {
dataDescriptor: false,
});

await Promise.all(
models.map((model) => {
const path = model.uri.fsPath.replace(/^\//, '');
const reader = new TextReader(model.getValue());
return zipWriter.add(path, reader);
}),
);

const zipBlob = await zipWriter.close();
downloadBlob(zipBlob, 'webcrack-export.zip');
}
12 changes: 11 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit bf7e16f

Please sign in to comment.