mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 10:03:33 +00:00
reorganize global types to shared directory
This commit is contained in:
parent
26c02e03c5
commit
9db2e51d2d
17 changed files with 160 additions and 144 deletions
|
|
@ -28,6 +28,7 @@ export default tseslint.config(
|
|||
...eslintPluginReactHooks.configs.recommended.rules,
|
||||
...eslintPluginReactRefresh.configs.vite.rules,
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-duplicate-enum-values': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
curly: ['error', 'all'],
|
||||
indent: [
|
||||
|
|
|
|||
|
|
@ -1,34 +1,6 @@
|
|||
import { AxiosHeaders } from 'axios';
|
||||
import isElectron from 'is-electron';
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverGte from 'semver/functions/gte';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ServerFeature } from '/@/renderer/api/features-types';
|
||||
import { ServerListItem } from '/@/renderer/api/types';
|
||||
import { toast } from '/@/renderer/components/toast';
|
||||
import { toast } from '/@/renderer/components';
|
||||
import { useAuthStore } from '/@/renderer/store';
|
||||
|
||||
// Since ts-rest client returns a strict response type, we need to add the headers to the body object
|
||||
export const resultWithHeaders = <ItemType extends z.ZodTypeAny>(itemSchema: ItemType) => {
|
||||
return z.object({
|
||||
data: itemSchema,
|
||||
headers: z.instanceof(AxiosHeaders),
|
||||
});
|
||||
};
|
||||
|
||||
export const resultSubsonicBaseResponse = <ItemType extends z.ZodRawShape>(
|
||||
itemSchema: ItemType,
|
||||
) => {
|
||||
return z.object({
|
||||
'subsonic-response': z
|
||||
.object({
|
||||
status: z.string(),
|
||||
version: z.string(),
|
||||
})
|
||||
.extend(itemSchema),
|
||||
});
|
||||
};
|
||||
import { ServerListItem } from '/@/shared/types/types';
|
||||
|
||||
export const authenticationFailure = (currentServer: null | ServerListItem) => {
|
||||
toast.error({
|
||||
|
|
@ -43,87 +15,3 @@ export const authenticationFailure = (currentServer: null | ServerListItem) => {
|
|||
useAuthStore.getState().actions.setCurrentServer(null);
|
||||
}
|
||||
};
|
||||
|
||||
export const hasFeature = (server: null | ServerListItem, feature: ServerFeature): boolean => {
|
||||
if (!server || !server.features) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (server.features[feature]?.length || 0) > 0;
|
||||
};
|
||||
|
||||
export type VersionInfo = ReadonlyArray<[string, Record<string, readonly number[]>]>;
|
||||
|
||||
/**
|
||||
* Returns the available server features given the version string.
|
||||
* @param versionInfo a list, in DECREASING VERSION order, of the features supported by the server.
|
||||
* The first version match will automatically consider the rest matched.
|
||||
* @example
|
||||
* ```
|
||||
* // The CORRECT way to order
|
||||
* const VERSION_INFO: VersionInfo = [
|
||||
* ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
||||
* ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
||||
* ];
|
||||
* // INCORRECT way to order
|
||||
* const VERSION_INFO: VersionInfo = [
|
||||
* ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
||||
* ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
||||
* ];
|
||||
* ```
|
||||
* @param version the version string (SemVer)
|
||||
* @returns a Record containing the matched features (if any) and their versions
|
||||
*/
|
||||
export const getFeatures = (
|
||||
versionInfo: VersionInfo,
|
||||
version: string,
|
||||
): Record<string, number[]> => {
|
||||
const cleanVersion = semverCoerce(version);
|
||||
const features: Record<string, number[]> = {};
|
||||
let matched = cleanVersion === null;
|
||||
|
||||
for (const [version, supportedFeatures] of versionInfo) {
|
||||
if (!matched) {
|
||||
matched = semverGte(cleanVersion!, version);
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
for (const [feature, feat] of Object.entries(supportedFeatures)) {
|
||||
if (feature in features) {
|
||||
features[feature].push(...feat);
|
||||
} else {
|
||||
features[feature] = [...feat];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return features;
|
||||
};
|
||||
|
||||
export const getClientType = (): string => {
|
||||
if (isElectron()) {
|
||||
return 'Desktop Client';
|
||||
}
|
||||
const agent = navigator.userAgent;
|
||||
switch (true) {
|
||||
case agent.toLowerCase().indexOf('edge') > -1:
|
||||
return 'Microsoft Edge';
|
||||
case agent.toLowerCase().indexOf('edg/') > -1:
|
||||
return 'Edge Chromium'; // Match also / to avoid matching for the older Edge
|
||||
case agent.toLowerCase().indexOf('opr') > -1:
|
||||
return 'Opera';
|
||||
case agent.toLowerCase().indexOf('chrome') > -1:
|
||||
return 'Chrome';
|
||||
case agent.toLowerCase().indexOf('trident') > -1:
|
||||
return 'Internet Explorer';
|
||||
case agent.toLowerCase().indexOf('firefox') > -1:
|
||||
return 'Firefox';
|
||||
case agent.toLowerCase().indexOf('safari') > -1:
|
||||
return 'Safari';
|
||||
default:
|
||||
return 'PC';
|
||||
}
|
||||
};
|
||||
|
||||
export const SEPARATOR_STRING = ' · ';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { JFAlbum, JFGenre, JFMusicFolder, JFPlaylist } from '/@/renderer/api/jellyfin.types';
|
||||
import { jfType } from '/@/renderer/api/jellyfin/jellyfin-types';
|
||||
import { JFAlbum, JFGenre, JFMusicFolder, JFPlaylist } from '/@/shared/api/jellyfin.types';
|
||||
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
|
|
@ -11,10 +11,9 @@ import {
|
|||
MusicFolder,
|
||||
Playlist,
|
||||
RelatedArtist,
|
||||
ServerListItem,
|
||||
ServerType,
|
||||
Song,
|
||||
} from '/@/renderer/api/types';
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { ServerListItem, ServerType } from '/@/shared/types/types';
|
||||
|
||||
const getStreamUrl = (args: {
|
||||
container?: string;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { SSArtistInfo } from '/@/renderer/api/subsonic.types';
|
||||
import { SSArtistInfo } from '/@/shared/api/subsonic.types';
|
||||
|
||||
export enum NDAlbumArtistListSort {
|
||||
ALBUM_COUNT = 'albumCount',
|
||||
|
|
@ -197,7 +197,7 @@ export type NDCreatePlaylistParams = {
|
|||
comment?: string;
|
||||
name: string;
|
||||
public?: boolean;
|
||||
rules?: null | Record<string, any>;
|
||||
rules?: null | Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type NDCreatePlaylistResponse = {
|
||||
|
|
@ -247,7 +247,7 @@ export type NDPlaylist = {
|
|||
ownerName: string;
|
||||
path: string;
|
||||
public: boolean;
|
||||
rules: null | Record<string, any>;
|
||||
rules: null | Record<string, unknown>;
|
||||
size: number;
|
||||
songCount: number;
|
||||
sync: boolean;
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import z from 'zod';
|
||||
|
||||
import { ndType } from './navidrome-types';
|
||||
|
||||
import { NDGenre } from '/@/renderer/api/navidrome.types';
|
||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import { NDGenre } from '/@/shared/api/navidrome.types';
|
||||
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
|
||||
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
|
|
@ -12,11 +11,10 @@ import {
|
|||
LibraryItem,
|
||||
Playlist,
|
||||
RelatedArtist,
|
||||
ServerListItem,
|
||||
ServerType,
|
||||
Song,
|
||||
User,
|
||||
} from '/@/renderer/api/types';
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { ServerListItem, ServerType } from '/@/shared/types/types';
|
||||
|
||||
const getImageUrl = (args: { url: null | string }) => {
|
||||
const { url } = args;
|
||||
|
|
@ -5,7 +5,7 @@ import {
|
|||
NDAlbumListSort,
|
||||
NDPlaylistListSort,
|
||||
NDSongListSort,
|
||||
} from '/@/renderer/api/navidrome.types';
|
||||
} from '/@/shared/api/navidrome.types';
|
||||
|
||||
const sortOrderValues = ['ASC', 'DESC'] as const;
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
|
|
@ -12,7 +12,7 @@ import {
|
|||
RelatedArtist,
|
||||
ServerListItem,
|
||||
ServerType,
|
||||
} from '/@/renderer/api/types';
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
const getCoverArtUrl = (args: {
|
||||
baseUrl: string | undefined;
|
||||
113
src/shared/api/utils.ts
Normal file
113
src/shared/api/utils.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { AxiosHeaders } from 'axios';
|
||||
import isElectron from 'is-electron';
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverGte from 'semver/functions/gte';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ServerListItem } from '/@/shared/types/domain-types';
|
||||
import { ServerFeature } from '/@/shared/types/features-types';
|
||||
|
||||
// Since ts-rest client returns a strict response type, we need to add the headers to the body object
|
||||
export const resultWithHeaders = <ItemType extends z.ZodTypeAny>(itemSchema: ItemType) => {
|
||||
return z.object({
|
||||
data: itemSchema,
|
||||
headers: z.instanceof(AxiosHeaders),
|
||||
});
|
||||
};
|
||||
|
||||
export const resultSubsonicBaseResponse = <ItemType extends z.ZodRawShape>(
|
||||
itemSchema: ItemType,
|
||||
) => {
|
||||
return z.object({
|
||||
'subsonic-response': z
|
||||
.object({
|
||||
status: z.string(),
|
||||
version: z.string(),
|
||||
})
|
||||
.extend(itemSchema),
|
||||
});
|
||||
};
|
||||
|
||||
export const hasFeature = (server: null | ServerListItem, feature: ServerFeature): boolean => {
|
||||
if (!server || !server.features) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (server.features[feature]?.length || 0) > 0;
|
||||
};
|
||||
|
||||
export type VersionInfo = ReadonlyArray<[string, Record<string, readonly number[]>]>;
|
||||
|
||||
/**
|
||||
* Returns the available server features given the version string.
|
||||
* @param versionInfo a list, in DECREASING VERSION order, of the features supported by the server.
|
||||
* The first version match will automatically consider the rest matched.
|
||||
* @example
|
||||
* ```
|
||||
* // The CORRECT way to order
|
||||
* const VERSION_INFO: VersionInfo = [
|
||||
* ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
||||
* ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
||||
* ];
|
||||
* // INCORRECT way to order
|
||||
* const VERSION_INFO: VersionInfo = [
|
||||
* ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
||||
* ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
||||
* ];
|
||||
* ```
|
||||
* @param version the version string (SemVer)
|
||||
* @returns a Record containing the matched features (if any) and their versions
|
||||
*/
|
||||
export const getFeatures = (
|
||||
versionInfo: VersionInfo,
|
||||
version: string,
|
||||
): Record<string, number[]> => {
|
||||
const cleanVersion = semverCoerce(version);
|
||||
const features: Record<string, number[]> = {};
|
||||
let matched = cleanVersion === null;
|
||||
|
||||
for (const [version, supportedFeatures] of versionInfo) {
|
||||
if (!matched) {
|
||||
matched = semverGte(cleanVersion!, version);
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
for (const [feature, feat] of Object.entries(supportedFeatures)) {
|
||||
if (feature in features) {
|
||||
features[feature].push(...feat);
|
||||
} else {
|
||||
features[feature] = [...feat];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return features;
|
||||
};
|
||||
|
||||
export const getClientType = (): string => {
|
||||
if (isElectron()) {
|
||||
return 'Desktop Client';
|
||||
}
|
||||
const agent = navigator.userAgent;
|
||||
switch (true) {
|
||||
case agent.toLowerCase().indexOf('edge') > -1:
|
||||
return 'Microsoft Edge';
|
||||
case agent.toLowerCase().indexOf('edg/') > -1:
|
||||
return 'Edge Chromium'; // Match also / to avoid matching for the older Edge
|
||||
case agent.toLowerCase().indexOf('opr') > -1:
|
||||
return 'Opera';
|
||||
case agent.toLowerCase().indexOf('chrome') > -1:
|
||||
return 'Chrome';
|
||||
case agent.toLowerCase().indexOf('trident') > -1:
|
||||
return 'Internet Explorer';
|
||||
case agent.toLowerCase().indexOf('firefox') > -1:
|
||||
return 'Firefox';
|
||||
case agent.toLowerCase().indexOf('safari') > -1:
|
||||
return 'Safari';
|
||||
default:
|
||||
return 'PC';
|
||||
}
|
||||
};
|
||||
|
||||
export const SEPARATOR_STRING = ' · ';
|
||||
|
|
@ -3,7 +3,8 @@ import reverse from 'lodash/reverse';
|
|||
import shuffle from 'lodash/shuffle';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ServerFeatures } from './features-types';
|
||||
import { jfType } from '../../renderer/api/jellyfin/jellyfin-types';
|
||||
import { ndType } from '../api/navidrome/navidrome-types';
|
||||
import {
|
||||
JFAlbumArtistListSort,
|
||||
JFAlbumListSort,
|
||||
|
|
@ -12,8 +13,7 @@ import {
|
|||
JFPlaylistListSort,
|
||||
JFSongListSort,
|
||||
JFSortOrder,
|
||||
} from './jellyfin.types';
|
||||
import { jfType } from './jellyfin/jellyfin-types';
|
||||
} from '../renderer/api/jellyfin.types';
|
||||
import {
|
||||
NDAlbumArtistListSort,
|
||||
NDAlbumListSort,
|
||||
|
|
@ -23,8 +23,8 @@ import {
|
|||
NDSongListSort,
|
||||
NDSortOrder,
|
||||
NDUserListSort,
|
||||
} from './navidrome.types';
|
||||
import { ndType } from './navidrome/navidrome-types';
|
||||
} from '../renderer/api/navidrome.types';
|
||||
import { ServerFeatures } from './features-types';
|
||||
|
||||
export enum LibraryItem {
|
||||
ALBUM = 'album',
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { AppRoute } from '@ts-rest/core';
|
||||
import { ReactNode } from 'react';
|
||||
import { Song } from 'src/main/features/core/lyrics/netease';
|
||||
|
||||
import { ServerFeatures } from '/@/renderer/api/features-types';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
|
|
@ -8,9 +9,8 @@ import {
|
|||
LibraryItem,
|
||||
Playlist,
|
||||
QueueSong,
|
||||
Song,
|
||||
} from '/@/renderer/api/types';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { ServerFeatures } from '/@/shared/types/features-types';
|
||||
|
||||
export enum ListDisplayType {
|
||||
CARD = 'card',
|
||||
|
|
@ -1,8 +1,24 @@
|
|||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
|
||||
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/i18n/**/*"],
|
||||
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/i18n/**/*", "src/types/**/*", "src/shared/**/*", "src/renderer/api/jellyfin/jellyfin-controller.ts", "src/renderer/api/jellyfin/jellyfin-api.ts", "src/renderer/api/navidrome/navidrome-api.ts", "src/renderer/api/navidrome/navidrome-controller.ts", "src/renderer/api/subsonic/subsonic-api.ts", "src/renderer/api/subsonic/subsonic-controller.ts"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["electron-vite/node"]
|
||||
"types": ["electron-vite/node"],
|
||||
"esModuleInterop": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"/@/renderer/*": [
|
||||
"src/renderer/*"
|
||||
],
|
||||
"/@/main/*": [
|
||||
"src/main/src/*"
|
||||
],
|
||||
"/@/preload/*": [
|
||||
"src/preload/*"
|
||||
],
|
||||
"/@/shared/*": [
|
||||
"src/shared/*"
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"src/renderer/**/*.tsx",
|
||||
"src/preload/*.d.ts",
|
||||
"src/i18n/**/*",
|
||||
"src/shared/**/*",
|
||||
"package.json"
|
||||
],
|
||||
"compilerOptions": {
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
"src/main/src/*"
|
||||
],
|
||||
"/@/shared/*": [
|
||||
"src/shared/src/*"
|
||||
"src/shared/*"
|
||||
],
|
||||
"/@/i18n/*": [
|
||||
"src/i18n/*"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue