securityos/components/system/Files/FileEntry/useFileContextMenu.ts

544 lines
18 KiB
TypeScript
Raw Normal View History

2024-09-06 15:32:35 +00:00
import extensions, {
TEXT_EDITORS,
} from "components/system/Files/FileEntry/extensions";
import { getProcessByFileExtension } from "components/system/Files/FileEntry/functions";
import useFile from "components/system/Files/FileEntry/useFile";
import type { FocusEntryFunctions } from "components/system/Files/FileManager/useFocusableEntries";
import type { FileActions } from "components/system/Files/FileManager/useFolder";
import { useFileSystem } from "contexts/fileSystem";
import { useMenu } from "contexts/menu";
import type {
ContextMenuCapture,
MenuItem,
} from "contexts/menu/useMenuContextState";
import { useProcesses } from "contexts/process";
import processDirectory from "contexts/process/directory";
import { useSession } from "contexts/session";
import { basename, dirname, extname, join } from "path";
import { useMemo } from "react";
import {
AUDIO_PLAYLIST_EXTENSIONS,
DESKTOP_PATH,
EDITABLE_IMAGE_FILE_EXTENSIONS,
EXTRACTABLE_EXTENSIONS,
IMAGE_FILE_EXTENSIONS,
MENU_SEPERATOR,
MOUNTABLE_EXTENSIONS,
ROOT_SHORTCUT,
SHORTCUT_EXTENSION,
SPREADSHEET_FORMATS,
UNSUPPORTED_BACKGROUND_EXTENSIONS,
} from "utils/constants";
import {
AUDIO_DECODE_FORMATS,
AUDIO_ENCODE_FORMATS,
VIDEO_DECODE_FORMATS,
VIDEO_ENCODE_FORMATS,
} from "utils/ffmpeg/formats";
import type { FFmpegTranscodeFile } from "utils/ffmpeg/types";
import { isFirefox } from "utils/functions";
import {
IMAGE_DECODE_FORMATS,
IMAGE_ENCODE_FORMATS,
} from "utils/imagemagick/formats";
import type { ImageMagickConvertFile } from "utils/imagemagick/types";
import type { URLTrack } from "webamp";
const useFileContextMenu = (
url: string,
pid: string,
path: string,
setRenaming: React.Dispatch<React.SetStateAction<string>>,
{
archiveFiles,
deleteLocalPath,
downloadFiles,
extractFiles,
newShortcut,
}: FileActions,
{ blurEntry, focusEntry }: FocusEntryFunctions,
focusedEntries: string[],
fileManagerId?: string,
readOnly?: boolean
): ContextMenuCapture => {
const { open, url: changeUrl } = useProcesses();
const { setWallpaper } = useSession();
const baseName = basename(path);
const isFocusedEntry = focusedEntries.includes(baseName);
const openFile = useFile(url);
const {
copyEntries,
createPath,
lstat,
mapFs,
moveEntries,
readFile,
rootFs,
unMapFs,
updateFolder,
} = useFileSystem();
const { contextMenu } = useMenu();
const { onContextMenuCapture, ...contextMenuHandlers } = useMemo(
() =>
contextMenu?.(() => {
const urlExtension = extname(url).toLowerCase();
const { process: extensionProcesses = [] } =
urlExtension in extensions ? extensions[urlExtension] : {};
const openWith = extensionProcesses.filter(
(process) => process !== pid
);
const openWithFiltered = openWith.filter((id) => id !== pid);
const absoluteEntries = (): string[] =>
focusedEntries.length === 1 || !isFocusedEntry
? [path]
: [
...new Set([
path,
...focusedEntries.map((entry) => join(dirname(path), entry)),
]),
];
const menuItems: MenuItem[] = [];
const pathExtension = extname(path).toLowerCase();
const isShortcut = pathExtension === SHORTCUT_EXTENSION;
const remoteMount = rootFs?.mountList.some(
(mountPath) =>
mountPath === path &&
rootFs?.mntMap[mountPath]?.getName() === "FileSystemAccess"
);
if (!readOnly && !remoteMount) {
const defaultProcess = getProcessByFileExtension(urlExtension);
menuItems.push(
{ action: () => moveEntries(absoluteEntries()), label: "Cortar" },
{ action: () => copyEntries(absoluteEntries()), label: "Copiar" },
MENU_SEPERATOR
);
if (
defaultProcess ||
isShortcut ||
(!pathExtension && !urlExtension)
) {
menuItems.push({
action: () =>
absoluteEntries().forEach(async (entry) => {
const shortcutProcess =
defaultProcess && !(await lstat(entry)).isDirectory()
? defaultProcess
: "FileExplorer";
newShortcut(entry, shortcutProcess);
}),
label: "Criar Atalho",
});
}
menuItems.push(
{
action: () =>
absoluteEntries().forEach((entry) => deleteLocalPath(entry)),
label: "Deletar",
},
{ action: () => setRenaming(baseName), label: "Renomear" }
);
if (path) {
if (path === join(DESKTOP_PATH, ROOT_SHORTCUT)) {
if (typeof FileSystemHandle === "function") {
const mapFileSystemDirectory = (
directory: string,
existingHandle?: FileSystemDirectoryHandle
): void => {
mapFs(directory, existingHandle)
.then((mappedFolder) => {
updateFolder("/", mappedFolder);
open("FileExplorer", {
url: join("/", mappedFolder),
});
})
.catch(() => {
// Ignore failure to map
});
};
const showMapDirectory = "showDirectoryPicker" in window;
const showMapOpfs =
typeof navigator.storage?.getDirectory === "function" &&
!isFirefox();
menuItems.unshift(
...(showMapDirectory
? [
{
action: () => mapFileSystemDirectory("/"),
label: "Mapear Diretório",
},
]
: []),
...(showMapOpfs
? [
{
action: async () => {
try {
mapFileSystemDirectory(
"/OPFS",
await navigator.storage.getDirectory()
);
} catch {
// Ignore failure to map directory
}
},
label: "Mapear OPFS",
},
]
: []),
...(showMapDirectory || showMapOpfs ? [MENU_SEPERATOR] : [])
);
}
} else {
menuItems.unshift(MENU_SEPERATOR);
if (
EXTRACTABLE_EXTENSIONS.has(pathExtension) ||
MOUNTABLE_EXTENSIONS.has(pathExtension)
) {
menuItems.unshift({
action: () => extractFiles(path),
label: "Extrair Aqui",
});
}
const canDecodeAudio = AUDIO_DECODE_FORMATS.has(pathExtension);
const canDecodeImage = IMAGE_DECODE_FORMATS.has(pathExtension);
const canDecodeVideo = VIDEO_DECODE_FORMATS.has(pathExtension);
if (canDecodeAudio || canDecodeImage || canDecodeVideo) {
const isAudioVideo = canDecodeAudio || canDecodeVideo;
const ENCODE_FORMATS = isAudioVideo
? canDecodeAudio
? AUDIO_ENCODE_FORMATS
: VIDEO_ENCODE_FORMATS
: IMAGE_ENCODE_FORMATS;
menuItems.unshift(MENU_SEPERATOR, {
label: "Converter para",
menu: ENCODE_FORMATS.filter(
(format) => format !== pathExtension
).map((format) => {
const extension = format.replace(".", "");
return {
action: async () => {
const transcodeFiles: (
| FFmpegTranscodeFile
| ImageMagickConvertFile
)[] = await Promise.all(
absoluteEntries().map(async (absoluteEntry) => [
absoluteEntry,
await readFile(absoluteEntry),
])
);
const transcodeFunction = isAudioVideo
? (await import("utils/ffmpeg")).transcode
: (await import("utils/imagemagick")).convert;
const transcodedFiles = await transcodeFunction(
transcodeFiles,
extension
);
await Promise.all(
transcodedFiles.map(
async ([
transcodedFileName,
transcodedFileData,
]) => {
const baseTranscodedName =
basename(transcodedFileName);
const transcodedDirName = dirname(path);
updateFolder(
transcodedDirName,
await createPath(
baseTranscodedName,
transcodedDirName,
transcodedFileData
)
);
}
)
);
},
label: extension.toUpperCase(),
};
}),
});
}
const canDecodeSpreadsheet =
SPREADSHEET_FORMATS.includes(pathExtension);
if (canDecodeSpreadsheet) {
menuItems.unshift(MENU_SEPERATOR, {
label: "Converter para",
menu: SPREADSHEET_FORMATS.filter(
(format) => format !== pathExtension
).map((format) => {
const extension = format.replace(".", "");
return {
action: () => {
absoluteEntries().forEach(async (absoluteEntry) => {
const newFilePath = `${dirname(
absoluteEntry
)}/${basename(
absoluteEntry,
extname(absoluteEntry)
)}.${extension}`;
const { convertSheet } = await import(
"utils/sheetjs"
);
const workBook = await convertSheet(
await readFile(absoluteEntry),
extension
);
const workBookDirName = dirname(path);
updateFolder(
workBookDirName,
await createPath(
basename(newFilePath),
workBookDirName,
Buffer.from(workBook)
)
);
});
},
label: extension.toUpperCase(),
};
}),
});
}
const canEncodePlaylist =
pathExtension !== ".m3u" &&
AUDIO_PLAYLIST_EXTENSIONS.has(pathExtension);
if (canEncodePlaylist) {
menuItems.unshift(MENU_SEPERATOR, {
action: () => {
absoluteEntries().forEach(async (absoluteEntry) => {
const newFilePath = `${dirname(absoluteEntry)}/${basename(
absoluteEntry,
extname(absoluteEntry)
)}.m3u`;
const { createM3uPlaylist, tracksFromPlaylist } =
await import("components/apps/Webamp/functions");
const playlist = createM3uPlaylist(
(await tracksFromPlaylist(
(await readFile(absoluteEntry)).toString(),
extname(absoluteEntry)
)) as URLTrack[]
);
const playlistDirName = dirname(path);
updateFolder(
playlistDirName,
await createPath(
basename(newFilePath),
playlistDirName,
Buffer.from(playlist)
)
);
});
},
label: "Converter para M3U",
});
}
menuItems.unshift(
{
action: () => archiveFiles(absoluteEntries()),
label: "Adicionar ao arquivo...",
},
{
action: () => downloadFiles(absoluteEntries()),
label: "Download",
}
);
if (!isShortcut && pid !== "FileExplorer") {
TEXT_EDITORS.forEach((textEditor) => {
if (
textEditor !== defaultProcess &&
!openWithFiltered.includes(textEditor)
) {
openWithFiltered.push(textEditor);
}
});
}
}
}
menuItems.unshift(MENU_SEPERATOR);
}
if (remoteMount) {
menuItems.push(MENU_SEPERATOR, {
action: () => unMapFs(path),
label: "Desconectar",
});
}
if (EDITABLE_IMAGE_FILE_EXTENSIONS.has(urlExtension)) {
menuItems.unshift({
action: () => {
open("Paint", { url });
},
label: "Editar",
});
}
if (
IMAGE_FILE_EXTENSIONS.has(pathExtension) &&
!UNSUPPORTED_BACKGROUND_EXTENSIONS.has(pathExtension)
) {
menuItems.unshift({
label: "Definir como Wallpaper",
menu: [
{
action: () => setWallpaper(path, "fill"),
label: "Preencher",
},
{
action: () => setWallpaper(path, "fit"),
label: "Ajustar",
},
{
action: () => setWallpaper(path, "stretch"),
label: "Esticar",
},
{
action: () => setWallpaper(path, "tile"),
label: "Lado a lado",
},
{
action: () => setWallpaper(path, "center"),
label: "Centralizar",
},
],
});
}
if (openWithFiltered.length > 0) {
menuItems.unshift({
label: "Abrir com",
menu: openWithFiltered.map((id): MenuItem => {
const { icon, title: label } = processDirectory[id] || {};
const action = (): void => {
openFile(id, icon);
};
return { action, icon, label };
}),
});
}
if (pid) {
const { icon: pidIcon } = processDirectory[pid] || {};
if (
isShortcut &&
url &&
url !== "/" &&
!url.startsWith("http:") &&
!url.startsWith("https:")
) {
const isFolder = urlExtension === "" || urlExtension === ".zip";
menuItems.unshift({
action: () => open("FileExplorer", { url: dirname(url) }, ""),
label: `Abrir ${isFolder ? "pasta" : "file"} local`,
});
}
if (
fileManagerId &&
pid === "FileExplorer" &&
!MOUNTABLE_EXTENSIONS.has(urlExtension)
) {
menuItems.unshift({
action: () => {
openFile(pid, pidIcon);
},
label: "Abrir em nova janela",
});
}
menuItems.unshift({
action: () => {
if (
pid === "FileExplorer" &&
fileManagerId &&
!MOUNTABLE_EXTENSIONS.has(urlExtension)
) {
changeUrl(fileManagerId, url);
} else {
openFile(pid, pidIcon);
}
},
icon: pidIcon,
label: "Abrir",
primary: true,
});
}
return menuItems;
}),
[
archiveFiles,
baseName,
changeUrl,
contextMenu,
copyEntries,
createPath,
deleteLocalPath,
downloadFiles,
extractFiles,
fileManagerId,
focusedEntries,
isFocusedEntry,
lstat,
mapFs,
moveEntries,
newShortcut,
open,
openFile,
path,
pid,
readFile,
readOnly,
rootFs?.mntMap,
rootFs?.mountList,
setRenaming,
setWallpaper,
unMapFs,
updateFolder,
url,
]
);
return {
onContextMenuCapture: (event?: React.MouseEvent | React.TouchEvent) => {
if (!isFocusedEntry) {
blurEntry();
focusEntry(baseName);
}
onContextMenuCapture(event);
},
...contextMenuHandlers,
};
};
export default useFileContextMenu;