Skip to content

Commit e0dc87a

Browse files
Merge pull request #1409 from hydralauncher/feature/custom-themes
Feature/custom themes
2 parents 42ae8e7 + e8d5c62 commit e0dc87a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1857
-195
lines changed

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hydralauncher",
3-
"version": "3.1.5",
3+
"version": "3.2.0",
44
"description": "Hydra",
55
"main": "./out/main/index.js",
66
"author": "Los Broxas",
@@ -36,6 +36,7 @@
3636
"@electron-toolkit/utils": "^3.0.0",
3737
"@fontsource/noto-sans": "^5.1.0",
3838
"@hookform/resolvers": "^3.9.1",
39+
"@monaco-editor/react": "^4.6.0",
3940
"@primer/octicons-react": "^19.9.0",
4041
"@radix-ui/react-dropdown-menu": "^2.1.2",
4142
"@reduxjs/toolkit": "^2.2.3",
@@ -59,6 +60,7 @@
5960
"i18next-browser-languagedetector": "^7.2.1",
6061
"jsdom": "^24.0.0",
6162
"jsonwebtoken": "^9.0.2",
63+
"kill-port": "^2.0.1",
6264
"knex": "^3.1.0",
6365
"lodash-es": "^4.17.21",
6466
"parse-torrent": "^11.0.17",

src/locales/en/translation.json

+29-3
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,10 @@
189189
"download_error_gofile_quota_exceeded": "You have exceeded your Gofile monthly quota. Please await the quota to reset.",
190190
"download_error_real_debrid_account_not_authorized": "Your Real-Debrid account is not authorized to make new downloads. Please check your account settings and try again.",
191191
"download_error_not_cached_in_real_debrid": "This download is not available on Real-Debrid and polling download status from Real-Debrid is not yet available.",
192-
"download_error_not_cached_in_torbox": "This download is not available on Torbox and polling download status from Torbox is not yet available."
192+
"download_error_not_cached_in_torbox": "This download is not available on Torbox and polling download status from Torbox is not yet available.",
193+
"game_removed_from_favorites": "Game removed from favorites",
194+
"game_added_to_favorites": "Game added to favorites"
193195
},
194-
195196
"activation": {
196197
"title": "Activate Hydra",
197198
"installation_id": "Installation ID:",
@@ -303,10 +304,35 @@
303304
"subscription_renew_cancelled": "Automatic renewal is disabled",
304305
"subscription_renews_on": "Your subscription renews on {{date}}",
305306
"bill_sent_until": "Your next bill will be sent until this day",
307+
"no_themes": "Seems like you don't have any themes yet, but no worries, click here to create your first masterpiece.",
308+
"editor_tab_code": "Code",
309+
"editor_tab_info": "Info",
310+
"editor_tab_save": "Save",
311+
"web_store": "Web store",
312+
"clear_themes": "Clear",
313+
"create_theme": "Create",
314+
"create_theme_modal_title": "Create custom theme",
315+
"create_theme_modal_description": "Create a new theme to customize Hydra's appearance",
316+
"theme_name": "Name",
317+
"insert_theme_name": "Insert theme name",
318+
"set_theme": "Set theme",
319+
"unset_theme": "Unset theme",
320+
"delete_theme": "Delete theme",
321+
"edit_theme": "Edit theme",
322+
"delete_all_themes": "Delete all themes",
323+
"delete_all_themes_description": "This will delete all your custom themes",
324+
"delete_theme_description": "This will delete the theme {{theme}}",
325+
"cancel": "Cancel",
326+
"appearance": "Appearance",
306327
"enable_torbox": "Enable Torbox",
307328
"torbox_description": "TorBox is your premium seedbox service rivaling even the best servers on the market.",
308329
"torbox_account_linked": "TorBox account linked",
309-
"real_debrid_account_linked": "Real-Debrid account linked"
330+
"real_debrid_account_linked": "Real-Debrid account linked",
331+
"name_min_length": "Theme name must be at least 3 characters long",
332+
"import_theme": "Import theme",
333+
"import_theme_description": "You will import {{theme}} from the theme store",
334+
"error_importing_theme": "Error importing theme",
335+
"theme_imported": "Theme imported successfully"
310336
},
311337
"notifications": {
312338
"download_complete": "Download complete",

src/locales/pt-BR/translation.json

+27-3
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,10 @@
179179
"download_error_gofile_quota_exceeded": "Você excedeu sua cota mensal do Gofile. Por favor, aguarde a cota resetar.",
180180
"download_error_real_debrid_account_not_authorized": "Sua conta do Real-Debrid não está autorizada a fazer novos downloads. Por favor, verifique sua assinatura e tente novamente.",
181181
"download_error_not_cached_in_real_debrid": "Este download não está disponível no Real-Debrid e a verificação do status do download não está disponível.",
182-
"download_error_not_cached_in_torbox": "Este download não está disponível no Torbox e a verificação do status do download não está disponível."
182+
"download_error_not_cached_in_torbox": "Este download não está disponível no Torbox e a verificação do status do download não está disponível.",
183+
"game_removed_from_favorites": "Jogo removido dos favoritos",
184+
"game_added_to_favorites": "Jogo adicionado aos favoritos"
183185
},
184-
185186
"activation": {
186187
"title": "Ativação",
187188
"installation_id": "ID da instalação:",
@@ -293,10 +294,33 @@
293294
"subscription_renew_cancelled": "A renovação automática está desativada",
294295
"subscription_renews_on": "Sua assinatura renova dia {{date}}",
295296
"bill_sent_until": "Sua próxima cobrança será enviada até esse dia",
297+
"no_themes": "Parece que você ainda não tem nenhum tema. Não se preocupe, clique aqui para criar sua primeira obra de arte.",
298+
"editor_tab_save": "Salvar",
299+
"web_store": "Loja de temas",
300+
"clear_themes": "Limpar",
301+
"create_theme": "Criar",
302+
"create_theme_modal_title": "Criar tema customizado",
303+
"create_theme_modal_description": "Criar novo tema para customizar a aparência do Hydra",
304+
"theme_name": "Nome",
305+
"insert_theme_name": "Insira o nome do tema",
306+
"set_theme": "Habilitar tema",
307+
"unset_theme": "Desabilitar tema",
308+
"delete_theme": "Deletar tema",
309+
"edit_theme": "Editar tema",
310+
"delete_all_themes": "Deletar todos os temas",
311+
"delete_all_themes_description": "Isso irá deletar todos os seus temas",
312+
"delete_theme_description": "Isso irá deletar o tema {{theme}}",
313+
"cancel": "Cancelar",
314+
"appearance": "Aparência",
296315
"enable_torbox": "Habilitar Torbox",
297316
"torbox_description": "TorBox é o seu serviço de seedbox premium que rivaliza até com os melhores servidores do mercado.",
298317
"torbox_account_linked": "Conta do TorBox vinculada",
299-
"real_debrid_account_linked": "Conta Real-Debrid associada"
318+
"real_debrid_account_linked": "Conta Real-Debrid associada",
319+
"name_min_length": "O nome do tema deve ter pelo menos 3 caracteres",
320+
"import_theme": "Importar tema",
321+
"import_theme_description": "Você irá importar {{theme}} da loja de temas",
322+
"error_importing_theme": "Erro ao importar tema",
323+
"theme_imported": "Tema importado com sucesso"
300324
},
301325
"notifications": {
302326
"download_complete": "Download concluído",

src/main/events/auth/get-session-hash.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@ import jwt from "jsonwebtoken";
33
import { registerEvent } from "../register-event";
44
import { db, levelKeys } from "@main/level";
55
import type { Auth } from "@types";
6-
import { Crypto } from "@main/services";
76

87
const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
98
const auth = await db.get<string, Auth>(levelKeys.auth, {
109
valueEncoding: "json",
1110
});
1211

1312
if (!auth) return null;
14-
const payload = jwt.decode(
15-
Crypto.decrypt(auth.accessToken)
16-
) as jwt.JwtPayload;
13+
const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload;
1714

1815
if (!payload) return null;
1916

src/main/events/index.ts

+10
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ import "./cloud-save/upload-save-game";
7777
import "./cloud-save/delete-game-artifact";
7878
import "./cloud-save/select-game-backup-path";
7979
import "./notifications/publish-new-repacks-notification";
80+
import "./themes/add-custom-theme";
81+
import "./themes/delete-custom-theme";
82+
import "./themes/get-all-custom-themes";
83+
import "./themes/delete-all-custom-themes";
84+
import "./themes/update-custom-theme";
85+
import "./themes/open-editor-window";
86+
import "./themes/get-custom-theme-by-id";
87+
import "./themes/get-active-custom-theme";
88+
import "./themes/close-editor-window";
89+
import "./themes/toggle-custom-theme";
8090
import { isPortableVersion } from "@main/helpers";
8191

8292
ipcMain.handle("ping", () => "pong");

src/main/events/misc/open-checkout.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { shell } from "electron";
22
import { registerEvent } from "../register-event";
3-
import { Crypto, HydraApi } from "@main/services";
3+
import { HydraApi } from "@main/services";
44
import { db, levelKeys } from "@main/level";
55
import type { Auth } from "@types";
66

@@ -14,7 +14,7 @@ const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => {
1414
}
1515

1616
const paymentToken = await HydraApi.post("/auth/payment", {
17-
refreshToken: Crypto.decrypt(auth.refreshToken),
17+
refreshToken: auth.refreshToken,
1818
}).then((response) => response.accessToken);
1919

2020
const params = new URLSearchParams({
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Theme } from "@types";
2+
import { registerEvent } from "../register-event";
3+
import { themesSublevel } from "@main/level";
4+
5+
const addCustomTheme = async (
6+
_event: Electron.IpcMainInvokeEvent,
7+
theme: Theme
8+
) => {
9+
await themesSublevel.put(theme.id, theme);
10+
};
11+
12+
registerEvent("addCustomTheme", addCustomTheme);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { WindowManager } from "@main/services";
2+
import { registerEvent } from "../register-event";
3+
4+
const closeEditorWindow = async (
5+
_event: Electron.IpcMainInvokeEvent,
6+
themeId?: string
7+
) => {
8+
WindowManager.closeEditorWindow(themeId);
9+
};
10+
11+
registerEvent("closeEditorWindow", closeEditorWindow);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { themesSublevel } from "@main/level";
2+
import { registerEvent } from "../register-event";
3+
4+
const deleteAllCustomThemes = async (_event: Electron.IpcMainInvokeEvent) => {
5+
await themesSublevel.clear();
6+
};
7+
8+
registerEvent("deleteAllCustomThemes", deleteAllCustomThemes);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { themesSublevel } from "@main/level";
2+
import { registerEvent } from "../register-event";
3+
4+
const deleteCustomTheme = async (
5+
_event: Electron.IpcMainInvokeEvent,
6+
themeId: string
7+
) => {
8+
await themesSublevel.del(themeId);
9+
};
10+
11+
registerEvent("deleteCustomTheme", deleteCustomTheme);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { themesSublevel } from "@main/level";
2+
import { registerEvent } from "../register-event";
3+
4+
const getActiveCustomTheme = async () => {
5+
const allThemes = await themesSublevel.values().all();
6+
return allThemes.find((theme) => theme.isActive);
7+
};
8+
9+
registerEvent("getActiveCustomTheme", getActiveCustomTheme);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { themesSublevel } from "@main/level";
2+
import { registerEvent } from "../register-event";
3+
4+
const getAllCustomThemes = async (_event: Electron.IpcMainInvokeEvent) => {
5+
return themesSublevel.values().all();
6+
};
7+
8+
registerEvent("getAllCustomThemes", getAllCustomThemes);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { themesSublevel } from "@main/level";
2+
import { registerEvent } from "../register-event";
3+
4+
const getCustomThemeById = async (
5+
_event: Electron.IpcMainInvokeEvent,
6+
themeId: string
7+
) => {
8+
return themesSublevel.get(themeId);
9+
};
10+
11+
registerEvent("getCustomThemeById", getCustomThemeById);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { WindowManager } from "@main/services";
2+
import { registerEvent } from "../register-event";
3+
4+
const openEditorWindow = async (
5+
_event: Electron.IpcMainInvokeEvent,
6+
themeId: string
7+
) => {
8+
WindowManager.openEditorWindow(themeId);
9+
};
10+
11+
registerEvent("openEditorWindow", openEditorWindow);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { themesSublevel } from "@main/level";
2+
import { registerEvent } from "../register-event";
3+
4+
const toggleCustomTheme = async (
5+
_event: Electron.IpcMainInvokeEvent,
6+
themeId: string,
7+
isActive: boolean
8+
) => {
9+
const theme = await themesSublevel.get(themeId);
10+
11+
if (!theme) {
12+
throw new Error("Theme not found");
13+
}
14+
15+
await themesSublevel.put(themeId, {
16+
...theme,
17+
isActive,
18+
updatedAt: new Date(),
19+
});
20+
};
21+
22+
registerEvent("toggleCustomTheme", toggleCustomTheme);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { themesSublevel } from "@main/level";
2+
import { registerEvent } from "../register-event";
3+
import { WindowManager } from "@main/services";
4+
5+
const updateCustomTheme = async (
6+
_event: Electron.IpcMainInvokeEvent,
7+
themeId: string,
8+
code: string
9+
) => {
10+
const theme = await themesSublevel.get(themeId);
11+
12+
if (!theme) {
13+
throw new Error("Theme not found");
14+
}
15+
16+
await themesSublevel.put(themeId, {
17+
...theme,
18+
code,
19+
updatedAt: new Date(),
20+
});
21+
22+
if (theme.isActive) {
23+
WindowManager.mainWindow?.webContents.send("css-injected", code);
24+
}
25+
};
26+
27+
registerEvent("updateCustomTheme", updateCustomTheme);
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,10 @@
11
import { registerEvent } from "../register-event";
22
import { db, levelKeys } from "@main/level";
3-
import { Crypto } from "@main/services";
43
import type { UserPreferences } from "@types";
54

65
const getUserPreferences = async () =>
7-
db
8-
.get<string, UserPreferences | null>(levelKeys.userPreferences, {
9-
valueEncoding: "json",
10-
})
11-
.then((userPreferences) => {
12-
if (userPreferences?.realDebridApiToken) {
13-
userPreferences.realDebridApiToken = Crypto.decrypt(
14-
userPreferences.realDebridApiToken
15-
);
16-
}
17-
18-
if (userPreferences?.torBoxApiToken) {
19-
userPreferences.torBoxApiToken = Crypto.decrypt(
20-
userPreferences.torBoxApiToken
21-
);
22-
}
23-
24-
return userPreferences;
25-
});
6+
db.get<string, UserPreferences | null>(levelKeys.userPreferences, {
7+
valueEncoding: "json",
8+
});
269

2710
registerEvent("getUserPreferences", getUserPreferences);

src/main/events/user-preferences/update-user-preferences.ts

-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { registerEvent } from "../register-event";
33
import type { UserPreferences } from "@types";
44
import i18next from "i18next";
55
import { db, levelKeys } from "@main/level";
6-
import { Crypto } from "@main/services";
76
import { patchUserProfile } from "../profile/update-profile";
87

98
const updateUserPreferences = async (
@@ -24,16 +23,6 @@ const updateUserPreferences = async (
2423
patchUserProfile({ language: preferences.language }).catch(() => {});
2524
}
2625

27-
if (preferences.realDebridApiToken) {
28-
preferences.realDebridApiToken = Crypto.encrypt(
29-
preferences.realDebridApiToken
30-
);
31-
}
32-
33-
if (preferences.torBoxApiToken) {
34-
preferences.torBoxApiToken = Crypto.encrypt(preferences.torBoxApiToken);
35-
}
36-
3726
if (!preferences.downloadsPath) {
3827
preferences.downloadsPath = null;
3928
}

0 commit comments

Comments
 (0)