diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 89dd01f5..834f47ba 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -95,6 +95,7 @@ import "./themes/toggle-custom-theme"; import "./themes/copy-theme-achievement-sound"; 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 "./download-sources/remove-download-source"; import "./download-sources/get-download-sources"; diff --git a/src/main/events/themes/get-theme-sound-data-url.ts b/src/main/events/themes/get-theme-sound-data-url.ts new file mode 100644 index 00000000..b9ace306 --- /dev/null +++ b/src/main/events/themes/get-theme-sound-data-url.ts @@ -0,0 +1,38 @@ +import { registerEvent } from "../register-event"; +import { getThemeSoundPath } from "@main/helpers"; +import fs from "node:fs"; +import path from "node:path"; +import { logger } from "@main/services"; + +const getThemeSoundDataUrl = async ( + _event: Electron.IpcMainInvokeEvent, + themeId: string +): Promise => { + try { + const soundPath = getThemeSoundPath(themeId); + + if (!soundPath || !fs.existsSync(soundPath)) { + return null; + } + + const buffer = await fs.promises.readFile(soundPath); + const ext = path.extname(soundPath).toLowerCase().slice(1); + + const mimeTypes: Record = { + mp3: "audio/mpeg", + wav: "audio/wav", + ogg: "audio/ogg", + m4a: "audio/mp4", + }; + + const mimeType = mimeTypes[ext] || "audio/mpeg"; + const base64 = buffer.toString("base64"); + + return `data:${mimeType};base64,${base64}`; + } catch (error) { + logger.error("Failed to get theme sound data URL", error); + return null; + } +}; + +registerEvent("getThemeSoundDataUrl", getThemeSoundDataUrl); diff --git a/src/preload/index.ts b/src/preload/index.ts index 951591a5..bfb1de6e 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -577,6 +577,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("removeThemeAchievementSound", themeId), getThemeSoundPath: (themeId: string) => ipcRenderer.invoke("getThemeSoundPath", themeId), + getThemeSoundDataUrl: (themeId: string) => + ipcRenderer.invoke("getThemeSoundDataUrl", themeId), importThemeSoundFromStore: ( themeId: string, themeName: string, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 44696872..9eefb477 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -416,6 +416,7 @@ declare global { ) => Promise; removeThemeAchievementSound: (themeId: string) => Promise; getThemeSoundPath: (themeId: string) => Promise; + getThemeSoundDataUrl: (themeId: string) => Promise; importThemeSoundFromStore: ( themeId: string, themeName: string, diff --git a/src/renderer/src/helpers.ts b/src/renderer/src/helpers.ts index d5326de0..e16aa7a4 100644 --- a/src/renderer/src/helpers.ts +++ b/src/renderer/src/helpers.ts @@ -130,9 +130,11 @@ export const getAchievementSoundUrl = async (): Promise => { const activeTheme = await window.electron.getActiveCustomTheme(); if (activeTheme?.hasCustomSound) { - const soundPath = await window.electron.getThemeSoundPath(activeTheme.id); - if (soundPath) { - return `file://${soundPath}`; + const soundDataUrl = await window.electron.getThemeSoundDataUrl( + activeTheme.id + ); + if (soundDataUrl) { + return soundDataUrl; } } } catch (error) { diff --git a/src/renderer/src/pages/theme-editor/theme-editor.scss b/src/renderer/src/pages/theme-editor/theme-editor.scss index b34217f9..2d3d9067 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.scss +++ b/src/renderer/src/pages/theme-editor/theme-editor.scss @@ -47,6 +47,8 @@ position: relative; border: 1px solid globals.$muted-color; border-radius: 2px; + flex: 1; + min-width: 0; } &__footer { @@ -80,7 +82,7 @@ } &__info { - padding: 16px; + padding: 8px; p { font-size: 16px; @@ -93,25 +95,39 @@ &__notification-preview { padding-top: 12px; display: flex; - flex-direction: column; + flex-direction: row; gap: 16px; + align-items: flex-start; &__select-variation { flex: inherit; } } + &__notification-preview-controls { + display: flex; + flex-direction: column; + gap: 16px; + flex-shrink: 0; + } + &__notification-controls { display: flex; flex-direction: row; align-items: center; - gap: 16px; + gap: 8px; } &__sound-controls { display: flex; - flex-direction: row; + flex-direction: column; gap: 8px; - flex-wrap: wrap; + width: fit-content; + + button, + .button { + width: auto; + align-self: flex-start; + } } } diff --git a/src/renderer/src/pages/theme-editor/theme-editor.tsx b/src/renderer/src/pages/theme-editor/theme-editor.tsx index 6057084f..75df5e1e 100644 --- a/src/renderer/src/pages/theme-editor/theme-editor.tsx +++ b/src/renderer/src/pages/theme-editor/theme-editor.tsx @@ -213,57 +213,59 @@ export default function ThemeEditor() {
-
- { - return { - key: variation, - value: variation, - label: t(variation), - }; +
+
+ { + return { + key: variation, + value: variation, + label: t(variation), + }; + } + )} + onChange={(value) => + setNotificationVariation( + value.target.value as keyof typeof notificationVariations + ) } - )} - onChange={(value) => - setNotificationVariation( - value.target.value as keyof typeof notificationVariations - ) - } - /> + /> - - setNotificationAlignment( - e.target.value as AchievementCustomNotificationPosition - ) - } - options={achievementCustomNotificationPositionOptions} - /> -
+ + setNotificationAlignment( + e.target.value as AchievementCustomNotificationPosition + ) + } + options={achievementCustomNotificationPositionOptions} + /> +
-
- - - {theme?.hasCustomSound && ( - - )} - + {theme?.hasCustomSound && ( + + )} + + +