mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Merge branch 'feature/cloud-sync-improvements' into feat/i18n
# Conflicts: # src/locales/en/translation.json
This commit is contained in:
commit
2620f31989
15 changed files with 169 additions and 130 deletions
5
src/renderer/src/declaration.d.ts
vendored
5
src/renderer/src/declaration.d.ts
vendored
|
@ -144,6 +144,11 @@ declare global {
|
|||
shop: GameShop
|
||||
) => Promise<LudusaviBackup | null>;
|
||||
deleteGameArtifact: (gameArtifactId: string) => Promise<{ ok: boolean }>;
|
||||
selectGameBackupPath: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
backupPath: string | null
|
||||
) => Promise<void>;
|
||||
onBackupDownloadComplete: (
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
|
|
|
@ -1,9 +1,27 @@
|
|||
import { style } from "@vanilla-extract/css";
|
||||
|
||||
import { SPACING_UNIT } from "../../../theme.css";
|
||||
import { SPACING_UNIT, vars } from "../../../theme.css";
|
||||
|
||||
export const mappingMethods = style({
|
||||
display: "grid",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
gridTemplateColumns: "repeat(2, 1fr)",
|
||||
});
|
||||
|
||||
export const fileList = style({
|
||||
listStyle: "none",
|
||||
margin: "0",
|
||||
padding: "0",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
marginTop: `${SPACING_UNIT * 2}px`,
|
||||
});
|
||||
|
||||
export const fileItem = style({
|
||||
flex: 1,
|
||||
color: vars.color.muted,
|
||||
textDecoration: "underline",
|
||||
display: "flex",
|
||||
cursor: "pointer",
|
||||
});
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { Button, Modal, ModalProps } from "@renderer/components";
|
||||
import { useContext, useMemo, useState } from "react";
|
||||
import { cloudSyncContext } from "@renderer/context";
|
||||
import { Button, Modal, ModalProps, TextField } from "@renderer/components";
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { cloudSyncContext, gameDetailsContext } from "@renderer/context";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CheckCircleFillIcon } from "@primer/octicons-react";
|
||||
import { CheckCircleFillIcon, FileDirectoryIcon } from "@primer/octicons-react";
|
||||
|
||||
import * as styles from "./cloud-sync-files-modal.css";
|
||||
import { formatBytes } from "@shared";
|
||||
import { vars } from "@renderer/theme.css";
|
||||
// import { useToast } from "@renderer/hooks";
|
||||
import { useToast } from "@renderer/hooks";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
export interface CloudSyncFilesModalProps
|
||||
extends Omit<ModalProps, "children" | "title"> {}
|
||||
|
@ -23,12 +23,30 @@ export function CloudSyncFilesModal({
|
|||
}: CloudSyncFilesModalProps) {
|
||||
const [selectedFileMappingMethod, setSelectedFileMappingMethod] =
|
||||
useState<FileMappingMethod>(FileMappingMethod.Automatic);
|
||||
const { backupPreview } = useContext(cloudSyncContext);
|
||||
// const { gameTitle } = useContext(gameDetailsContext);
|
||||
const { backupPreview, getGameBackupPreview } = useContext(cloudSyncContext);
|
||||
const { shop, objectId } = useContext(gameDetailsContext);
|
||||
|
||||
const { t } = useTranslation("game_details");
|
||||
|
||||
// const { showSuccessToast } = useToast();
|
||||
const { showSuccessToast } = useToast();
|
||||
|
||||
const { register, setValue } = useForm<{
|
||||
customBackupPath: string | null;
|
||||
}>({
|
||||
defaultValues: {
|
||||
customBackupPath: null,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (backupPreview?.customBackupPath) {
|
||||
setSelectedFileMappingMethod(FileMappingMethod.Manual);
|
||||
} else {
|
||||
setSelectedFileMappingMethod(FileMappingMethod.Automatic);
|
||||
}
|
||||
|
||||
setValue("customBackupPath", backupPreview?.customBackupPath ?? null);
|
||||
}, [visible, setValue, backupPreview]);
|
||||
|
||||
const files = useMemo(() => {
|
||||
if (!backupPreview) {
|
||||
|
@ -44,28 +62,42 @@ export function CloudSyncFilesModal({
|
|||
});
|
||||
}, [backupPreview]);
|
||||
|
||||
// const handleAddCustomPathClick = useCallback(async () => {
|
||||
// const { filePaths } = await window.electron.showOpenDialog({
|
||||
// properties: ["openDirectory"],
|
||||
// });
|
||||
const handleAddCustomPathClick = useCallback(async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
// if (filePaths && filePaths.length > 0) {
|
||||
// const path = filePaths[0];
|
||||
// await window.electron.selectGameBackupDirectory(gameTitle, path);
|
||||
// showSuccessToast("custom_backup_location_set");
|
||||
// getGameBackupPreview();
|
||||
// }
|
||||
// }, [gameTitle, showSuccessToast, getGameBackupPreview]);
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
const path = filePaths[0];
|
||||
setValue("customBackupPath", path);
|
||||
|
||||
await window.electron.selectGameBackupPath(shop, objectId!, path);
|
||||
showSuccessToast("custom_backup_location_set");
|
||||
getGameBackupPreview();
|
||||
}
|
||||
}, [objectId, setValue, shop, showSuccessToast, getGameBackupPreview]);
|
||||
|
||||
const handleFileMappingMethodClick = useCallback(
|
||||
(mappingOption: FileMappingMethod) => {
|
||||
if (mappingOption === FileMappingMethod.Automatic) {
|
||||
getGameBackupPreview();
|
||||
window.electron.selectGameBackupPath(shop, objectId!, null);
|
||||
}
|
||||
|
||||
setSelectedFileMappingMethod(mappingOption);
|
||||
},
|
||||
[getGameBackupPreview, shop, objectId]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title="Gerenciar arquivos"
|
||||
description="Escolha quais diretórios serão sincronizados"
|
||||
title={t("manage_files")}
|
||||
description={t("manage_files_description")}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<span>{t("mapping_method_label")}</span>
|
||||
<span style={{ marginBottom: 8 }}>{t("mapping_method_label")}</span>
|
||||
|
||||
<div className={styles.mappingMethods}>
|
||||
{Object.values(FileMappingMethod).map((mappingMethod) => (
|
||||
|
@ -76,8 +108,7 @@ export function CloudSyncFilesModal({
|
|||
? "primary"
|
||||
: "outline"
|
||||
}
|
||||
onClick={() => setSelectedFileMappingMethod(mappingMethod)}
|
||||
disabled={mappingMethod === FileMappingMethod.Manual}
|
||||
onClick={() => handleFileMappingMethodClick(mappingMethod)}
|
||||
>
|
||||
{selectedFileMappingMethod === mappingMethod && (
|
||||
<CheckCircleFillIcon />
|
||||
|
@ -89,46 +120,33 @@ export function CloudSyncFilesModal({
|
|||
</div>
|
||||
|
||||
<div style={{ marginTop: 16 }}>
|
||||
{/* <TextField
|
||||
readOnly
|
||||
theme="dark"
|
||||
disabled
|
||||
placeholder={t("select_folder")}
|
||||
rightContent={
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={handleAddCustomPathClick}
|
||||
>
|
||||
<FileDirectoryIcon />
|
||||
{t("select_executable")}
|
||||
</Button>
|
||||
}
|
||||
/> */}
|
||||
{selectedFileMappingMethod === FileMappingMethod.Automatic ? (
|
||||
<p>{t("files_automatically_mapped")}</p>
|
||||
) : (
|
||||
<TextField
|
||||
{...register("customBackupPath")}
|
||||
readOnly
|
||||
theme="dark"
|
||||
disabled
|
||||
placeholder={t("select_folder")}
|
||||
rightContent={
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={handleAddCustomPathClick}
|
||||
>
|
||||
<FileDirectoryIcon />
|
||||
{t("select_executable")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<p>{t("files_automatically_mapped")}</p>
|
||||
|
||||
<ul
|
||||
style={{
|
||||
listStyle: "none",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 8,
|
||||
marginTop: 16,
|
||||
}}
|
||||
>
|
||||
<ul className={styles.fileList}>
|
||||
{files.map((file) => (
|
||||
<li key={file.path} style={{ display: "flex" }}>
|
||||
<button
|
||||
style={{
|
||||
flex: 1,
|
||||
color: vars.color.muted,
|
||||
textDecoration: "underline",
|
||||
display: "flex",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
className={styles.fileItem}
|
||||
onClick={() => window.electron.showItemInFolder(file.path)}
|
||||
>
|
||||
{file.path.split("/").at(-1)}
|
||||
|
|
|
@ -163,7 +163,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
|
|||
type="button"
|
||||
className={styles.manageFilesButton}
|
||||
onClick={() => setShowCloudSyncFilesModal(true)}
|
||||
disabled={disableActions || !backupPreview?.overall.totalGames}
|
||||
disabled={disableActions}
|
||||
>
|
||||
{t("manage_files")}
|
||||
</button>
|
||||
|
@ -199,7 +199,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
|
|||
|
||||
{artifacts.length > 0 ? (
|
||||
<ul className={styles.artifacts}>
|
||||
{artifacts.map((artifact, index) => (
|
||||
{artifacts.map((artifact) => (
|
||||
<li key={artifact.id} className={styles.artifactButton}>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
<div
|
||||
|
@ -210,7 +210,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
|
|||
marginBottom: 4,
|
||||
}}
|
||||
>
|
||||
<h3>{t("backup_title", { number: index + 1 })}</h3>
|
||||
<h3>Backup from 22/10</h3>
|
||||
<small>{formatBytes(artifact.artifactLengthInBytes)}</small>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue