restructure files onto electron-vite boilerplate

This commit is contained in:
jeffvli 2025-05-18 14:03:18 -07:00
parent 91ce2cd8a1
commit 1cf587bc8f
457 changed files with 9927 additions and 11705 deletions

View file

@ -1,19 +1,21 @@
import { useMemo } from 'react';
import { Divider, Group, Stack } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useDebouncedValue } from '@mantine/hooks';
import { openModal } from '@mantine/modals';
import orderBy from 'lodash/orderBy';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import {
InternetProviderLyricSearchResponse,
LyricSource,
LyricsOverride,
} from '../../../api/types';
import { useLyricSearch } from '../queries/lyric-search-query';
import { ScrollArea, Spinner, Text, TextInput } from '/@/renderer/components';
import i18n from '/@/i18n/i18n';
import { ScrollArea, Spinner, Text, TextInput } from '/@/renderer/components';
const SearchItem = styled.button`
all: unset;
@ -34,7 +36,7 @@ interface SearchResultProps {
onClick?: () => void;
}
const SearchResult = ({ data, onClick }: SearchResultProps) => {
const { artist, name, source, score, id } = data;
const { artist, id, name, score, source } = data;
const percentageScore = useMemo(() => {
if (!score) return 0;
@ -140,8 +142,8 @@ export const LyricsSearchForm = ({ artist, name, onSearchOverride }: LyricSearch
<Spinner container />
) : (
<ScrollArea
offsetScrollbars
h={400}
offsetScrollbars
pr="1rem"
type="auto"
w="100%"
@ -149,8 +151,8 @@ export const LyricsSearchForm = ({ artist, name, onSearchOverride }: LyricSearch
<Stack spacing="md">
{searchResults.map((result) => (
<SearchResult
key={`${result.source}-${result.id}`}
data={result}
key={`${result.source}-${result.id}`}
onClick={() => {
onSearchOverride?.({
artist: result.artist,

View file

@ -1,10 +1,11 @@
import { ComponentPropsWithoutRef } from 'react';
import { TextTitle } from '/@/renderer/components/text-title';
import { TitleProps } from '@mantine/core';
import { ComponentPropsWithoutRef } from 'react';
import styled from 'styled-components';
import { TextTitle } from '/@/renderer/components/text-title';
interface LyricLineProps extends ComponentPropsWithoutRef<'div'> {
alignment: 'left' | 'center' | 'right';
alignment: 'center' | 'left' | 'right';
fontSize: number;
text: string;
}
@ -34,7 +35,7 @@ const StyledText = styled(TextTitle)<TitleProps & { $alignment: string; $fontSiz
}
`;
export const LyricLine = ({ text, alignment, fontSize, ...props }: LyricLineProps) => {
export const LyricLine = ({ alignment, fontSize, text, ...props }: LyricLineProps) => {
return (
<StyledText
$alignment={alignment}

View file

@ -2,6 +2,7 @@ import { Box, Center, Group, Select, SelectItem } from '@mantine/core';
import isElectron from 'is-electron';
import { useTranslation } from 'react-i18next';
import { RiAddFill, RiSubtractFill } from 'react-icons/ri';
import { LyricsOverride } from '/@/renderer/api/types';
import { Button, NumberInput, Tooltip } from '/@/renderer/components';
import { openLyricSearchModal } from '/@/renderer/features/lyrics/components/lyrics-search-form';
@ -56,9 +57,9 @@ export const LyricsActions = ({
<Select
clearable={false}
data={languages}
onChange={(value) => setIndex(parseInt(value!, 10))}
style={{ bottom: 30, position: 'absolute' }}
value={index.toString()}
onChange={(value) => setIndex(parseInt(value!, 10))}
/>
</Center>
)}
@ -66,9 +67,7 @@ export const LyricsActions = ({
<Group position="center">
{isDesktop && sources.length ? (
<Button
uppercase
disabled={isActionsDisabled}
variant="subtle"
onClick={() =>
openLyricSearchModal({
artist: currentSong?.artistName,
@ -76,14 +75,16 @@ export const LyricsActions = ({
onSearchOverride,
})
}
uppercase
variant="subtle"
>
{t('common.search', { postProcess: 'titleCase' })}
</Button>
) : null}
<Button
aria-label="Decrease lyric offset"
variant="subtle"
onClick={() => handleLyricOffset(delayMs - 50)}
variant="subtle"
>
<RiSubtractFill />
</Button>
@ -93,25 +94,25 @@ export const LyricsActions = ({
>
<NumberInput
aria-label="Lyric offset"
onChange={handleLyricOffset}
styles={{ input: { textAlign: 'center' } }}
value={delayMs || 0}
width={55}
onChange={handleLyricOffset}
/>
</Tooltip>
<Button
aria-label="Increase lyric offset"
variant="subtle"
onClick={() => handleLyricOffset(delayMs + 50)}
variant="subtle"
>
<RiAddFill />
</Button>
{isDesktop && sources.length ? (
<Button
uppercase
disabled={isActionsDisabled}
variant="subtle"
onClick={onResetLyric}
uppercase
variant="subtle"
>
{t('common.reset', { postProcess: 'sentenceCase' })}
</Button>
@ -121,10 +122,10 @@ export const LyricsActions = ({
<Box style={{ position: 'absolute', right: 0, top: 0 }}>
{isDesktop && sources.length ? (
<Button
uppercase
disabled={isActionsDisabled}
variant="subtle"
onClick={onRemoveLyric}
uppercase
variant="subtle"
>
{t('common.clear', { postProcess: 'sentenceCase' })}
</Button>
@ -134,10 +135,10 @@ export const LyricsActions = ({
<Box style={{ position: 'absolute', right: 0, top: -50 }}>
{isDesktop && sources.length ? (
<Button
uppercase
disabled={isActionsDisabled}
variant="subtle"
onClick={onTranslateLyric}
uppercase
variant="subtle"
>
{t('common.translation', { postProcess: 'sentenceCase' })}
</Button>

View file

@ -1,24 +1,26 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Center, Group } from '@mantine/core';
import { AnimatePresence, motion } from 'framer-motion';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { useTranslation } from 'react-i18next';
import { RiInformationFill } from 'react-icons/ri';
import styled from 'styled-components';
import { useSongLyricsByRemoteId, useSongLyricsBySong } from './queries/lyric-query';
import { translateLyrics } from './queries/lyric-translate';
import { SynchronizedLyrics, SynchronizedLyricsProps } from './synchronized-lyrics';
import { queryKeys } from '/@/renderer/api/query-keys';
import { FullLyricsMetadata, LyricSource, LyricsOverride } from '/@/renderer/api/types';
import { Spinner, TextTitle } from '/@/renderer/components';
import { ErrorFallback } from '/@/renderer/features/action-required';
import { LyricsActions } from '/@/renderer/features/lyrics/lyrics-actions';
import {
UnsynchronizedLyrics,
UnsynchronizedLyricsProps,
} from '/@/renderer/features/lyrics/unsynchronized-lyrics';
import { useCurrentSong, usePlayerStore, useLyricsSettings } from '/@/renderer/store';
import { FullLyricsMetadata, LyricSource, LyricsOverride } from '/@/renderer/api/types';
import { LyricsActions } from '/@/renderer/features/lyrics/lyrics-actions';
import { queryKeys } from '/@/renderer/api/query-keys';
import { queryClient } from '/@/renderer/lib/react-query';
import { useCurrentSong, useLyricsSettings, usePlayerStore } from '/@/renderer/store';
const ActionsContainer = styled.div`
position: absolute;
@ -89,7 +91,7 @@ export const Lyrics = () => {
const lyricsSettings = useLyricsSettings();
const { t } = useTranslation();
const [index, setIndex] = useState(0);
const [translatedLyrics, setTranslatedLyrics] = useState<string | null>(null);
const [translatedLyrics, setTranslatedLyrics] = useState<null | string>(null);
const [showTranslation, setShowTranslation] = useState(false);
const { data, isInitialLoading } = useSongLyricsBySong(
@ -153,7 +155,7 @@ export const Lyrics = () => {
: lyrics.lyrics;
const { translationApiKey, translationApiProvider, translationTargetLanguage } =
lyricsSettings;
const TranslatedText: string | null = await translateLyrics(
const TranslatedText: null | string = await translateLyrics(
originalLyrics,
translationApiKey,
translationApiProvider,
@ -250,11 +252,11 @@ export const Lyrics = () => {
<LyricsActions
index={index}
languages={languages}
setIndex={setIndex}
onRemoveLyric={handleOnRemoveLyric}
onResetLyric={handleOnResetLyric}
onSearchOverride={handleOnSearchOverride}
onTranslateLyric={handleOnTranslateLyric}
setIndex={setIndex}
/>
</ActionsContainer>
</LyricsContainer>

View file

@ -1,23 +1,24 @@
import { UseQueryResult, useQuery, useQueryClient } from '@tanstack/react-query';
import { useQuery, useQueryClient, UseQueryResult } from '@tanstack/react-query';
import isElectron from 'is-electron';
import { api } from '/@/renderer/api';
import { ServerFeature } from '/@/renderer/api/features-types';
import { queryKeys } from '/@/renderer/api/query-keys';
import {
FullLyricsMetadata,
InternetProviderLyricResponse,
LyricGetQuery,
LyricsQuery,
QueueSong,
SynchronizedLyricsArray,
InternetProviderLyricResponse,
FullLyricsMetadata,
LyricGetQuery,
StructuredLyric,
ServerType,
StructuredLyric,
SynchronizedLyricsArray,
} from '/@/renderer/api/types';
import { hasFeature } from '/@/renderer/api/utils';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById, useLyricsSettings } from '/@/renderer/store';
import { queryKeys } from '/@/renderer/api/query-keys';
import { api } from '/@/renderer/api';
import isElectron from 'is-electron';
import { hasFeature } from '/@/renderer/api/utils';
import { ServerFeature } from '/@/renderer/api/features-types';
const lyricsIpc = isElectron() ? window.electron.lyrics : null;
const lyricsIpc = isElectron() ? window.api.lyrics : null;
// Match LRC lyrics format by https://github.com/ustbhuangyi/lyric-parser
// [mm:ss.SSS] text
@ -62,7 +63,7 @@ const formatLyrics = (lyrics: string) => {
export const useServerLyrics = (
args: QueryHookArgs<LyricsQuery>,
): UseQueryResult<string | null> => {
): UseQueryResult<null | string> => {
const { query, serverId } = args;
const server = getServerById(serverId);
@ -92,7 +93,7 @@ export const useSongLyricsBySong = (
cacheTime: Infinity,
enabled: !!song && !!server,
onError: () => {},
queryFn: async ({ signal }): Promise<FullLyricsMetadata | StructuredLyric[] | null> => {
queryFn: async ({ signal }): Promise<FullLyricsMetadata | null | StructuredLyric[]> => {
if (!server) throw new Error('Server not found');
if (!song) return null;
@ -156,7 +157,7 @@ export const useSongLyricsBySong = (
export const useSongLyricsByRemoteId = (
args: QueryHookArgs<Partial<LyricGetQuery>>,
): UseQueryResult<string | null> => {
): UseQueryResult<null | string> => {
const queryClient = useQueryClient();
const { query, serverId } = args;

View file

@ -1,5 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import isElectron from 'is-electron';
import { queryKeys } from '/@/renderer/api/query-keys';
import {
InternetProviderLyricSearchResponse,
@ -8,7 +9,7 @@ import {
} from '/@/renderer/api/types';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
const lyricsIpc = isElectron() ? window.electron.lyrics : null;
const lyricsIpc = isElectron() ? window.api.lyrics : null;
export const useLyricSearch = (args: Omit<QueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
const { options, query } = args;

View file

@ -3,8 +3,8 @@ import axios from 'axios';
export const translateLyrics = async (
originalLyrics: string,
translationApiKey: string,
translationApiProvider: string | null,
translationTargetLanguage: string | null,
translationApiProvider: null | string,
translationTargetLanguage: null | string,
) => {
let TranslatedText = '';
if (translationApiProvider === 'Microsoft Azure') {

View file

@ -1,4 +1,11 @@
import isElectron from 'is-electron';
import { useCallback, useEffect, useRef } from 'react';
import styled from 'styled-components';
import { FullLyricsMetadata, SynchronizedLyricsArray } from '/@/renderer/api/types';
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble';
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
import {
useCurrentPlayer,
useCurrentStatus,
@ -10,16 +17,10 @@ import {
useSetCurrentTime,
} from '/@/renderer/store';
import { PlaybackType, PlayerStatus } from '/@/renderer/types';
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
import isElectron from 'is-electron';
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
import { FullLyricsMetadata, SynchronizedLyricsArray } from '/@/renderer/api/types';
import styled from 'styled-components';
import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble';
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
const utils = isElectron() ? window.electron.utils : null;
const mpris = isElectron() && utils?.isLinux() ? window.electron.mpris : null;
const mpvPlayer = isElectron() ? window.api.mpvPlayer : null;
const utils = isElectron() ? window.api.utils : null;
const mpris = isElectron() && utils?.isLinux() ? window.api.mpris : null;
const SynchronizedLyricsContainer = styled.div<{ $gap: number }>`
display: flex;
@ -55,7 +56,7 @@ const SynchronizedLyricsContainer = styled.div<{ $gap: number }>`
export interface SynchronizedLyricsProps extends Omit<FullLyricsMetadata, 'lyrics'> {
lyrics: SynchronizedLyricsArray;
translatedLyrics?: string | null;
translatedLyrics?: null | string;
}
export const SynchronizedLyrics = ({
@ -185,7 +186,7 @@ export const SynchronizedLyrics = ({
'sychronized-lyrics-scroll-container',
) as HTMLElement;
const currentLyric = document.querySelector(`#lyric-${index}`) as HTMLElement;
// eslint-disable-next-line no-unsafe-optional-chaining
const offsetTop = currentLyric?.offsetTop - doc?.clientHeight / 2 ?? 0;
if (currentLyric === null) {
@ -372,16 +373,16 @@ export const SynchronizedLyrics = ({
className="lyric-line synchronized"
fontSize={settings.fontSize}
id={`lyric-${idx}`}
text={text}
onClick={() => handleSeek(time / 1000)}
text={text}
/>
{translatedLyrics && (
<LyricLine
alignment={settings.alignment}
className="lyric-line synchronized translation"
fontSize={settings.fontSize * 0.8}
text={translatedLyrics.split('\n')[idx]}
onClick={() => handleSeek(time / 1000)}
text={translatedLyrics.split('\n')[idx]}
/>
)}
</div>

View file

@ -1,12 +1,13 @@
import { useMemo } from 'react';
import styled from 'styled-components';
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
import { FullLyricsMetadata } from '/@/renderer/api/types';
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
import { useLyricsSettings } from '/@/renderer/store';
export interface UnsynchronizedLyricsProps extends Omit<FullLyricsMetadata, 'lyrics'> {
lyrics: string;
translatedLyrics?: string | null;
translatedLyrics?: null | string;
}
const UnsynchronizedLyricsContainer = styled.div<{ $gap: number }>`