mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
feat: adding local file protocol
This commit is contained in:
parent
1ef8e3fce3
commit
1fb1c9e81a
@ -24,7 +24,6 @@ import "./library/remove-game";
|
|||||||
import "./library/remove-game-from-library";
|
import "./library/remove-game-from-library";
|
||||||
import "./misc/open-external";
|
import "./misc/open-external";
|
||||||
import "./misc/show-open-dialog";
|
import "./misc/show-open-dialog";
|
||||||
import "./misc/image-path-to-base-64";
|
|
||||||
import "./torrenting/cancel-game-download";
|
import "./torrenting/cancel-game-download";
|
||||||
import "./torrenting/pause-game-download";
|
import "./torrenting/pause-game-download";
|
||||||
import "./torrenting/resume-game-download";
|
import "./torrenting/resume-game-download";
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import mime from "mime";
|
|
||||||
import { registerEvent } from "../register-event";
|
|
||||||
import fs from "node:fs";
|
|
||||||
|
|
||||||
const imagePathToBase64 = async (
|
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
|
||||||
filePath: string
|
|
||||||
) => {
|
|
||||||
const buffer = fs.readFileSync(filePath);
|
|
||||||
const mimeType = mime.getType(filePath);
|
|
||||||
return `data:${mimeType};base64,${buffer.toString("base64")}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
registerEvent("imagePathToBase64", imagePathToBase64);
|
|
@ -2,6 +2,7 @@ import { registerEvent } from "../register-event";
|
|||||||
import { HydraApi } from "@main/services/hydra-api";
|
import { HydraApi } from "@main/services/hydra-api";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
import mime from "mime";
|
import mime from "mime";
|
||||||
import { UserProfile } from "@types";
|
import { UserProfile } from "@types";
|
||||||
|
|
||||||
@ -26,6 +27,8 @@ const updateProfile = async (
|
|||||||
displayName: string,
|
displayName: string,
|
||||||
newProfileImagePath: string | null
|
newProfileImagePath: string | null
|
||||||
): Promise<UserProfile> => {
|
): Promise<UserProfile> => {
|
||||||
|
console.log(newProfileImagePath);
|
||||||
|
|
||||||
if (!newProfileImagePath) {
|
if (!newProfileImagePath) {
|
||||||
return (await patchUserProfile(displayName)).data;
|
return (await patchUserProfile(displayName)).data;
|
||||||
}
|
}
|
||||||
@ -35,7 +38,7 @@ const updateProfile = async (
|
|||||||
const fileSizeInBytes = stats.size;
|
const fileSizeInBytes = stats.size;
|
||||||
|
|
||||||
const profileImageUrl = await HydraApi.post(`/presigned-urls/profile-image`, {
|
const profileImageUrl = await HydraApi.post(`/presigned-urls/profile-image`, {
|
||||||
imageExt: newProfileImagePath.split(".").at(-1),
|
imageExt: path.extname(newProfileImagePath).slice(1),
|
||||||
imageLength: fileSizeInBytes,
|
imageLength: fileSizeInBytes,
|
||||||
})
|
})
|
||||||
.then(async (preSignedResponse) => {
|
.then(async (preSignedResponse) => {
|
||||||
|
@ -2,6 +2,7 @@ import { app, BrowserWindow, net, protocol } from "electron";
|
|||||||
import updater from "electron-updater";
|
import updater from "electron-updater";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import url from "node:url";
|
||||||
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
||||||
import { DownloadManager, logger, WindowManager } from "@main/services";
|
import { DownloadManager, logger, WindowManager } from "@main/services";
|
||||||
import { dataSource } from "@main/data-source";
|
import { dataSource } from "@main/data-source";
|
||||||
@ -51,9 +52,10 @@ if (process.defaultApp) {
|
|||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
electronApp.setAppUserModelId("site.hydralauncher.hydra");
|
electronApp.setAppUserModelId("site.hydralauncher.hydra");
|
||||||
|
|
||||||
protocol.handle("hydra", (request) =>
|
protocol.handle("local", (request) => {
|
||||||
net.fetch("file://" + request.url.slice("hydra://".length))
|
const filePath = request.url.slice("local://".length);
|
||||||
);
|
return net.fetch(url.pathToFileURL(filePath).toString());
|
||||||
|
});
|
||||||
|
|
||||||
await dataSource.initialize();
|
await dataSource.initialize();
|
||||||
await dataSource.runMigrations();
|
await dataSource.runMigrations();
|
||||||
|
@ -106,8 +106,6 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
getVersion: () => ipcRenderer.invoke("getVersion"),
|
getVersion: () => ipcRenderer.invoke("getVersion"),
|
||||||
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
|
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
|
||||||
openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
|
openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
|
||||||
imagePathToBase64: (filePath: string) =>
|
|
||||||
ipcRenderer.invoke("imagePathToBase64", filePath),
|
|
||||||
showOpenDialog: (options: Electron.OpenDialogOptions) =>
|
showOpenDialog: (options: Electron.OpenDialogOptions) =>
|
||||||
ipcRenderer.invoke("showOpenDialog", options),
|
ipcRenderer.invoke("showOpenDialog", options),
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<title>Hydra</title>
|
<title>Hydra</title>
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://cdn.losbroxas.org https://steamcdn-a.akamaihd.net https://shared.akamai.steamstatic.com https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com; media-src 'self' data: https://cdn.losbroxas.org https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com https://shared.akamai.steamstatic.com;"
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: local: https://cdn.losbroxas.org https://steamcdn-a.akamaihd.net https://shared.akamai.steamstatic.com https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com; media-src 'self' data: https://cdn.losbroxas.org https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com https://shared.akamai.steamstatic.com;"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color: #1c1c1c">
|
<body style="background-color: #1c1c1c">
|
||||||
|
1
src/renderer/src/declaration.d.ts
vendored
1
src/renderer/src/declaration.d.ts
vendored
@ -96,7 +96,6 @@ declare global {
|
|||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
openExternal: (src: string) => Promise<void>;
|
openExternal: (src: string) => Promise<void>;
|
||||||
imagePathToBase64: (filePath: string) => Promise<string>;
|
|
||||||
getVersion: () => Promise<string>;
|
getVersion: () => Promise<string>;
|
||||||
ping: () => string;
|
ping: () => string;
|
||||||
getDefaultDownloadsPath: () => Promise<string>;
|
getDefaultDownloadsPath: () => Promise<string>;
|
||||||
|
@ -17,7 +17,7 @@ const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
|
|||||||
|
|
||||||
export interface ProfileContentProps {
|
export interface ProfileContentProps {
|
||||||
userProfile: UserProfile;
|
userProfile: UserProfile;
|
||||||
updateUserProfile: () => void;
|
updateUserProfile: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserContent({
|
export function UserContent({
|
||||||
@ -70,10 +70,6 @@ export function UserContent({
|
|||||||
navigate("/");
|
navigate("/");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateUserProfile = async () => {
|
|
||||||
updateUserProfile();
|
|
||||||
};
|
|
||||||
|
|
||||||
const isMe = userDetails?.id == userProfile.id;
|
const isMe = userDetails?.id == userProfile.id;
|
||||||
|
|
||||||
const profileContentBoxBackground = useMemo(() => {
|
const profileContentBoxBackground = useMemo(() => {
|
||||||
@ -87,7 +83,7 @@ export function UserContent({
|
|||||||
<UserEditProfileModal
|
<UserEditProfileModal
|
||||||
visible={showEditProfileModal}
|
visible={showEditProfileModal}
|
||||||
onClose={() => setShowEditProfileModal(false)}
|
onClose={() => setShowEditProfileModal(false)}
|
||||||
updateUser={handleUpdateUserProfile}
|
updateUserProfile={updateUserProfile}
|
||||||
userProfile={userProfile}
|
userProfile={userProfile}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -1,33 +1,36 @@
|
|||||||
import { Button, Modal, TextField } from "@renderer/components";
|
import { Button, Modal, TextField } from "@renderer/components";
|
||||||
import { UserProfile } from "@types";
|
import { UserProfile } from "@types";
|
||||||
import * as styles from "./user.css";
|
import * as styles from "./user.css";
|
||||||
import { PencilIcon, PersonIcon } from "@primer/octicons-react";
|
import { DeviceCameraIcon, PersonIcon } from "@primer/octicons-react";
|
||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
import { useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useToast, useUserDetails } from "@renderer/hooks";
|
import { useToast, useUserDetails } from "@renderer/hooks";
|
||||||
|
|
||||||
export interface UserEditProfileModalProps {
|
export interface UserEditProfileModalProps {
|
||||||
userProfile: UserProfile;
|
userProfile: UserProfile;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
updateUser: () => Promise<void>;
|
updateUserProfile: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserEditProfileModal = ({
|
export const UserEditProfileModal = ({
|
||||||
userProfile,
|
userProfile,
|
||||||
visible,
|
visible,
|
||||||
onClose,
|
onClose,
|
||||||
updateUser,
|
updateUserProfile,
|
||||||
}: UserEditProfileModalProps) => {
|
}: UserEditProfileModalProps) => {
|
||||||
const [displayName, setDisplayName] = useState(userProfile.displayName);
|
const [displayName, setDisplayName] = useState("");
|
||||||
const [newImagePath, setNewImagePath] = useState<string | null>(null);
|
const [newImagePath, setNewImagePath] = useState<string | null>(null);
|
||||||
const [newImageBase64, setNewImageBase64] = useState<string | null>(null);
|
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
const { patchUser } = useUserDetails();
|
const { patchUser } = useUserDetails();
|
||||||
|
|
||||||
const { showSuccessToast, showErrorToast } = useToast();
|
const { showSuccessToast, showErrorToast } = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDisplayName(userProfile.displayName);
|
||||||
|
}, [userProfile.displayName]);
|
||||||
|
|
||||||
const handleChangeProfileAvatar = async () => {
|
const handleChangeProfileAvatar = async () => {
|
||||||
const { filePaths } = await window.electron.showOpenDialog({
|
const { filePaths } = await window.electron.showOpenDialog({
|
||||||
properties: ["openFile"],
|
properties: ["openFile"],
|
||||||
@ -42,19 +45,16 @@ export const UserEditProfileModal = ({
|
|||||||
if (filePaths && filePaths.length > 0) {
|
if (filePaths && filePaths.length > 0) {
|
||||||
const path = filePaths[0];
|
const path = filePaths[0];
|
||||||
|
|
||||||
window.electron.imagePathToBase64(path).then((base64) => {
|
|
||||||
setNewImageBase64(base64);
|
|
||||||
});
|
|
||||||
|
|
||||||
setNewImagePath(path);
|
setNewImagePath(path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveProfile = async () => {
|
const handleSaveProfile = async () => {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
|
|
||||||
patchUser(displayName, newImagePath)
|
patchUser(displayName, newImagePath)
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
updateUser();
|
await updateUserProfile();
|
||||||
showSuccessToast("Salvo com sucesso");
|
showSuccessToast("Salvo com sucesso");
|
||||||
cleanFormAndClose();
|
cleanFormAndClose();
|
||||||
})
|
})
|
||||||
@ -69,7 +69,6 @@ export const UserEditProfileModal = ({
|
|||||||
const resetModal = () => {
|
const resetModal = () => {
|
||||||
setDisplayName(userProfile.displayName);
|
setDisplayName(userProfile.displayName);
|
||||||
setNewImagePath(null);
|
setNewImagePath(null);
|
||||||
setNewImageBase64(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanFormAndClose = () => {
|
const cleanFormAndClose = () => {
|
||||||
@ -77,6 +76,12 @@ export const UserEditProfileModal = ({
|
|||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const avatarUrl = useMemo(() => {
|
||||||
|
if (newImagePath) return `local:${newImagePath}`;
|
||||||
|
if (userProfile.profileImageUrl) return userProfile.profileImageUrl;
|
||||||
|
return null;
|
||||||
|
}, [newImagePath, userProfile.profileImageUrl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
@ -84,7 +89,8 @@ export const UserEditProfileModal = ({
|
|||||||
title="Editar Perfil"
|
title="Editar Perfil"
|
||||||
onClose={cleanFormAndClose}
|
onClose={cleanFormAndClose}
|
||||||
>
|
>
|
||||||
<section
|
<form
|
||||||
|
onSubmit={handleSaveProfile}
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
@ -95,20 +101,21 @@ export const UserEditProfileModal = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className={styles.profileAvatarEditContainer}
|
className={styles.profileAvatarEditContainer}
|
||||||
onClick={handleChangeProfileAvatar}
|
onClick={handleChangeProfileAvatar}
|
||||||
>
|
>
|
||||||
{newImageBase64 || userProfile.profileImageUrl ? (
|
{avatarUrl ? (
|
||||||
<img
|
<img
|
||||||
className={styles.profileAvatar}
|
className={styles.profileAvatar}
|
||||||
alt={userProfile.displayName}
|
alt={userProfile.displayName}
|
||||||
src={newImageBase64 ?? userProfile.profileImageUrl ?? ""}
|
src={avatarUrl}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PersonIcon size={96} />
|
<PersonIcon size={96} />
|
||||||
)}
|
)}
|
||||||
<div className={styles.editProfileImageBadge}>
|
<div className={styles.editProfileImageBadge}>
|
||||||
<PencilIcon size={16} />
|
<DeviceCameraIcon size={16} />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -121,11 +128,11 @@ export const UserEditProfileModal = ({
|
|||||||
<Button
|
<Button
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
style={{ alignSelf: "end" }}
|
style={{ alignSelf: "end" }}
|
||||||
onClick={handleSaveProfile}
|
type="submit"
|
||||||
>
|
>
|
||||||
{isSaving ? "Salvando..." : "Salvar"}
|
{isSaving ? "Salvando…" : "Salvar"}
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -16,7 +16,7 @@ export const User = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const getUserProfile = useCallback(() => {
|
const getUserProfile = useCallback(() => {
|
||||||
window.electron.getUser(userId!).then((userProfile) => {
|
return window.electron.getUser(userId!).then((userProfile) => {
|
||||||
if (userProfile) {
|
if (userProfile) {
|
||||||
dispatch(setHeaderTitle(userProfile.displayName));
|
dispatch(setHeaderTitle(userProfile.displayName));
|
||||||
setUserProfile(userProfile);
|
setUserProfile(userProfile);
|
||||||
@ -28,9 +28,7 @@ export const User = () => {
|
|||||||
getUserProfile();
|
getUserProfile();
|
||||||
}, [getUserProfile]);
|
}, [getUserProfile]);
|
||||||
|
|
||||||
const handleUpdateProfile = () => {
|
console.log(userProfile);
|
||||||
getUserProfile();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
|
<SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
|
||||||
@ -38,7 +36,7 @@ export const User = () => {
|
|||||||
{userProfile ? (
|
{userProfile ? (
|
||||||
<UserContent
|
<UserContent
|
||||||
userProfile={userProfile}
|
userProfile={userProfile}
|
||||||
updateUserProfile={handleUpdateProfile}
|
updateUserProfile={getUserProfile}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<UserSkeleton />
|
<UserSkeleton />
|
||||||
|
Loading…
Reference in New Issue
Block a user