mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feat] added blacklist for shared folders
* change BasicModal data to handle cascading modals better
This commit is contained in:
@@ -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 }
|
||||
]
|
||||
}});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user