[feat] added blacklist for shared folders

* change BasicModal data to handle cascading modals better
This commit is contained in:
silentrald
2024-12-11 22:28:07 +08:00
parent 0f5ea88a27
commit 65fb281a64
5 changed files with 148 additions and 44 deletions
+8
View File
@@ -601,6 +601,10 @@
"ENOSPC": "The disk is full, make some space and try again.",
"UNKNOWN_ERROR": "An unknown error has occurred while linking the folder."
}
},
"adding-error": {
"title": "Adding shared folder failed",
"msg": "You can't add \"{folder}\" to shared folders"
}
},
"create-launch-shortcut": {
@@ -805,6 +809,10 @@
"link-all": "Link all"
}
},
"adding-shared-folder": {
"title": "Adding Shared Folder",
"description": "Are you sure you want to add \"{folder}\" to shared folder?"
},
"create-launch-shortcut": {
"title": "Create a shortcut",
"desc": "Creating a shortcut will allow you to start Beat Saber with the chosen options without going through BSManager.",
@@ -1,7 +1,7 @@
import { BsmButton, BsmButtonType } from "renderer/components/shared/bsm-button.component";
import { BsmImage } from "renderer/components/shared/bsm-image.component";
import { cn } from "renderer/helpers/css-class.helpers";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
type BasicModalOptions = {
@@ -12,7 +12,11 @@ type BasicModalOptions = {
id: string;
text: string;
type: BsmButtonType,
isCancel?: boolean;
/**
* true - ModalExitCode.COMPLETED
* undefined/false - ModalExitCode.CANCELED
*/
onClick?: () => boolean;
}[];
buttonsLayout?: "row" | "column";
};
@@ -21,20 +25,32 @@ export const BasicModal: ModalComponent<BasicModalOptions["buttons"][0]["id"], B
data: { title, image, body, buttons, buttonsLayout = "column" }
} }) => {
const t = useTranslation();
const t = useTranslationV2();
const handleClick = (button: BasicModalOptions["buttons"][0]) => {
resolver({ exitCode: button.isCancel ? ModalExitCode.CANCELED : ModalExitCode.COMPLETED, data: button.id });
resolver({
exitCode: button.onClick?.()
? ModalExitCode.COMPLETED
: ModalExitCode.CANCELED,
data: button.id
});
}
return (
<form className="text-gray-900 dark:text-white">
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(title)}</h1>
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t.text(title)}</h1>
<BsmImage className="mx-auto h-24" image={image} />
{ body && <p className="w-full">{t(body)}</p> }
{ body && <p className="w-full">{t.text(body)}</p> }
<div className={cn("grid gap-2 mt-4")} style={{ gridAutoFlow: buttonsLayout, ...(buttonsLayout === "row" ? { gridTemplateRows: `repeat(${buttons.length}, 1fr)` } : { gridTemplateColumns: `repeat(${buttons.length}, 1fr)` }) }}>
{buttons.map(button => (
<BsmButton key={button.id} typeColor={button.type} className="h-8 rounded-md text-center flex justify-center items-center" onClick={() => handleClick(button)} withBar={false} text={button.text} />
<BsmButton
key={button.id}
typeColor={button.type}
className="h-8 rounded-md text-center flex justify-center items-center"
onClick={() => handleClick(button)}
withBar={false}
text={button.text}
/>
))}
</div>
</form>
@@ -6,30 +6,32 @@ import { BsmButton } from "renderer/components/shared/bsm-button.component";
import { useObservable } from "renderer/hooks/use-observable.hook";
import { useService } from "renderer/hooks/use-service.hook";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
import { ConfigurationService } from "renderer/services/configuration.service";
import { IpcService } from "renderer/services/ipc.service";
import { ModalComponent } from "renderer/services/modale.service";
import { ModalComponent, ModalService } from "renderer/services/modale.service";
import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionType } from "renderer/services/version-folder-linker.service";
import { lastValueFrom } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
import { SHARED_FOLDER_BLACKLIST } from "renderer/config/default-configuration.config";
import { NotificationService } from "renderer/services/notification.service";
import { BasicModal } from "../basic-modal.component";
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {data} }) => {
const SHARED_FOLDERS_KEY = "default-shared-folders";
const SHARED_FOLDERS_KEY = "default-shared-folders";
export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: { data: version } }) => {
const config = useService(ConfigurationService);
const ipc = useService(IpcService);
const linker = useService(VersionFolderLinkerService);
const versionManager = useService(BSVersionManagerService);
const t = useTranslation();
const t = useTranslationV2();
const [folders, setFolders] = useState<string[]>(Array.from(new Set([...config.get<string[]>(SHARED_FOLDERS_KEY)]).values()));
useEffect(() => {
linker
.getLinkedFolders(data, { relative: true })
.getLinkedFolders(version, { relative: true })
.toPromise()
.then(linkedFolders => {
setFolders(prev => Array.from(new Set([...prev, ...linkedFolders]).values()));
@@ -45,42 +47,24 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {d
config.set(SHARED_FOLDERS_KEY, folders);
}, [folders]);
const addFolder = async () => {
const versionPath = await lastValueFrom(versionManager.getVersionPath(data));
const folder = await lastValueFrom(ipc.sendV2("choose-folder", {
defaultPath: versionPath
}));
if (!folder || folder.canceled || !folder.filePaths?.length) {
return;
}
const relativeFolder = await lastValueFrom(ipc.sendV2("full-version-path-to-relative", { version: data, fullPath: folder.filePaths[0] }));
if (folders.includes(relativeFolder)) {
return;
}
setFolders(pre => [...pre, relativeFolder]);
};
const removeFolder = (index: number) => {
setFolders(prev => prev.filter((_, i) => i !== index));
};
const linkAll = () => {
folders.forEach(relativeFolder => linker.linkVersionFolder({ version: data, relativeFolder, type: VersionLinkerActionType.Link }));
folders.forEach(relativeFolder => linker.linkVersionFolder({ version, relativeFolder, type: VersionLinkerActionType.Link }));
};
return (
<form className="w-full max-w-md ">
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t("modals.shared-folders.title")}</h1>
<p className="my-3">{t("modals.shared-folders.description")}</p>
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t.text("modals.shared-folders.title")}</h1>
<p className="my-3">{t.text("modals.shared-folders.description")}</p>
<ul className="flex flex-col gap-1 mb-2 h-[300px] max-h-[300px] overflow-scroll scrollbar-default px-1">
{folders.map((folder, index) => (
<FolderItem
key={folder}
version={data}
version={version}
relativeFolder={folder}
onDelete={() => {
removeFolder(index);
@@ -89,13 +73,93 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {d
))}
</ul>
<div className="grid grid-flow-col gap-3 grid-cols-2">
<BsmButton icon="add" className="h-8 rounded-md flex justify-center items-center font-bold bg-light-main-color-1 dark:bg-main-color-1" iconClassName="h-6 aspect-square text-current" onClick={addFolder} withBar={false} text="modals.shared-folders.buttons.add-folder" />
<AddFolderButton
version={version}
folders={folders}
setFolders={setFolders}
/>
<BsmButton icon="link" className="h-8 rounded-md flex justify-center items-center font-bold" typeColor="primary" iconClassName="h-6 aspect-square text-current -rotate-45" onClick={linkAll} withBar={false} text="modals.shared-folders.buttons.link-all" />
</div>
</form>
);
};
function AddFolderButton({
version,
folders,
setFolders,
}: Readonly<{
version: BSVersion;
folders: string[];
setFolders: (value: string[]) => void;
}>) {
const config = useService(ConfigurationService);
const ipc = useService(IpcService);
const versionManager = useService(BSVersionManagerService);
const notification = useService(NotificationService);
const modal = useService(ModalService);
const t = useTranslationV2();
const addFolder = async () => {
const versionPath = await lastValueFrom(versionManager.getVersionPath(version));
const folder = await lastValueFrom(ipc.sendV2("choose-folder", {
defaultPath: versionPath
}));
if (!folder || folder.canceled || !folder.filePaths?.length) {
return;
}
const relativeFolder = await lastValueFrom(ipc.sendV2("full-version-path-to-relative", { version, fullPath: folder.filePaths[0] }));
if (folders.includes(relativeFolder)) {
return;
}
if (SHARED_FOLDER_BLACKLIST.error.includes(relativeFolder)) {
notification.notifyError({
title: "notifications.shared-folder.adding-error.title",
desc: t.text("notifications.shared-folder.adding-error.msg", {
folder: relativeFolder
}),
});
return;
}
if (SHARED_FOLDER_BLACKLIST.warn.includes(relativeFolder)) {
await modal.openModal(BasicModal, { data: {
title: "modals.adding-shared-folder.title",
body: t.text("modals.adding-shared-folder.description", {
folder: relativeFolder
}),
image: BeatConflict,
buttons: [
{ id: "cancel", text: "misc.cancel", type: "cancel" },
{
id: "confirm", text: "misc.confirm", type: "primary",
onClick() {
config.set(SHARED_FOLDERS_KEY, [...folders, relativeFolder]);
return true;
},
}
]
}});
return;
}
setFolders([...folders, relativeFolder]);
};
return <BsmButton
icon="add"
className="h-8 rounded-md flex justify-center items-center font-bold bg-light-main-color-1 dark:bg-main-color-1"
iconClassName="h-6 aspect-square text-current"
onClick={addFolder}
withBar={false}
text="modals.shared-folders.buttons.add-folder"
/>;
}
// -------- FOLDER ITEM --------
type FolderProps = {
@@ -107,7 +171,7 @@ type FolderProps = {
const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
const linker = useService(VersionFolderLinkerService);
const t = useTranslation();
const t = useTranslationV2();
const color = useThemeColor("first-color");
const state = useObservable(() => linker.$folderLinkedState(version, relativeFolder), FolderLinkState.Unlinked, [version, relativeFolder]);
@@ -139,7 +203,7 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
{name}
</span>
<div className="flex flex-row gap-1.5">
<Tippy placement="left" content={t(`modals.shared-folders.buttons.${state === FolderLinkState.Linked ? "unlink-folder" : "link-folder"}`)} arrow={false}>
<Tippy placement="left" content={t.text(`modals.shared-folders.buttons.${state === FolderLinkState.Linked ? "unlink-folder" : "link-folder"}`)} arrow={false}>
<LinkButton
className="p-0.5 h-7 shrink-0 aspect-square blur-0 cursor-pointer hover:brightness-75"
state={state}
@@ -34,3 +34,19 @@ export const defaultConfiguration: {
};
export type ThemeConfig = "dark" | "light" | "os";
export const SHARED_FOLDER_BLACKLIST = {
error: [
".DepotDownloader",
"Beat Saber_Data",
"IPA",
"Libs",
"Plugins",
"MonoBleedingEdge",
],
warn: [
"DLC",
"Logs",
],
};
@@ -241,8 +241,8 @@ export function SettingsPage() {
body: "pages.settings.advanced.hardware-acceleration.modal.body",
image: BeatConflict,
buttons: [
{ id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true },
{ id: "confirm", text: "pages.settings.advanced.hardware-acceleration.modal.confirm-btn", type: "error" }
{ id: "cancel", text: "misc.cancel", type: "cancel" },
{ id: "confirm", text: "pages.settings.advanced.hardware-acceleration.modal.confirm-btn", type: "error", onClick: () => true },
]
}});
@@ -275,8 +275,8 @@ export function SettingsPage() {
body: "pages.settings.advanced.use-symlinks.modal.body",
image: BeatConflict,
buttons: [
{ id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true },
{ id: "confirm", text: "pages.settings.advanced.use-symlinks.modal.confirm-btn", type: "error" }
{ id: "cancel", text: "misc.cancel", type: "cancel" },
{ id: "confirm", text: "pages.settings.advanced.use-symlinks.modal.confirm-btn", type: "error", onClick: () => true }
]
}});