mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-23 10:56:28 +02:00
lint fixes
This commit is contained in:
@@ -864,6 +864,18 @@ export const controller: GeneralController = {
|
||||
server.type,
|
||||
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
|
||||
},
|
||||
startScan(args) {
|
||||
const server = getServerById(args.apiClientProps.serverId);
|
||||
|
||||
if (!server) {
|
||||
throw new Error(`${i18n.t('error.apiRouteError')}: startScan`);
|
||||
}
|
||||
|
||||
return apiController(
|
||||
'startScan',
|
||||
server.type,
|
||||
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
|
||||
},
|
||||
updateInternetRadioStation(args) {
|
||||
const server = getServerById(args.apiClientProps.serverId);
|
||||
|
||||
@@ -924,16 +936,4 @@ export const controller: GeneralController = {
|
||||
server.type,
|
||||
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
|
||||
},
|
||||
startScan(args) {
|
||||
const server = getServerById(args.apiClientProps.serverId);
|
||||
|
||||
if (!server) {
|
||||
throw new Error(`${i18n.t('error.apiRouteError')}: startScan`);
|
||||
}
|
||||
|
||||
return apiController(
|
||||
'startScan',
|
||||
server.type,
|
||||
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -365,6 +365,16 @@ export const contract = c.router({
|
||||
400: jfType._response.error,
|
||||
},
|
||||
},
|
||||
startScan: {
|
||||
body: z.null(),
|
||||
method: 'POST',
|
||||
path: 'Library/Refresh',
|
||||
query: z.object({}),
|
||||
responses: {
|
||||
204: z.null(),
|
||||
400: jfType._response.error,
|
||||
},
|
||||
},
|
||||
updatePlaylist: {
|
||||
body: jfType._parameters.updatePlaylist,
|
||||
method: 'POST',
|
||||
@@ -392,16 +402,6 @@ export const contract = c.router({
|
||||
400: jfType._response.error,
|
||||
},
|
||||
},
|
||||
startScan: {
|
||||
body: z.null(),
|
||||
method: 'POST',
|
||||
path: 'Library/Refresh',
|
||||
query: z.object({}),
|
||||
responses: {
|
||||
204: z.null(),
|
||||
400: jfType._response.error,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const axiosClient = axios.create({});
|
||||
|
||||
@@ -1931,6 +1931,20 @@ export const JellyfinController: InternalControllerEndpoint = {
|
||||
|
||||
return null;
|
||||
},
|
||||
startScan: async (args) => {
|
||||
const { apiClientProps } = args;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).startScan({
|
||||
body: null,
|
||||
query: {},
|
||||
});
|
||||
|
||||
if (res.status !== 204) {
|
||||
throw new Error('Failed to start scan');
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
updateInternetRadioStation: async (args) => {
|
||||
const { apiClientProps, body, query } = args;
|
||||
|
||||
@@ -1996,20 +2010,6 @@ export const JellyfinController: InternalControllerEndpoint = {
|
||||
'Failed to upload playlist image',
|
||||
);
|
||||
},
|
||||
startScan: async (args) => {
|
||||
const { apiClientProps } = args;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).startScan({
|
||||
body: null,
|
||||
query: {},
|
||||
});
|
||||
|
||||
if (res.status !== 204) {
|
||||
throw new Error('Failed to start scan');
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
function getLibraryId(musicFolderId?: string | string[]) {
|
||||
|
||||
@@ -1261,6 +1261,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
id: res.body.data.id,
|
||||
};
|
||||
},
|
||||
startScan: SubsonicController.startScan,
|
||||
updateInternetRadioStation: async (args) => {
|
||||
const { apiClientProps, body, query } = args;
|
||||
|
||||
@@ -1411,5 +1412,4 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
|
||||
return res.data?.status === 'ok';
|
||||
},
|
||||
startScan: SubsonicController.startScan,
|
||||
};
|
||||
|
||||
@@ -186,6 +186,14 @@ export const contract = c.router({
|
||||
200: ssType._response.randomSongList,
|
||||
},
|
||||
},
|
||||
getScanStatus: {
|
||||
method: 'GET',
|
||||
path: 'getScanStatus.view',
|
||||
query: ssType._parameters.getScanStatus,
|
||||
responses: {
|
||||
200: ssType._response.getScanStatus,
|
||||
},
|
||||
},
|
||||
getServerInfo: {
|
||||
method: 'GET',
|
||||
path: 'getOpenSubsonicExtensions.view',
|
||||
@@ -337,6 +345,14 @@ export const contract = c.router({
|
||||
200: ssType._response.setRating,
|
||||
},
|
||||
},
|
||||
startScan: {
|
||||
method: 'GET',
|
||||
path: 'startScan.view',
|
||||
query: ssType._parameters.startScan,
|
||||
responses: {
|
||||
200: ssType._response.startScan,
|
||||
},
|
||||
},
|
||||
updateInternetRadioStation: {
|
||||
method: 'GET',
|
||||
path: 'updateInternetRadioStation.view',
|
||||
@@ -353,22 +369,6 @@ export const contract = c.router({
|
||||
200: ssType._response.baseResponse,
|
||||
},
|
||||
},
|
||||
startScan: {
|
||||
method: 'GET',
|
||||
path: 'startScan.view',
|
||||
query: ssType._parameters.startScan,
|
||||
responses: {
|
||||
200: ssType._response.startScan,
|
||||
},
|
||||
},
|
||||
getScanStatus: {
|
||||
method: 'GET',
|
||||
path: 'getScanStatus.view',
|
||||
query: ssType._parameters.getScanStatus,
|
||||
responses: {
|
||||
200: ssType._response.getScanStatus,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const axiosClient = axios.create({});
|
||||
|
||||
@@ -2461,6 +2461,17 @@ export const SubsonicController: InternalControllerEndpoint = {
|
||||
|
||||
return null;
|
||||
},
|
||||
startScan: async (args) => {
|
||||
const { apiClientProps } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).startScan({ query: {} });
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to start scan');
|
||||
}
|
||||
|
||||
return res.body.scanStatus;
|
||||
},
|
||||
updateInternetRadioStation: async (args) => {
|
||||
const { apiClientProps, body, query } = args;
|
||||
|
||||
@@ -2497,17 +2508,6 @@ export const SubsonicController: InternalControllerEndpoint = {
|
||||
|
||||
return null;
|
||||
},
|
||||
startScan: async (args) => {
|
||||
const { apiClientProps } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).startScan({ query: {} });
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to start scan');
|
||||
}
|
||||
|
||||
return res.body.scanStatus;
|
||||
},
|
||||
};
|
||||
|
||||
function getLibraryId(musicFolderId?: string | string[]) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { openModal } from '@mantine/modals';
|
||||
import isElectron from 'is-electron';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { openModal } from '@mantine/modals';
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { SongEditModal } from '/@/renderer/features/tag-editor/components/song-edit-modal';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||
import { Song } from '/@/shared/types/domain-types';
|
||||
import { SongEditModal } from '/@/renderer/features/tag-editor/components/song-edit-modal';
|
||||
|
||||
interface EditMetadataActionProps {
|
||||
albumIds?: string[];
|
||||
@@ -31,7 +31,7 @@ const getAlbumSongs = async (albumIds: string[], serverId: string): Promise<Song
|
||||
export const EditMetadataAction = ({ albumIds, songs: songItems }: EditMetadataActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const server = useCurrentServer();
|
||||
const songs = songItems?.filter((s) => s.path) ?? [];
|
||||
const songs = useMemo(() => songItems?.filter((s) => s.path) ?? [], [songItems]);
|
||||
const count = albumIds?.length ?? songs.length;
|
||||
|
||||
const onSelect = useCallback(async () => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { EditMetadataAction } from '/@/renderer/features/context-menu/actions/edit-metadata-action';
|
||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||
import { EditMetadataAction } from '/@/renderer/features/context-menu/actions/edit-metadata-action';
|
||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||
import { EditMetadataAction } from '/@/renderer/features/context-menu/actions/edit-metadata-action';
|
||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||
import { EditMetadataAction } from '/@/renderer/features/context-menu/actions/edit-metadata-action';
|
||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
.artworkBox {
|
||||
aspect-ratio: 1 / 1;
|
||||
.artwork-box {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
container-type: inline-size;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: var(--mantine-color-dark-6);
|
||||
border: 1px solid var(--mantine-color-default-border);
|
||||
border-radius: var(--mantine-radius-sm);
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.artworkImage {
|
||||
.artwork-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
@@ -20,10 +20,10 @@
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.placeholderText {
|
||||
.placeholder-text {
|
||||
font-size: 5cqw;
|
||||
}
|
||||
|
||||
.removeButton {
|
||||
.remove-button {
|
||||
color: var(--theme-colors-state-error);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { DragDropZone } from '/@/shared/components/drag-drop-zone/drag-drop-zone';
|
||||
import styles from './artwork-panel.module.css';
|
||||
|
||||
import { Button } from '/@/shared/components/button/button';
|
||||
import { DragDropZone } from '/@/shared/components/drag-drop-zone/drag-drop-zone';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
|
||||
import styles from './artwork-panel.module.css';
|
||||
|
||||
interface ArtworkPanelProps {
|
||||
artworkDisplayUrl: null | string;
|
||||
artworkIsMixed: boolean;
|
||||
@@ -49,12 +49,7 @@ export const ArtworkPanel = ({
|
||||
)}
|
||||
</DragDropZone>
|
||||
{showRemoveButton && (
|
||||
<Button
|
||||
className={styles.removeButton}
|
||||
onClick={onRemove}
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
>
|
||||
<Button className={styles.removeButton} onClick={onRemove} size="sm" variant="subtle">
|
||||
{removeArtworkLabel}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
.tabLabel {
|
||||
.tab-label {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tableScroller {
|
||||
.table-scroller {
|
||||
max-height: 55vh;
|
||||
overflow-y: auto;
|
||||
margin-top: var(--theme-spacing-sm);
|
||||
padding-top: var(--theme-spacing-sm);
|
||||
padding-bottom: var(--theme-spacing-sm);
|
||||
margin-top: var(--theme-spacing-sm);
|
||||
overflow-y: auto;
|
||||
border-top: 1px solid var(--mantine-color-default-border);
|
||||
}
|
||||
|
||||
.tableCell {
|
||||
.table-cell {
|
||||
padding: var(--theme-spacing-xs) var(--theme-spacing-sm);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.tableHeader {
|
||||
.table-header {
|
||||
width: 1%;
|
||||
color: var(--theme-colors-foreground-muted);
|
||||
font-weight: 500;
|
||||
padding: var(--theme-spacing-xs) var(--theme-spacing-sm);
|
||||
font-weight: 500;
|
||||
vertical-align: middle;
|
||||
color: var(--theme-colors-foreground-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { closeAllModals } from '@mantine/modals';
|
||||
import { useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useMetadataEditor } from '../hooks/use-metadata-editor';
|
||||
import { ArtworkPanel } from './artwork-panel';
|
||||
import styles from './song-edit-modal.module.css';
|
||||
import { TagFieldRow } from './tag-field-row';
|
||||
|
||||
import { Button } from '/@/shared/components/button/button';
|
||||
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
||||
@@ -15,10 +18,6 @@ import { Tabs } from '/@/shared/components/tabs/tabs';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { Song } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useMetadataEditor } from '../hooks/use-metadata-editor';
|
||||
import { ArtworkPanel } from './artwork-panel';
|
||||
import { TagFieldRow } from './tag-field-row';
|
||||
|
||||
export const SongEditModal = ({ songs }: { songs: Song[] }) => {
|
||||
const { t } = useTranslation();
|
||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
.removeCell {
|
||||
.remove-cell {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.removeButton {
|
||||
.remove-button {
|
||||
font-size: 1.05rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { RiCloseLine } from 'react-icons/ri';
|
||||
|
||||
import type { KnownTag } from '../utils/known-tags';
|
||||
|
||||
import styles from './tag-field-row.module.css';
|
||||
|
||||
import { Button } from '/@/shared/components/button/button';
|
||||
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
||||
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||
import { Table } from '/@/shared/components/table/table';
|
||||
import { Textarea } from '/@/shared/components/textarea/textarea';
|
||||
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||
|
||||
import type { KnownTag } from '../utils/known-tags';
|
||||
|
||||
import styles from './tag-field-row.module.css';
|
||||
import { Textarea } from '/@/shared/components/textarea/textarea';
|
||||
|
||||
interface TagFieldRowProps {
|
||||
isMixed: boolean;
|
||||
|
||||
@@ -4,12 +4,14 @@ import type {
|
||||
BatchProgress,
|
||||
TagEditorUtils,
|
||||
} from '/@/shared/types/tag-editor';
|
||||
|
||||
import { closeAllModals } from '@mantine/modals';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FIELD_PRIORITY, KNOWN_TAG_MAP, KNOWN_TAGS, type KnownTag } from '../utils/known-tags';
|
||||
import { base64ToBytes, filterTagSummary, formatBatchFileErrors } from '../utils/utils';
|
||||
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
|
||||
@@ -49,15 +49,15 @@ const FIELD_TYPE_OVERRIDES: Partial<
|
||||
lyrics: 'textarea',
|
||||
};
|
||||
|
||||
/** Which form control to render for a tag row. */
|
||||
export type TagFieldType = 'boolean' | 'number' | 'string' | 'textarea';
|
||||
|
||||
export interface KnownTag {
|
||||
key: string;
|
||||
label: string;
|
||||
type: TagFieldType;
|
||||
}
|
||||
|
||||
/** Which form control to render for a tag row. */
|
||||
export type TagFieldType = 'boolean' | 'number' | 'string' | 'textarea';
|
||||
|
||||
const humanizeKey = (key: string): string =>
|
||||
key
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
|
||||
Reference in New Issue
Block a user