feishin/src/renderer/app.tsx

297 lines
11 KiB
TypeScript
Raw Normal View History

import { useEffect, useMemo, useState, useRef } from 'react';
2022-12-19 15:59:14 -08:00
import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model';
import { ModuleRegistry } from '@ag-grid-community/core';
import { InfiniteRowModelModule } from '@ag-grid-community/infinite-row-model';
import { MantineProvider } from '@mantine/core';
2023-08-07 21:44:39 -07:00
import isElectron from 'is-electron';
2022-12-19 15:59:14 -08:00
import { initSimpleImg } from 'react-simple-img';
import { toast } from './components';
2022-12-19 15:59:14 -08:00
import { useTheme } from './hooks';
2023-08-07 21:44:39 -07:00
import { IsUpdatedDialog } from './is-updated-dialog';
2022-12-19 15:59:14 -08:00
import { AppRouter } from './router/app-router';
import {
useHotkeySettings,
usePlaybackSettings,
useRemoteSettings,
useSettingsStore,
} from './store/settings.store';
2022-12-19 15:59:14 -08:00
import './styles/global.scss';
import { ContextMenuProvider } from '/@/renderer/features/context-menu';
2022-12-31 19:26:58 -08:00
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
import { PlayQueueHandlerContext } from '/@/renderer/features/player';
import { getMpvProperties } from '/@/renderer/features/settings/components/playback/mpv-settings';
2024-08-27 08:26:34 -07:00
import { PlayerState, useCssSettings, usePlayerStore, useQueueControls } from '/@/renderer/store';
import { FontType, PlaybackType, PlayerStatus, WebAudio } from '/@/renderer/types';
2023-07-23 05:31:10 -07:00
import '@ag-grid-community/styles/ag-grid.css';
import { WebAudioContext } from '/@/renderer/features/player/context/webaudio-context';
2023-10-23 06:58:39 -07:00
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
import i18n from '/@/i18n/i18n';
2024-02-01 08:17:31 -08:00
import { useServerVersion } from '/@/renderer/hooks/use-server-version';
import { updateSong } from '/@/renderer/features/player/update-remote-song';
2024-08-27 08:26:34 -07:00
import { sanitizeCss } from '/@/renderer/utils/sanitize';
2024-09-01 08:26:30 -07:00
import { setQueue } from '/@/renderer/utils/set-transcoded-queue-data';
2022-12-19 15:59:14 -08:00
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
initSimpleImg({ threshold: 0.05 }, true);
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
const ipc = isElectron() ? window.electron.ipc : null;
const remote = isElectron() ? window.electron.remote : null;
const utils = isElectron() ? window.electron.utils : null;
2022-12-19 15:59:14 -08:00
export const App = () => {
2023-07-01 19:10:05 -07:00
const theme = useTheme();
const accent = useSettingsStore((store) => store.general.accent);
const language = useSettingsStore((store) => store.general.language);
const nativeImageAspect = useSettingsStore((store) => store.general.nativeAspectRatio);
const { builtIn, custom, system, type } = useSettingsStore((state) => state.font);
2024-08-27 08:26:34 -07:00
const { enabled, content } = useCssSettings();
2023-07-01 19:10:05 -07:00
const { type: playbackType } = usePlaybackSettings();
const { bindings } = useHotkeySettings();
const handlePlayQueueAdd = useHandlePlayQueueAdd();
const { clearQueue, restoreQueue } = useQueueControls();
const remoteSettings = useRemoteSettings();
const textStyleRef = useRef<HTMLStyleElement>();
2024-08-27 08:26:34 -07:00
const cssRef = useRef<HTMLStyleElement>();
2023-10-23 06:58:39 -07:00
useDiscordRpc();
2024-02-01 08:17:31 -08:00
useServerVersion();
2023-07-01 19:10:05 -07:00
useEffect(() => {
if (type === FontType.SYSTEM && system) {
const root = document.documentElement;
root.style.setProperty('--content-font-family', 'dynamic-font');
if (!textStyleRef.current) {
textStyleRef.current = document.createElement('style');
document.body.appendChild(textStyleRef.current);
}
textStyleRef.current.textContent = `
@font-face {
font-family: "dynamic-font";
src: local("${system}");
}`;
} else if (type === FontType.CUSTOM && custom) {
const root = document.documentElement;
root.style.setProperty('--content-font-family', 'dynamic-font');
if (!textStyleRef.current) {
textStyleRef.current = document.createElement('style');
document.body.appendChild(textStyleRef.current);
}
textStyleRef.current.textContent = `
@font-face {
font-family: "dynamic-font";
src: url("feishin://${custom}");
}`;
} else {
const root = document.documentElement;
root.style.setProperty('--content-font-family', builtIn);
}
}, [builtIn, custom, system, type]);
2023-07-01 19:10:05 -07:00
const [webAudio, setWebAudio] = useState<WebAudio>();
2024-08-27 08:26:34 -07:00
useEffect(() => {
if (enabled && content) {
// Yes, CSS is sanitized here as well. Prevent a suer from changing the
// localStorage to bypass sanitizing.
const sanitized = sanitizeCss(content);
if (!cssRef.current) {
cssRef.current = document.createElement('style');
document.body.appendChild(cssRef.current);
}
cssRef.current.textContent = sanitized;
return () => {
cssRef.current!.textContent = '';
};
}
return () => {};
}, [content, enabled]);
useEffect(() => {
const root = document.documentElement;
root.style.setProperty('--primary-color', accent);
}, [accent]);
useEffect(() => {
const root = document.documentElement;
root.style.setProperty('--image-fit', nativeImageAspect ? 'contain' : 'cover');
}, [nativeImageAspect]);
2023-07-23 05:31:10 -07:00
const providerValue = useMemo(() => {
return { handlePlayQueueAdd };
}, [handlePlayQueueAdd]);
const webAudioProvider = useMemo(() => {
return { setWebAudio, webAudio };
}, [webAudio]);
2023-07-01 19:10:05 -07:00
// Start the mpv instance on startup
useEffect(() => {
const initializeMpv = async () => {
if (playbackType === PlaybackType.LOCAL) {
const isRunning: boolean | undefined = await mpvPlayer?.isRunning();
2023-07-01 19:10:05 -07:00
mpvPlayer?.stop();
if (!isRunning) {
const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters;
const properties: Record<string, any> = {
2024-09-29 16:16:33 -07:00
speed: usePlayerStore.getState().speed,
...getMpvProperties(useSettingsStore.getState().playback.mpvProperties),
};
2023-07-01 19:10:05 -07:00
await mpvPlayer?.initialize({
extraParameters,
properties,
});
2023-07-01 19:10:05 -07:00
mpvPlayer?.volume(properties.volume);
}
2023-07-01 19:10:05 -07:00
}
utils?.restoreQueue();
};
2023-05-20 19:56:17 -07:00
if (isElectron()) {
2023-07-01 19:10:05 -07:00
initializeMpv();
}
return () => {
clearQueue();
mpvPlayer?.stop();
mpvPlayer?.cleanup();
};
2023-07-01 19:10:05 -07:00
}, [clearQueue, playbackType]);
2023-07-01 19:10:05 -07:00
useEffect(() => {
if (isElectron()) {
ipc?.send('set-global-shortcuts', bindings);
}
}, [bindings]);
useEffect(() => {
if (utils) {
utils.onSaveQueue(() => {
2023-07-01 19:10:05 -07:00
const { current, queue } = usePlayerStore.getState();
const stateToSave: Partial<Pick<PlayerState, 'current' | 'queue'>> = {
current: {
...current,
status: PlayerStatus.PAUSED,
},
queue,
};
utils.saveQueue(stateToSave);
2023-07-01 19:10:05 -07:00
});
utils.onRestoreQueue((_event: any, data) => {
2023-07-01 19:10:05 -07:00
const playerData = restoreQueue(data);
if (playbackType === PlaybackType.LOCAL) {
2024-09-01 08:26:30 -07:00
setQueue(playerData, true);
2023-07-01 19:10:05 -07:00
}
updateSong(playerData.current.song);
2023-07-01 19:10:05 -07:00
});
}
2023-07-01 19:10:05 -07:00
return () => {
ipc?.removeAllListeners('renderer-restore-queue');
ipc?.removeAllListeners('renderer-save-queue');
2023-07-01 19:10:05 -07:00
};
}, [playbackType, restoreQueue]);
useEffect(() => {
if (remote) {
remote
?.updateSetting(
remoteSettings.enabled,
remoteSettings.port,
remoteSettings.username,
remoteSettings.password,
)
.catch((error) => {
toast.warn({ message: error, title: 'Failed to enable remote' });
});
}
// We only want to fire this once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (language) {
i18n.changeLanguage(language);
}
}, [language]);
2023-07-01 19:10:05 -07:00
return (
<MantineProvider
withGlobalStyles
withNormalizeCSS
theme={{
colorScheme: theme as 'light' | 'dark',
components: {
Modal: {
styles: {
body: { background: 'var(--modal-bg)', padding: '1rem !important' },
close: { marginRight: '0.5rem' },
content: { borderRadius: '5px' },
header: {
background: 'var(--modal-header-bg)',
paddingBottom: '1rem',
},
title: { fontSize: 'medium', fontWeight: 500 },
},
},
},
defaultRadius: 'xs',
dir: 'ltr',
focusRing: 'auto',
focusRingStyles: {
inputStyles: () => ({
border: '1px solid var(--primary-color)',
}),
resetStyles: () => ({ outline: 'none' }),
styles: () => ({
outline: '1px solid var(--primary-color)',
outlineOffset: '-1px',
}),
},
fontFamily: 'var(--content-font-family)',
fontSizes: {
lg: '1.1rem',
md: '1rem',
sm: '0.9rem',
xl: '1.5rem',
xs: '0.8rem',
},
headings: {
fontFamily: 'var(--content-font-family)',
fontWeight: 700,
},
other: {},
spacing: {
lg: '2rem',
md: '1rem',
sm: '0.5rem',
xl: '4rem',
xs: '0rem',
},
}}
>
<PlayQueueHandlerContext.Provider value={providerValue}>
<ContextMenuProvider>
<WebAudioContext.Provider value={webAudioProvider}>
<AppRouter />
</WebAudioContext.Provider>{' '}
</ContextMenuProvider>
</PlayQueueHandlerContext.Provider>
2023-08-07 21:44:39 -07:00
<IsUpdatedDialog />
2023-07-01 19:10:05 -07:00
</MantineProvider>
);
2022-12-19 15:59:14 -08:00
};