Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,9 @@
"hide_original": "Hide original",
"review_from_blocked_user": "Review from blocked user",
"show": "Show",
"hide": "Hide"
"hide": "Hide",
"share": "Share",
"link_copied": "Link copied"
},
"activation": {
"title": "Activate Hydra",
Expand Down Expand Up @@ -571,7 +573,10 @@
"notification_preview": "Achievement Notification Preview",
"enable_friend_start_game_notifications": "When a friend starts playing a game",
"autoplay_trailers_on_game_page": "Automatically start playing trailers on game page",
"hide_to_tray_on_game_start": "Hide Hydra to tray on game startup"
"hide_to_tray_on_game_start": "Hide Hydra to tray on game startup",
"show_sidebar_library": "Show library section in sidebar",
"show_sidebar_favorites": "Show favorites section in sidebar",
"theme_variant": "Theme variant"
},
"notifications": {
"download_complete": "Download complete",
Expand Down
9 changes: 7 additions & 2 deletions src/locales/ru/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@
"no_repacks_found": "Источники для этой игры не найдены",
"show": "Показать",
"hide": "Скрыть",
"share": "Поделиться",
"link_copied": "Ссылка скопирована",
"delete_review": "Удалить отзыв",
"remove_review": "Удалить отзыв",
"delete_review_modal_title": "Вы уверены, что хотите удалить свой отзыв?",
Expand Down Expand Up @@ -566,7 +568,10 @@
"notification_preview": "Предварительный просмотр уведомления о достижении",
"enable_friend_start_game_notifications": "Когда друг начинает играть в игру",
"autoplay_trailers_on_game_page": "Автоматически начинать воспроизведение трейлеров на странице игры",
"hide_to_tray_on_game_start": "Скрывать Hydra в трей при запуске игры"
"hide_to_tray_on_game_start": "Скрывать Hydra в трей при запуске игры",
"show_sidebar_library": "Показывать библиотеку в боковой панели",
"show_sidebar_favorites": "Показывать избранное в боковой панели",
"theme_variant": "Вариант темы"
},
"notifications": {
"download_complete": "Загрузка завершена",
Expand Down Expand Up @@ -681,7 +686,7 @@
"report_reason_spam": "Спам",
"report_reason_other": "Другое",
"profile_reported": "Жалоба на профиль отправлена",
"your_friend_code": "Код вашего друга:",
"your_friend_code": "Код приглашения:",
"upload_banner": "Загрузить баннер",
"uploading_banner": "Загрузка баннера...",
"background_image_updated": "Фоновое изображение обновлено",
Expand Down
1 change: 1 addition & 0 deletions src/main/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import "./themes/remove-theme-achievement-sound";
import "./themes/get-theme-sound-path";
import "./themes/get-theme-sound-data-url";
import "./themes/import-theme-sound-from-store";
import "./themes/import-theme-sound-from-url";
import "./download-sources/remove-download-source";
import "./download-sources/get-download-sources";
import { isPortableVersion } from "@main/helpers";
Expand Down
53 changes: 53 additions & 0 deletions src/main/events/themes/import-theme-sound-from-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { registerEvent } from "../register-event";
import fs from "node:fs";
import path from "node:path";
import axios from "axios";
import { getThemePath } from "@main/helpers";
import { themesSublevel } from "@main/level";
import { logger } from "@main/services";

const importThemeSoundFromUrl = async (
_event: Electron.IpcMainInvokeEvent,
themeId: string,
soundUrl: string
): Promise<void> => {
const theme = await themesSublevel.get(themeId);
if (!theme) {
throw new Error("Theme not found");
}

try {
const response = await axios.get(soundUrl, {
responseType: "arraybuffer",
timeout: 15000,
});

const urlExt = path.extname(new URL(soundUrl).pathname).toLowerCase();
const ext =
urlExt && [".wav", ".mp3", ".ogg", ".m4a"].includes(urlExt)
? urlExt
: ".mp3";

const themeDir = getThemePath(themeId, theme.name);

if (!fs.existsSync(themeDir)) {
fs.mkdirSync(themeDir, { recursive: true });
}

const destinationPath = path.join(themeDir, `achievement${ext}`);
await fs.promises.writeFile(destinationPath, response.data);

await themesSublevel.put(themeId, {
...theme,
hasCustomSound: true,
updatedAt: new Date(),
});

logger.log(`Successfully imported sound from URL for theme ${theme.name}`);
} catch (error) {
logger.error("Failed to import sound from URL", error);
throw error;
}
};

registerEvent("importThemeSoundFromUrl", importThemeSoundFromUrl);
86 changes: 57 additions & 29 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,32 +158,50 @@ const handleDeepLinkPath = (uri?: string) => {
try {
const url = new URL(uri);

if (url.host === "install-source") {
WindowManager.redirect(`settings${url.search}`);
return;
}

if (url.host === "profile") {
const userId = url.searchParams.get("userId");

if (userId) {
WindowManager.redirect(`profile/${userId}`);
}

return;
}

if (url.host === "install-theme") {
const themeName = url.searchParams.get("theme");
const authorId = url.searchParams.get("authorId");
const authorName = url.searchParams.get("authorName");

if (themeName && authorId && authorName) {
WindowManager.redirect(
`settings?theme=${themeName}&authorId=${authorId}&authorName=${authorName}`
);
}
}
const handlers: Record<string, (u: URL) => void> = {
"install-source": (u) => {
WindowManager.redirect(`settings${u.search}`);
},
profile: (u) => {
const userId = u.searchParams.get("userId");
if (userId) WindowManager.redirect(`profile/${userId}`);
},
"install-theme": (u) => {
const themeName = u.searchParams.get("theme");
const authorId = u.searchParams.get("authorId");
if (!themeName || !authorId) return;
const authorName = u.searchParams.get("authorName");
const source = u.searchParams.get("source");
const sound = u.searchParams.get("sound");
const params = new URLSearchParams();
params.set("theme", themeName);
params.set("authorId", authorId);
if (authorName) params.set("authorName", authorName);
if (source) params.set("source", source);
if (sound) params.set("sound", sound);
WindowManager.redirect(`settings?${params.toString()}`);
},
game: (u) => {
const shop = u.searchParams.get("shop");
const objectId = u.searchParams.get("objectId");
const title = u.searchParams.get("title");
if (shop && objectId && title) {
WindowManager.redirect(`game/${shop}/${objectId}?title=${title}`);
return;
}
const pathSegments = u.pathname.split("/").filter(Boolean);
if (pathSegments.length >= 2) {
const [pathShop, pathObjectId] = pathSegments;
const pathTitle = title ?? "";
WindowManager.redirect(
`game/${pathShop}/${pathObjectId}?title=${pathTitle}`
);
}
},
};

const handler = handlers[url.host];
if (handler) handler(url);
} catch (error) {
logger.error("Error handling deep link", uri, error);
}
Expand All @@ -200,13 +218,23 @@ app.on("second-instance", (_event, commandLine) => {
WindowManager.createMainWindow();
}

handleDeepLinkPath(commandLine.pop());
const deepLinkArg = commandLine.find((arg) =>
arg.startsWith(`${PROTOCOL}://`)
);
handleDeepLinkPath(deepLinkArg);
});

app.on("open-url", (_event, url) => {
handleDeepLinkPath(url);
app.on("will-finish-launching", () => {
app.on("open-url", (_event, url) => {
handleDeepLinkPath(url);
});
});

const initialDeepLink = process.argv.find((arg) =>
arg.startsWith(`${PROTOCOL}://`)
);
handleDeepLinkPath(initialDeepLink);

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
Expand Down
2 changes: 2 additions & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,8 @@ contextBridge.exposeInMainWorld("electron", {
themeName,
storeUrl
),
importThemeSoundFromUrl: (themeId: string, soundUrl: string) =>
ipcRenderer.invoke("importThemeSoundFromUrl", themeId, soundUrl),

/* Editor */
openEditorWindow: (themeId: string) =>
Expand Down
28 changes: 26 additions & 2 deletions src/renderer/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
removeCustomCss,
getAchievementSoundUrl,
getAchievementSoundVolume,
parseThemeBlocks,
parseCssVars,
} from "./helpers";
import "./app.scss";

Expand Down Expand Up @@ -207,6 +209,28 @@ export function App() {
const activeTheme = await window.electron.getActiveCustomTheme();
if (activeTheme?.code) {
injectCustomCss(activeTheme.code);

const blocks = parseThemeBlocks(activeTheme.code);

const parseVars = parseCssVars;

const storedVariant = globalThis.localStorage.getItem(
`customThemeVariant:${activeTheme.id}`
);
const rootBlock = blocks.find((b) => b.name === "root");
const selectedBlock = storedVariant
? blocks.find((b) => b.name === storedVariant)
: null;
if (rootBlock) {
parseVars(rootBlock.content).forEach(({ key, value }) => {
document.documentElement.style.setProperty(key, value);
});
}
if (selectedBlock && storedVariant !== "root") {
parseVars(selectedBlock.content).forEach(({ key, value }) => {
document.documentElement.style.setProperty(key, value);
});
}
} else {
removeCustomCss();
}
Expand All @@ -217,7 +241,7 @@ export function App() {
}, [loadAndApplyTheme]);

useEffect(() => {
const unsubscribe = window.electron.onCustomThemeUpdated(() => {
const unsubscribe = globalThis.electron.onCustomThemeUpdated(() => {
loadAndApplyTheme();
});

Expand All @@ -233,7 +257,7 @@ export function App() {
}, []);

useEffect(() => {
const unsubscribe = window.electron.onAchievementUnlocked(() => {
const unsubscribe = globalThis.electron.onAchievementUnlocked(() => {
playAudio();
});

Expand Down
Loading
Loading