From 9db2e51d2dceb9cae3509a43bc0d698081b7c7df Mon Sep 17 00:00:00 2001 From: jeffvli Date: Tue, 20 May 2025 18:08:51 -0700 Subject: [PATCH] reorganize global types to shared directory --- eslint.config.mjs | 1 + src/renderer/api/utils.ts | 116 +----------------- .../api/jellyfin.types.ts | 0 .../api/jellyfin/jellyfin-normalize.ts | 9 +- .../api/jellyfin/jellyfin-types.ts | 0 .../api/navidrome.types.ts | 6 +- .../api/navidrome/navidrome-normalize.ts | 12 +- .../api/navidrome/navidrome-types.ts | 2 +- .../api/subsonic.types.ts | 0 .../api/subsonic/subsonic-normalize.ts | 4 +- .../api/subsonic/subsonic-types.ts | 0 src/shared/api/utils.ts | 113 +++++++++++++++++ .../types.ts => shared/types/domain-types.ts} | 10 +- .../api => shared/types}/features-types.ts | 0 src/{renderer => shared/types}/types.ts | 8 +- tsconfig.node.json | 20 ++- tsconfig.web.json | 3 +- 17 files changed, 160 insertions(+), 144 deletions(-) rename src/{renderer => shared}/api/jellyfin.types.ts (100%) rename src/{renderer => shared}/api/jellyfin/jellyfin-normalize.ts (98%) rename src/{renderer => shared}/api/jellyfin/jellyfin-types.ts (100%) rename src/{renderer => shared}/api/navidrome.types.ts (99%) rename src/{renderer => shared}/api/navidrome/navidrome-normalize.ts (97%) rename src/{renderer => shared}/api/navidrome/navidrome-types.ts (99%) rename src/{renderer => shared}/api/subsonic.types.ts (100%) rename src/{renderer => shared}/api/subsonic/subsonic-normalize.ts (98%) rename src/{renderer => shared}/api/subsonic/subsonic-types.ts (100%) create mode 100644 src/shared/api/utils.ts rename src/{renderer/api/types.ts => shared/types/domain-types.ts} (99%) rename src/{renderer/api => shared/types}/features-types.ts (100%) rename src/{renderer => shared/types}/types.ts (96%) diff --git a/eslint.config.mjs b/eslint.config.mjs index b286f9b3..4a50a3bd 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -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: [ diff --git a/src/renderer/api/utils.ts b/src/renderer/api/utils.ts index 00914d12..09720a7f 100644 --- a/src/renderer/api/utils.ts +++ b/src/renderer/api/utils.ts @@ -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 = (itemSchema: ItemType) => { - return z.object({ - data: itemSchema, - headers: z.instanceof(AxiosHeaders), - }); -}; - -export const resultSubsonicBaseResponse = ( - 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]>; - -/** - * 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 => { - const cleanVersion = semverCoerce(version); - const features: Record = {}; - 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 = ' · '; diff --git a/src/renderer/api/jellyfin.types.ts b/src/shared/api/jellyfin.types.ts similarity index 100% rename from src/renderer/api/jellyfin.types.ts rename to src/shared/api/jellyfin.types.ts diff --git a/src/renderer/api/jellyfin/jellyfin-normalize.ts b/src/shared/api/jellyfin/jellyfin-normalize.ts similarity index 98% rename from src/renderer/api/jellyfin/jellyfin-normalize.ts rename to src/shared/api/jellyfin/jellyfin-normalize.ts index f61d86bb..0d453ff2 100644 --- a/src/renderer/api/jellyfin/jellyfin-normalize.ts +++ b/src/shared/api/jellyfin/jellyfin-normalize.ts @@ -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; diff --git a/src/renderer/api/jellyfin/jellyfin-types.ts b/src/shared/api/jellyfin/jellyfin-types.ts similarity index 100% rename from src/renderer/api/jellyfin/jellyfin-types.ts rename to src/shared/api/jellyfin/jellyfin-types.ts diff --git a/src/renderer/api/navidrome.types.ts b/src/shared/api/navidrome.types.ts similarity index 99% rename from src/renderer/api/navidrome.types.ts rename to src/shared/api/navidrome.types.ts index 15473f6a..3f28bd2a 100644 --- a/src/renderer/api/navidrome.types.ts +++ b/src/shared/api/navidrome.types.ts @@ -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; + rules?: null | Record; }; export type NDCreatePlaylistResponse = { @@ -247,7 +247,7 @@ export type NDPlaylist = { ownerName: string; path: string; public: boolean; - rules: null | Record; + rules: null | Record; size: number; songCount: number; sync: boolean; diff --git a/src/renderer/api/navidrome/navidrome-normalize.ts b/src/shared/api/navidrome/navidrome-normalize.ts similarity index 97% rename from src/renderer/api/navidrome/navidrome-normalize.ts rename to src/shared/api/navidrome/navidrome-normalize.ts index 70d69cee..2177d626 100644 --- a/src/renderer/api/navidrome/navidrome-normalize.ts +++ b/src/shared/api/navidrome/navidrome-normalize.ts @@ -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; diff --git a/src/renderer/api/navidrome/navidrome-types.ts b/src/shared/api/navidrome/navidrome-types.ts similarity index 99% rename from src/renderer/api/navidrome/navidrome-types.ts rename to src/shared/api/navidrome/navidrome-types.ts index 125f1263..f689b410 100644 --- a/src/renderer/api/navidrome/navidrome-types.ts +++ b/src/shared/api/navidrome/navidrome-types.ts @@ -5,7 +5,7 @@ import { NDAlbumListSort, NDPlaylistListSort, NDSongListSort, -} from '/@/renderer/api/navidrome.types'; +} from '/@/shared/api/navidrome.types'; const sortOrderValues = ['ASC', 'DESC'] as const; diff --git a/src/renderer/api/subsonic.types.ts b/src/shared/api/subsonic.types.ts similarity index 100% rename from src/renderer/api/subsonic.types.ts rename to src/shared/api/subsonic.types.ts diff --git a/src/renderer/api/subsonic/subsonic-normalize.ts b/src/shared/api/subsonic/subsonic-normalize.ts similarity index 98% rename from src/renderer/api/subsonic/subsonic-normalize.ts rename to src/shared/api/subsonic/subsonic-normalize.ts index beb98643..a16061c6 100644 --- a/src/renderer/api/subsonic/subsonic-normalize.ts +++ b/src/shared/api/subsonic/subsonic-normalize.ts @@ -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; diff --git a/src/renderer/api/subsonic/subsonic-types.ts b/src/shared/api/subsonic/subsonic-types.ts similarity index 100% rename from src/renderer/api/subsonic/subsonic-types.ts rename to src/shared/api/subsonic/subsonic-types.ts diff --git a/src/shared/api/utils.ts b/src/shared/api/utils.ts new file mode 100644 index 00000000..308299ff --- /dev/null +++ b/src/shared/api/utils.ts @@ -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 = (itemSchema: ItemType) => { + return z.object({ + data: itemSchema, + headers: z.instanceof(AxiosHeaders), + }); +}; + +export const resultSubsonicBaseResponse = ( + 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]>; + +/** + * 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 => { + const cleanVersion = semverCoerce(version); + const features: Record = {}; + 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 = ' · '; diff --git a/src/renderer/api/types.ts b/src/shared/types/domain-types.ts similarity index 99% rename from src/renderer/api/types.ts rename to src/shared/types/domain-types.ts index 854721f2..cbe15d04 100644 --- a/src/renderer/api/types.ts +++ b/src/shared/types/domain-types.ts @@ -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', diff --git a/src/renderer/api/features-types.ts b/src/shared/types/features-types.ts similarity index 100% rename from src/renderer/api/features-types.ts rename to src/shared/types/features-types.ts diff --git a/src/renderer/types.ts b/src/shared/types/types.ts similarity index 96% rename from src/renderer/types.ts rename to src/shared/types/types.ts index dd0cd400..8515d9a4 100644 --- a/src/renderer/types.ts +++ b/src/shared/types/types.ts @@ -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', diff --git a/tsconfig.node.json b/tsconfig.node.json index e2771862..add56753 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -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/*" + ], + } } } diff --git a/tsconfig.web.json b/tsconfig.web.json index 9ba8703c..34e32ed4 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -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/*"