From 9d796108a566289d7762c134de906b06dd7186e3 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 15 Apr 2024 16:49:39 +0200 Subject: [PATCH 1/9] setup sources reordering --- package.json | 3 + pnpm-lock.yaml | 103 ++++++++++++++----- src/assets/locales/en.json | 2 + src/components/form/SortableList.tsx | 89 ++++++++++++++++ src/hooks/useProviderScrape.tsx | 5 + src/hooks/useSettingsState.ts | 16 ++- src/pages/Settings.tsx | 15 +++ src/pages/parts/settings/PreferencesPart.tsx | 17 +++ src/stores/preferences/index.tsx | 13 ++- 9 files changed, 235 insertions(+), 28 deletions(-) create mode 100644 src/components/form/SortableList.tsx diff --git a/package.json b/package.json index b9482526..ad6a855b 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,9 @@ ] }, "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", "@formkit/auto-animate": "^0.8.1", "@headlessui/react": "^1.7.17", "@ladjs/country-language": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54dd87b7..0bca4afe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,15 @@ overrides: rollup: npm:@rollup/wasm-node dependencies: + '@dnd-kit/core': + specifier: ^6.1.0 + version: 6.1.0(react-dom@18.2.0)(react@18.2.0) + '@dnd-kit/sortable': + specifier: ^8.0.0 + version: 8.0.0(@dnd-kit/core@6.1.0)(react@18.2.0) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@18.2.0) '@formkit/auto-animate': specifier: ^0.8.1 version: 0.8.1 @@ -274,7 +283,7 @@ devDependencies: version: 0.5.9(prettier@3.1.1) rollup-plugin-visualizer: specifier: ^5.11.0 - version: 5.11.0(@rollup/wasm-node@4.14.2) + version: 5.11.0(@rollup/wasm-node@4.14.3) tailwind-scrollbar: specifier: ^3.0.5 version: 3.0.5(tailwindcss@3.4.0) @@ -2688,6 +2697,49 @@ packages: to-fast-properties: 2.0.0 dev: true + /@dnd-kit/accessibility@3.1.0(react@18.2.0): + resolution: {integrity: sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + tslib: 2.6.2 + dev: false + + /@dnd-kit/core@6.1.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@dnd-kit/accessibility': 3.1.0(react@18.2.0) + '@dnd-kit/utilities': 3.2.2(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tslib: 2.6.2 + dev: false + + /@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0)(react@18.2.0): + resolution: {integrity: sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==} + peerDependencies: + '@dnd-kit/core': ^6.1.0 + react: '>=16.8.0' + dependencies: + '@dnd-kit/core': 6.1.0(react-dom@18.2.0)(react@18.2.0) + '@dnd-kit/utilities': 3.2.2(react@18.2.0) + react: 18.2.0 + tslib: 2.6.2 + dev: false + + /@dnd-kit/utilities@3.2.2(react@18.2.0): + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + tslib: 2.6.2 + dev: false + /@esbuild/aix-ppc64@0.19.10: resolution: {integrity: sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==} engines: {node: '>=12'} @@ -3175,7 +3227,7 @@ packages: engines: {node: '>=14.0.0'} dev: false - /@rollup/plugin-babel@5.3.1(@babel/core@7.24.3)(@rollup/wasm-node@4.14.2): + /@rollup/plugin-babel@5.3.1(@babel/core@7.24.3)(@rollup/wasm-node@4.14.3): resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} peerDependencies: @@ -3188,36 +3240,36 @@ packages: dependencies: '@babel/core': 7.24.3 '@babel/helper-module-imports': 7.24.3 - '@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.14.2) - rollup: /@rollup/wasm-node@4.14.2 + '@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.14.3) + rollup: /@rollup/wasm-node@4.14.3 dev: true - /@rollup/plugin-node-resolve@11.2.1(@rollup/wasm-node@4.14.2): + /@rollup/plugin-node-resolve@11.2.1(@rollup/wasm-node@4.14.3): resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==} engines: {node: '>= 10.0.0'} peerDependencies: rollup: npm:@rollup/wasm-node dependencies: - '@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.14.2) + '@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.14.3) '@types/resolve': 1.17.1 builtin-modules: 3.3.0 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.8 - rollup: /@rollup/wasm-node@4.14.2 + rollup: /@rollup/wasm-node@4.14.3 dev: true - /@rollup/plugin-replace@2.4.2(@rollup/wasm-node@4.14.2): + /@rollup/plugin-replace@2.4.2(@rollup/wasm-node@4.14.3): resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} peerDependencies: rollup: npm:@rollup/wasm-node dependencies: - '@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.14.2) + '@rollup/pluginutils': 3.1.0(@rollup/wasm-node@4.14.3) magic-string: 0.25.9 - rollup: /@rollup/wasm-node@4.14.2 + rollup: /@rollup/wasm-node@4.14.3 dev: true - /@rollup/pluginutils@3.1.0(@rollup/wasm-node@4.14.2): + /@rollup/pluginutils@3.1.0(@rollup/wasm-node@4.14.3): resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} engines: {node: '>= 8.0.0'} peerDependencies: @@ -3226,11 +3278,11 @@ packages: '@types/estree': 0.0.39 estree-walker: 1.0.1 picomatch: 2.3.1 - rollup: /@rollup/wasm-node@4.14.2 + rollup: /@rollup/wasm-node@4.14.3 dev: true - /@rollup/wasm-node@4.14.2: - resolution: {integrity: sha512-iwZbxtvP/0icwPWExUZWfA3A2jqQkDY38E8R5onRY2ALFmom0k7e37n9WDcJMMRcx/pdenfN8NaSohzX9LiDEQ==} + /@rollup/wasm-node@4.14.3: + resolution: {integrity: sha512-UyFUQV/iAu/Wt6rY6uQMYBQlfTMsynzYVIz6i7s9ySwjoG9WDNgtkK1TrazCSrUFbmuPZi2gbJm6VWdJCVw2yA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: @@ -6557,7 +6609,7 @@ packages: '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.6) '@babel/types': 7.23.6 kleur: 4.1.5 - rollup: /@rollup/wasm-node@4.14.2 + rollup: /@rollup/wasm-node@4.14.3 unplugin: 1.5.1 transitivePeerDependencies: - supports-color @@ -7529,7 +7581,7 @@ packages: glob: 7.2.3 dev: true - /rollup-plugin-terser@7.0.2(@rollup/wasm-node@4.14.2): + /rollup-plugin-terser@7.0.2(@rollup/wasm-node@4.14.3): resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser peerDependencies: @@ -7537,12 +7589,12 @@ packages: dependencies: '@babel/code-frame': 7.24.2 jest-worker: 26.6.2 - rollup: /@rollup/wasm-node@4.14.2 + rollup: /@rollup/wasm-node@4.14.3 serialize-javascript: 4.0.0 terser: 5.30.0 dev: true - /rollup-plugin-visualizer@5.11.0(@rollup/wasm-node@4.14.2): + /rollup-plugin-visualizer@5.11.0(@rollup/wasm-node@4.14.3): resolution: {integrity: sha512-exM0Ms2SN3AgTzMeW7y46neZQcyLY7eKwWAop1ZoRTCZwyrIRdMMJ6JjToAJbML77X/9N8ZEpmXG4Z/Clb9k8g==} engines: {node: '>=14'} hasBin: true @@ -7554,7 +7606,7 @@ packages: dependencies: open: 8.4.2 picomatch: 2.3.1 - rollup: /@rollup/wasm-node@4.14.2 + rollup: /@rollup/wasm-node@4.14.3 source-map: 0.7.4 yargs: 17.7.2 dev: true @@ -8692,7 +8744,7 @@ packages: '@types/node': 20.10.5 esbuild: 0.19.10 postcss: 8.4.32 - rollup: /@rollup/wasm-node@4.14.2 + rollup: /@rollup/wasm-node@4.14.3 optionalDependencies: fsevents: 2.3.3 dev: true @@ -8959,9 +9011,9 @@ packages: '@babel/core': 7.24.3 '@babel/preset-env': 7.24.3(@babel/core@7.24.3) '@babel/runtime': 7.24.1 - '@rollup/plugin-babel': 5.3.1(@babel/core@7.24.3)(@rollup/wasm-node@4.14.2) - '@rollup/plugin-node-resolve': 11.2.1(@rollup/wasm-node@4.14.2) - '@rollup/plugin-replace': 2.4.2(@rollup/wasm-node@4.14.2) + '@rollup/plugin-babel': 5.3.1(@babel/core@7.24.3)(@rollup/wasm-node@4.14.3) + '@rollup/plugin-node-resolve': 11.2.1(@rollup/wasm-node@4.14.3) + '@rollup/plugin-replace': 2.4.2(@rollup/wasm-node@4.14.3) '@surma/rollup-plugin-off-main-thread': 2.2.3 ajv: 8.12.0 common-tags: 1.8.2 @@ -8970,8 +9022,8 @@ packages: glob: 7.2.3 lodash: 4.17.21 pretty-bytes: 5.6.0 - rollup: /@rollup/wasm-node@4.14.2 - rollup-plugin-terser: 7.0.2(@rollup/wasm-node@4.14.2) + rollup: /@rollup/wasm-node@4.14.3 + rollup-plugin-terser: 7.0.2(@rollup/wasm-node@4.14.3) source-map: 0.8.0-beta.0 stringify-object: 3.3.0 strip-comments: 2.0.1 @@ -9016,6 +9068,7 @@ packages: /workbox-google-analytics@7.0.0: resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} + deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained dependencies: workbox-background-sync: 7.0.0 workbox-core: 7.0.0 diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 6b951af6..acab2c56 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -527,6 +527,8 @@ "autoplay": "Autoplay", "autoplayDescription": "Automatically play the next episode in a series after reaching the end. Can be enabled by users with the browser extension, a custom proxy, or with the default setup if allowed by the host.", "autoplayLabel": "Autoplay", + "sourceOrder": "Reordering sources", + "sourceOrderDescription": "Drag and drop to reorder sources. This will determine the order in which sources are checked for the media you are trying to watch.", "title": "Preferences" }, "reset": "Reset", diff --git a/src/components/form/SortableList.tsx b/src/components/form/SortableList.tsx new file mode 100644 index 00000000..90c8fd94 --- /dev/null +++ b/src/components/form/SortableList.tsx @@ -0,0 +1,89 @@ +import { + DndContext, + DragEndEvent, + KeyboardSensor, + PointerSensor, + closestCenter, + useSensor, + useSensors, +} from "@dnd-kit/core"; +import { + SortableContext, + arrayMove, + sortableKeyboardCoordinates, + useSortable, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import classNames from "classnames"; + +import { Icon, Icons } from "../Icon"; + +function SortableItem(props: { id: string }) { + const { attributes, listeners, setNodeRef, transform, transition } = + useSortable({ id: props.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + + return ( +
+ {props.id} + +
+ ); +} + +export function DraggableList(props: { + items: string[]; + setItems: (items: string[]) => void; +}) { + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + if (!over) return; + if (active.id !== over.id) { + const currentItems = props.items; + const oldIndex = currentItems.indexOf(active.id as string); + const newIndex = currentItems.indexOf(over.id as string); + const newItems = arrayMove(currentItems, oldIndex, newIndex); + props.setItems(newItems); + } + }; + + return ( + + +
+ {props.items.map((id) => ( + + ))} +
+
+
+ ); +} diff --git a/src/hooks/useProviderScrape.tsx b/src/hooks/useProviderScrape.tsx index 932c8449..e83973e2 100644 --- a/src/hooks/useProviderScrape.tsx +++ b/src/hooks/useProviderScrape.tsx @@ -14,6 +14,7 @@ import { } from "@/backend/helpers/providerApi"; import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers"; import { getProviders } from "@/backend/providers/providers"; +import { usePreferencesStore } from "@/stores/preferences"; export interface ScrapingItems { id: string; @@ -156,6 +157,8 @@ export function useScrape() { startScrape, } = useBaseScrape(); + const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder); + const startScraping = useCallback( async (media: ScrapeMedia) => { const providerApiUrl = getLoadbalancedProviderApiUrl(); @@ -181,6 +184,7 @@ export function useScrape() { const providers = getProviders(); const output = await providers.runAll({ media, + sourceOrder: preferredSourceOrder, events: { init: initEvent, start: startEvent, @@ -199,6 +203,7 @@ export function useScrape() { discoverEmbedsEvent, getResult, startScrape, + preferredSourceOrder, ], ); diff --git a/src/hooks/useSettingsState.ts b/src/hooks/useSettingsState.ts index eb8cd73f..667093fb 100644 --- a/src/hooks/useSettingsState.ts +++ b/src/hooks/useSettingsState.ts @@ -52,6 +52,7 @@ export function useSettingsState( | undefined, enableThumbnails: boolean, enableAutoplay: boolean, + sourceOrder: string[], ) { const [proxyUrlsState, setProxyUrls, resetProxyUrls, proxyUrlsChanged] = useDerived(proxyUrls); @@ -91,6 +92,12 @@ export function useSettingsState( resetEnableAutoplay, enableAutoplayChanged, ] = useDerived(enableAutoplay); + const [ + sourceOrderState, + setSourceOrderState, + resetSourceOrder, + sourceOrderChanged, + ] = useDerived(sourceOrder); function reset() { resetTheme(); @@ -103,6 +110,7 @@ export function useSettingsState( resetProfile(); resetEnableThumbnails(); resetEnableAutoplay(); + resetSourceOrder(); } const changed = @@ -114,7 +122,8 @@ export function useSettingsState( proxyUrlsChanged || profileChanged || enableThumbnailsChanged || - enableAutoplayChanged; + enableAutoplayChanged || + sourceOrderChanged; return { reset, @@ -164,5 +173,10 @@ export function useSettingsState( set: setEnableAutoplayState, changed: enableAutoplayChanged, }, + sourceOrder: { + state: sourceOrderState, + set: setSourceOrderState, + changed: sourceOrderChanged, + }, }; } diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 997fa3ed..720d55ea 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -11,6 +11,7 @@ import { import { getSessions, updateSession } from "@/backend/accounts/sessions"; import { updateSettings } from "@/backend/accounts/settings"; import { editUser } from "@/backend/accounts/user"; +import { getProviders } from "@/backend/providers/providers"; import { Button } from "@/components/buttons/Button"; import { WideContainer } from "@/components/layout/WideContainer"; import { UserIcons } from "@/components/UserIcon"; @@ -125,6 +126,9 @@ export function SettingsPage() { const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay); const setEnableAutoplay = usePreferencesStore((s) => s.setEnableAutoplay); + const sourceOrder = usePreferencesStore((s) => s.sourceOrder); + const setSourceOrder = usePreferencesStore((s) => s.setSourceOrder); + const account = useAuthStore((s) => s.account); const updateProfile = useAuthStore((s) => s.setAccountProfile); const updateDeviceName = useAuthStore((s) => s.updateDeviceName); @@ -148,6 +152,7 @@ export function SettingsPage() { account?.profile, enableThumbnails, enableAutoplay, + sourceOrder, ); useEffect(() => { @@ -201,6 +206,7 @@ export function SettingsPage() { setEnableThumbnails(state.enableThumbnails.state); setEnableAutoplay(state.enableAutoplay.state); + setSourceOrder(state.sourceOrder.state); setAppLanguage(state.appLanguage.state); setTheme(state.theme.state); setSubStyling(state.subtitleStyling.state); @@ -227,6 +233,7 @@ export function SettingsPage() { setEnableThumbnails, state, setEnableAutoplay, + setSourceOrder, setAppLanguage, setTheme, setSubStyling, @@ -274,6 +281,14 @@ export function SettingsPage() { setEnableThumbnails={state.enableThumbnails.set} enableAutoplay={state.enableAutoplay.state} setEnableAutoplay={state.enableAutoplay.set} + sourceOrder={ + state.sourceOrder.state.length > 0 + ? state.sourceOrder.state + : getProviders() + .listSources() + .map((s) => s.id) + } + setSourceOrder={state.sourceOrder.set} />
diff --git a/src/pages/parts/settings/PreferencesPart.tsx b/src/pages/parts/settings/PreferencesPart.tsx index 71f9c5f8..2dab46b9 100644 --- a/src/pages/parts/settings/PreferencesPart.tsx +++ b/src/pages/parts/settings/PreferencesPart.tsx @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"; import { Toggle } from "@/components/buttons/Toggle"; import { FlagIcon } from "@/components/FlagIcon"; import { Dropdown } from "@/components/form/Dropdown"; +import { DraggableList } from "@/components/form/SortableList"; import { Heading1 } from "@/components/utils/Text"; import { appLanguageOptions } from "@/setup/i18n"; import { isAutoplayAllowed } from "@/utils/autoplay"; @@ -16,6 +17,8 @@ export function PreferencesPart(props: { setEnableThumbnails: (v: boolean) => void; enableAutoplay: boolean; setEnableAutoplay: (v: boolean) => void; + sourceOrder: string[]; + setSourceOrder: (v: string[]) => void; }) { const { t } = useTranslation(); const sorted = sortLangCodes(appLanguageOptions.map((item) => item.code)); @@ -94,6 +97,20 @@ export function PreferencesPart(props: {

+ +
+

+ {t("settings.preferences.sourceOrder")} +

+

+ {t("settings.preferences.sourceOrderDescription")} +

+ + +
); } diff --git a/src/stores/preferences/index.tsx b/src/stores/preferences/index.tsx index ce198388..cc421573 100644 --- a/src/stores/preferences/index.tsx +++ b/src/stores/preferences/index.tsx @@ -4,26 +4,35 @@ import { immer } from "zustand/middleware/immer"; export interface PreferencesStore { enableThumbnails: boolean; - setEnableThumbnails(v: boolean): void; enableAutoplay: boolean; + sourceOrder: string[]; + + setEnableThumbnails(v: boolean): void; setEnableAutoplay(v: boolean): void; + setSourceOrder(v: string[]): void; } export const usePreferencesStore = create( persist( immer((set) => ({ enableThumbnails: false, + enableAutoplay: false, + sourceOrder: [], setEnableThumbnails(v) { set((s) => { s.enableThumbnails = v; }); }, - enableAutoplay: false, setEnableAutoplay(v) { set((s) => { s.enableAutoplay = v; }); }, + setSourceOrder(v) { + set((s) => { + s.sourceOrder = v; + }); + }, })), { name: "__MW::preferences", From df89bda66a28d1063f24874726bd7d7c52b4f82e Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 15 Apr 2024 16:52:50 +0200 Subject: [PATCH 2/9] rename --- src/components/form/SortableList.tsx | 2 +- src/pages/parts/settings/PreferencesPart.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/form/SortableList.tsx b/src/components/form/SortableList.tsx index 90c8fd94..27975bd1 100644 --- a/src/components/form/SortableList.tsx +++ b/src/components/form/SortableList.tsx @@ -45,7 +45,7 @@ function SortableItem(props: { id: string }) { ); } -export function DraggableList(props: { +export function SortableList(props: { items: string[]; setItems: (items: string[]) => void; }) { diff --git a/src/pages/parts/settings/PreferencesPart.tsx b/src/pages/parts/settings/PreferencesPart.tsx index 2dab46b9..1afef469 100644 --- a/src/pages/parts/settings/PreferencesPart.tsx +++ b/src/pages/parts/settings/PreferencesPart.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import { Toggle } from "@/components/buttons/Toggle"; import { FlagIcon } from "@/components/FlagIcon"; import { Dropdown } from "@/components/form/Dropdown"; -import { DraggableList } from "@/components/form/SortableList"; +import { SortableList } from "@/components/form/SortableList"; import { Heading1 } from "@/components/utils/Text"; import { appLanguageOptions } from "@/setup/i18n"; import { isAutoplayAllowed } from "@/utils/autoplay"; @@ -106,7 +106,7 @@ export function PreferencesPart(props: { {t("settings.preferences.sourceOrderDescription")}

- From 42d7be106e0a35e7a576d84ec8b5ba575a1b17f9 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 15 Apr 2024 17:15:15 +0200 Subject: [PATCH 3/9] use name of source instead --- src/components/form/SortableList.tsx | 23 ++++++++++++-------- src/pages/parts/settings/PreferencesPart.tsx | 14 ++++++++++-- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/components/form/SortableList.tsx b/src/components/form/SortableList.tsx index 27975bd1..bdbb0fdd 100644 --- a/src/components/form/SortableList.tsx +++ b/src/components/form/SortableList.tsx @@ -19,9 +19,14 @@ import classNames from "classnames"; import { Icon, Icons } from "../Icon"; -function SortableItem(props: { id: string }) { +export interface Item { + id: string; + name: string; +} + +function SortableItem(props: { item: Item }) { const { attributes, listeners, setNodeRef, transform, transition } = - useSortable({ id: props.id }); + useSortable({ id: props.item.id }); const style = { transform: CSS.Transform.toString(transform), @@ -39,15 +44,15 @@ function SortableItem(props: { id: string }) { transform && "cursor-grabbing", )} > - {props.id} + {props.item.name} ); } export function SortableList(props: { - items: string[]; - setItems: (items: string[]) => void; + items: Item[]; + setItems: (items: Item[]) => void; }) { const sensors = useSensors( useSensor(PointerSensor), @@ -61,8 +66,8 @@ export function SortableList(props: { if (!over) return; if (active.id !== over.id) { const currentItems = props.items; - const oldIndex = currentItems.indexOf(active.id as string); - const newIndex = currentItems.indexOf(over.id as string); + const oldIndex = currentItems.findIndex((item) => item.id === active.id); + const newIndex = currentItems.findIndex((item) => item.id === over.id); const newItems = arrayMove(currentItems, oldIndex, newIndex); props.setItems(newItems); } @@ -79,8 +84,8 @@ export function SortableList(props: { strategy={verticalListSortingStrategy} >
- {props.items.map((id) => ( - + {props.items.map((item) => ( + ))}
diff --git a/src/pages/parts/settings/PreferencesPart.tsx b/src/pages/parts/settings/PreferencesPart.tsx index 1afef469..de943325 100644 --- a/src/pages/parts/settings/PreferencesPart.tsx +++ b/src/pages/parts/settings/PreferencesPart.tsx @@ -1,6 +1,8 @@ import classNames from "classnames"; import { useTranslation } from "react-i18next"; +import { getCachedMetadata } from "@/backend/helpers/providerApi"; +import { getProviders } from "@/backend/providers/providers"; import { Toggle } from "@/components/buttons/Toggle"; import { FlagIcon } from "@/components/FlagIcon"; import { Dropdown } from "@/components/form/Dropdown"; @@ -107,8 +109,16 @@ export function PreferencesPart(props: {

({ + id, + name: + getProviders() + .listSources() + .find((s) => s.id === id)?.name || id, + }))} + setItems={(items) => + props.setSourceOrder(items.map((item) => item.id)) + } /> From 427646b444e4d8242fd66833585370938d2fa819 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 15 Apr 2024 17:29:30 +0200 Subject: [PATCH 4/9] fix dragging not working on mobile devices --- src/components/form/SortableList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/form/SortableList.tsx b/src/components/form/SortableList.tsx index bdbb0fdd..b139b8e4 100644 --- a/src/components/form/SortableList.tsx +++ b/src/components/form/SortableList.tsx @@ -40,7 +40,7 @@ function SortableItem(props: { item: Item }) { {...attributes} {...listeners} className={classNames( - "bg-dropdown-background hover:bg-dropdown-hoverBackground select-none cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg", + "bg-dropdown-background hover:bg-dropdown-hoverBackground select-none cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg touch-none", transform && "cursor-grabbing", )} > From d607a7cce1345c195f22b1508ba2e350caf05637 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 15 Apr 2024 18:20:12 +0200 Subject: [PATCH 5/9] remove unused import --- src/pages/parts/settings/PreferencesPart.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/parts/settings/PreferencesPart.tsx b/src/pages/parts/settings/PreferencesPart.tsx index de943325..4057f47c 100644 --- a/src/pages/parts/settings/PreferencesPart.tsx +++ b/src/pages/parts/settings/PreferencesPart.tsx @@ -1,7 +1,6 @@ import classNames from "classnames"; import { useTranslation } from "react-i18next"; -import { getCachedMetadata } from "@/backend/helpers/providerApi"; import { getProviders } from "@/backend/providers/providers"; import { Toggle } from "@/components/buttons/Toggle"; import { FlagIcon } from "@/components/FlagIcon"; From b671f228634bf8ab13170425aef892ab62b8b4b8 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 15 Apr 2024 22:46:38 +0200 Subject: [PATCH 6/9] grey out sources that are not available on your current device --- src/assets/locales/en.json | 2 +- src/backend/providers/providers.ts | 8 ++++++ src/components/form/SortableList.tsx | 7 +++-- src/pages/Settings.tsx | 26 +++++++++++------ src/pages/parts/settings/PreferencesPart.tsx | 30 ++++++++++++++------ 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index acab2c56..b5976dd5 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -528,7 +528,7 @@ "autoplayDescription": "Automatically play the next episode in a series after reaching the end. Can be enabled by users with the browser extension, a custom proxy, or with the default setup if allowed by the host.", "autoplayLabel": "Autoplay", "sourceOrder": "Reordering sources", - "sourceOrderDescription": "Drag and drop to reorder sources. This will determine the order in which sources are checked for the media you are trying to watch.", + "sourceOrderDescription": "Drag and drop to reorder sources. This will determine the order in which sources are checked for the media you are trying to watch. If a source is greyed out, it means it is not available on your device.", "title": "Preferences" }, "reset": "Reset", diff --git a/src/backend/providers/providers.ts b/src/backend/providers/providers.ts index ac4a7dfa..fba9b4d5 100644 --- a/src/backend/providers/providers.ts +++ b/src/backend/providers/providers.ts @@ -25,3 +25,11 @@ export function getProviders() { target: targets.BROWSER, }); } + +export function getAllProviders() { + return makeProviders({ + fetcher: makeStandardFetcher(fetch), + target: targets.BROWSER_EXTENSION, + consistentIpForRequests: true, + }); +} diff --git a/src/components/form/SortableList.tsx b/src/components/form/SortableList.tsx index b139b8e4..43da96fa 100644 --- a/src/components/form/SortableList.tsx +++ b/src/components/form/SortableList.tsx @@ -22,6 +22,7 @@ import { Icon, Icons } from "../Icon"; export interface Item { id: string; name: string; + disabled?: boolean; } function SortableItem(props: { item: Item }) { @@ -40,11 +41,13 @@ function SortableItem(props: { item: Item }) { {...attributes} {...listeners} className={classNames( - "bg-dropdown-background hover:bg-dropdown-hoverBackground select-none cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg touch-none", - transform && "cursor-grabbing", + "bg-dropdown-background hover:bg-dropdown-hoverBackground select-none space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg touch-none", + props.item.disabled && "opacity-50", + transform ? "cursor-grabbing" : "cursor-grab", )} > {props.item.name} + {props.item.disabled && } ); diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 720d55ea..03aea1db 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -11,7 +11,7 @@ import { import { getSessions, updateSession } from "@/backend/accounts/sessions"; import { updateSettings } from "@/backend/accounts/settings"; import { editUser } from "@/backend/accounts/user"; -import { getProviders } from "@/backend/providers/providers"; +import { getAllProviders, getProviders } from "@/backend/providers/providers"; import { Button } from "@/components/buttons/Button"; import { WideContainer } from "@/components/layout/WideContainer"; import { UserIcons } from "@/components/UserIcon"; @@ -155,6 +155,22 @@ export function SettingsPage() { sourceOrder, ); + const availableSources = useMemo(() => { + const sources = getAllProviders().listSources(); + const sourceIDs = sources.map((s) => s.id); + const stateSources = state.sourceOrder.state; + + // Filter out sources that are not in `stateSources` and are in `sources` + const updatedSources = stateSources.filter((ss) => sourceIDs.includes(ss)); + + // Add sources from `sources` that are not in `stateSources` + const missingSources = sources + .filter((s) => !stateSources.includes(s.id)) + .map((s) => s.id); + + return [...updatedSources, ...missingSources]; + }, [state.sourceOrder.state]); + useEffect(() => { setPreviewTheme(activeTheme ?? "default"); }, [setPreviewTheme, activeTheme]); @@ -281,13 +297,7 @@ export function SettingsPage() { setEnableThumbnails={state.enableThumbnails.set} enableAutoplay={state.enableAutoplay.state} setEnableAutoplay={state.enableAutoplay.set} - sourceOrder={ - state.sourceOrder.state.length > 0 - ? state.sourceOrder.state - : getProviders() - .listSources() - .map((s) => s.id) - } + sourceOrder={availableSources} setSourceOrder={state.sourceOrder.set} /> diff --git a/src/pages/parts/settings/PreferencesPart.tsx b/src/pages/parts/settings/PreferencesPart.tsx index 4057f47c..d127655f 100644 --- a/src/pages/parts/settings/PreferencesPart.tsx +++ b/src/pages/parts/settings/PreferencesPart.tsx @@ -1,7 +1,9 @@ import classNames from "classnames"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { getProviders } from "@/backend/providers/providers"; +import { getAllProviders, getProviders } from "@/backend/providers/providers"; +import { Button } from "@/components/buttons/Button"; import { Toggle } from "@/components/buttons/Toggle"; import { FlagIcon } from "@/components/FlagIcon"; import { Dropdown } from "@/components/form/Dropdown"; @@ -38,6 +40,17 @@ export function PreferencesPart(props: { (item) => item.id === getLocaleInfo(props.language)?.code, ); + const allSources = getAllProviders().listSources(); + + const sourceItems = useMemo(() => { + const currentDeviceSources = getProviders().listSources(); + return props.sourceOrder.map((id) => ({ + id, + name: allSources.find((s) => s.id === id)?.name || id, + disabled: !currentDeviceSources.find((s) => s.id === id), + })); + }, [props.sourceOrder, allSources]); + return (
{t("settings.preferences.title")} @@ -108,17 +121,18 @@ export function PreferencesPart(props: {

({ - id, - name: - getProviders() - .listSources() - .find((s) => s.id === id)?.name || id, - }))} + items={sourceItems} setItems={(items) => props.setSourceOrder(items.map((item) => item.id)) } /> +
); From f248a5ff61b1135583af2b8278e611c2e20d3cb7 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 15 Apr 2024 22:51:49 +0200 Subject: [PATCH 7/9] remove unused import --- src/pages/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 03aea1db..a0818112 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -11,7 +11,7 @@ import { import { getSessions, updateSession } from "@/backend/accounts/sessions"; import { updateSettings } from "@/backend/accounts/settings"; import { editUser } from "@/backend/accounts/user"; -import { getAllProviders, getProviders } from "@/backend/providers/providers"; +import { getAllProviders } from "@/backend/providers/providers"; import { Button } from "@/components/buttons/Button"; import { WideContainer } from "@/components/layout/WideContainer"; import { UserIcons } from "@/components/UserIcon"; From e2f1f8274b2212bffad7099c72a20a073333409e Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 15 Apr 2024 23:17:21 +0200 Subject: [PATCH 8/9] add global extension check --- src/index.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/index.tsx b/src/index.tsx index d5fffaae..29d76566 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -31,6 +31,7 @@ import { SettingsSyncer } from "@/stores/subtitles/SettingsSyncer"; import { ThemeProvider } from "@/stores/theme"; import { TurnstileProvider } from "@/stores/turnstile"; +import { extensionInfo } from "./backend/extension/messaging"; import { initializeChromecast } from "./setup/chromecast"; import { initializeOldStores } from "./stores/__old/migrations"; @@ -140,6 +141,15 @@ function TheRouter(props: { children: ReactNode }) { return {props.children}; } +// Checks if the extension is installed +function ExtensionInfoLoader() { + useAsync(async () => { + await extensionInfo(); + }, []); + + return null; +} + const container = document.getElementById("root"); const root = createRoot(container!); @@ -149,6 +159,7 @@ root.render( }> + From 07b07fc3547f1897b667c7ab7f552d0a18349d39 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Thu, 18 Apr 2024 20:31:53 +0200 Subject: [PATCH 9/9] throw promise to trigger suspense --- src/index.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 29d76566..b6e76888 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -31,7 +31,10 @@ import { SettingsSyncer } from "@/stores/subtitles/SettingsSyncer"; import { ThemeProvider } from "@/stores/theme"; import { TurnstileProvider } from "@/stores/turnstile"; -import { extensionInfo } from "./backend/extension/messaging"; +import { + extensionInfo, + isExtensionActiveCached, +} from "./backend/extension/messaging"; import { initializeChromecast } from "./setup/chromecast"; import { initializeOldStores } from "./stores/__old/migrations"; @@ -142,10 +145,10 @@ function TheRouter(props: { children: ReactNode }) { } // Checks if the extension is installed -function ExtensionInfoLoader() { - useAsync(async () => { - await extensionInfo(); - }, []); +function ExtensionStatus() { + if (!isExtensionActiveCached()) { + throw extensionInfo(); + } return null; } @@ -159,7 +162,7 @@ root.render( }> - +