Skip to content

Commit 6fb46dc

Browse files
Drkjr92Prozilla
andauthored
Ability to download files from the virtual file system
* add download functionality for files via FileExplorer * make sure we only download files. valid files to download have extensions * Moved code for downloading to virtual file * Added support for non-text file downloads --------- Co-authored-by: Prozilla <[email protected]>
1 parent a746d47 commit 6fb46dc

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

packages/apps/file-explorer/src/components/FileExplorer.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ChangeEventHandler, FC, KeyboardEventHandler, useCallback, useEffect, useState } from "react";
22
import styles from "./FileExplorer.module.css";
33
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4-
import { faArrowUp, faCaretLeft, faCaretRight, faCircleInfo, faCog, faDesktop, faFileLines, faHouse, faImage, faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons";
4+
import { faArrowUp, faCaretLeft, faCaretRight, faCircleInfo, faCog, faDesktop, faFileLines, faHouse, faImage, faPlus, faSearch, faTrash, faUpload } from "@fortawesome/free-solid-svg-icons";
55
import { QuickAccessButton } from "./QuickAccessButton";
66
import { ImportButton } from "./ImportButton";
77
import { Actions, ClickAction, CODE_EXTENSIONS, DialogBox, DirectoryList, Divider, FileEventHandler, FolderEventHandler, ModalProps, ModalsConfig, OnSelectionChangeParams, useAlert, useContextMenu, useHistory, useSystemManager, useVirtualRoot, useWindowedModal, useWindowsManager, utilStyles, Vector2, VirtualFile, VirtualFolder, VirtualFolderLink, VirtualRoot, WindowProps } from "@prozilla-os/core";
@@ -41,6 +41,11 @@ export function FileExplorer({ app, path: startPath, selectorMode, Footer, onSel
4141
}
4242
if (windowsManager != null) (file as VirtualFile).open(windowsManager);
4343
}}/>
44+
{(props.triggerParams as VirtualFile)?.isDownloadable() &&
45+
<ClickAction label="Export" icon={faUpload} onTrigger={(_event, file) => {
46+
(file as VirtualFile).download();
47+
}}/>
48+
}
4449
<ClickAction label="Delete" icon={faTrash} onTrigger={(_event, file) => {
4550
(file as VirtualFile).delete();
4651
}}/>

packages/core/src/features/_utils/browser.utils.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,17 @@ export function removeBaseUrl(url: string) {
117117

118118
export function copyToClipboard(string: string, onSuccess?: (value: void) => void, onFail?: (value: void) => void) {
119119
void navigator.clipboard.writeText(string).then(onSuccess, onFail);
120+
}
121+
122+
export function downloadUrl(url: string, name: string) {
123+
// Create invisible anchor element with download URL
124+
const anchor = document.createElement("a");
125+
anchor.href = url;
126+
anchor.download = name;
127+
anchor.style.display = "none";
128+
129+
// Click anchor element
130+
document.body.appendChild(anchor);
131+
anchor.click();
132+
document.body.removeChild(anchor);
120133
}

packages/core/src/features/virtual-drive/file/virtualFile.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { FILE_SCHEMES, IMAGE_EXTENSIONS } from "../../../constants/virtualDrive.const";
2+
import { downloadUrl } from "../../_utils";
23
import { WindowsManager } from "../../windows/windowsManager";
34
import { VirtualBase, VirtualBaseJson } from "../virtualBase";
45

@@ -187,6 +188,34 @@ export class VirtualFile extends VirtualBase {
187188
return `${type} file (.${this.extension.toLowerCase()})`.trim();
188189
}
189190

191+
download() {
192+
if (!this.isDownloadable()) {
193+
return;
194+
}
195+
196+
try {
197+
if (this.source != null) {
198+
downloadUrl(this.source, this.id);
199+
} else if (this.content != null) {
200+
const blob = new Blob([this.content], { type: "text/plain" });
201+
const url = window.URL.createObjectURL(blob);
202+
downloadUrl(url, this.id);
203+
window.URL.revokeObjectURL(url);
204+
}
205+
} catch (error) {
206+
console.error("Error while downloading file:", error);
207+
}
208+
}
209+
210+
isDownloadable(): boolean {
211+
if (this.content != null) {
212+
return true;
213+
} else if (this.source != null) {
214+
return !this.source.startsWith(FILE_SCHEMES.external) && !this.source.startsWith(FILE_SCHEMES.app);
215+
}
216+
return false;
217+
}
218+
190219
toJSON(): VirtualFileJson | null {
191220
// Don't return file if it can't or hasn't been edited
192221
if (!this.canBeEdited || (this.editedByUser == null || !this.editedByUser))

0 commit comments

Comments
 (0)