mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 18:13:31 +00:00
restructure files onto electron-vite boilerplate
This commit is contained in:
parent
91ce2cd8a1
commit
1cf587bc8f
457 changed files with 9927 additions and 11705 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }>`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue