mirror of
https://github.com/movie-web/movie-web.git
synced 2025-09-13 18:13:24 +00:00
Compare commits
9 Commits
feature/so
...
upgrade
Author | SHA1 | Date | |
---|---|---|---|
|
91757c0cda | ||
|
6acb51e8a3 | ||
|
fe0d2c91a4 | ||
|
e1b9393584 | ||
|
2d9bc0149f | ||
|
9bd5f30f40 | ||
|
0a15bb2023 | ||
|
cfa3cfd072 | ||
|
5fbe5d1ff5 |
@@ -2,7 +2,7 @@ FROM node:20-alpine as build
|
||||
WORKDIR /app
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
RUN npm i -g pnpm@8
|
||||
|
||||
COPY package.json ./
|
||||
COPY pnpm-lock.yaml ./
|
||||
|
115
package.json
115
package.json
@@ -3,6 +3,10 @@
|
||||
"version": "4.7.0",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/movie-web/movie-web",
|
||||
"engines": {
|
||||
"node": ">= 20",
|
||||
"pnpm": "^8"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
@@ -26,109 +30,106 @@
|
||||
]
|
||||
},
|
||||
"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",
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@headlessui/react": "^1.7.19",
|
||||
"@ladjs/country-language": "^1.0.3",
|
||||
"@movie-web/providers": "^2.3.0",
|
||||
"@noble/hashes": "^1.3.3",
|
||||
"@plasmohq/messaging": "^0.6.1",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@plasmohq/messaging": "^0.6.2",
|
||||
"@react-spring/web": "^9.7.3",
|
||||
"@scure/bip39": "^1.2.2",
|
||||
"@scure/bip39": "^1.3.0",
|
||||
"@sozialhelden/ietf-language-tags": "^5.4.2",
|
||||
"@types/node-forge": "^1.3.10",
|
||||
"classnames": "^2.3.2",
|
||||
"core-js": "^3.34.0",
|
||||
"@types/node-forge": "^1.3.11",
|
||||
"classnames": "^2.5.1",
|
||||
"core-js": "^3.37.0",
|
||||
"detect-browser": "^5.3.0",
|
||||
"dompurify": "^3.0.6",
|
||||
"flag-icons": "^7.1.0",
|
||||
"dompurify": "^3.1.0",
|
||||
"flag-icons": "^7.2.1",
|
||||
"focus-trap-react": "^10.2.3",
|
||||
"fscreen": "^1.2.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"hls.js": "^1.5.7",
|
||||
"i18next": "^23.7.11",
|
||||
"immer": "^10.0.3",
|
||||
"hls.js": "^1.5.8",
|
||||
"i18next": "^23.11.2",
|
||||
"immer": "^10.0.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"million": "^2.6.4",
|
||||
"nanoid": "^5.0.4",
|
||||
"million": "^3.0.6",
|
||||
"nanoid": "^5.0.7",
|
||||
"node-forge": "^1.3.1",
|
||||
"ofetch": "^1.3.3",
|
||||
"ofetch": "^1.3.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-ga4": "^2.1.0",
|
||||
"react-google-recaptcha-v3": "^1.10.1",
|
||||
"react-helmet-async": "^2.0.4",
|
||||
"react-i18next": "^14.0.0",
|
||||
"react-i18next": "^14.1.0",
|
||||
"react-lazy-with-preload": "^2.2.1",
|
||||
"react-router-dom": "^6.21.1",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-sticky-el": "^2.1.0",
|
||||
"react-turnstile": "^1.1.2",
|
||||
"react-use": "^17.4.2",
|
||||
"semver": "^7.5.4",
|
||||
"react-turnstile": "^1.1.3",
|
||||
"react-use": "^17.5.0",
|
||||
"semver": "^7.6.0",
|
||||
"slugify": "^1.6.6",
|
||||
"subsrt-ts": "^2.1.2",
|
||||
"zustand": "^4.4.7"
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.6",
|
||||
"@babel/preset-env": "^7.23.6",
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@rollup/wasm-node": "^4.9.4",
|
||||
"@types/chromecast-caf-sender": "^1.0.8",
|
||||
"@types/crypto-js": "^4.2.1",
|
||||
"@babel/core": "^7.24.4",
|
||||
"@babel/preset-env": "^7.24.4",
|
||||
"@babel/preset-typescript": "^7.24.1",
|
||||
"@rollup/wasm-node": "^4.15.0",
|
||||
"@types/chromecast-caf-sender": "^1.0.9",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/fscreen": "^1.0.4",
|
||||
"@types/lodash.isequal": "^4.5.8",
|
||||
"@types/lodash.merge": "^4.6.9",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
"@types/node": "^20.10.5",
|
||||
"@types/node": "^20.12.7",
|
||||
"@types/pako": "^2.0.3",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-stickynode": "^4.0.3",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"@types/semver": "^7.5.6",
|
||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||
"@typescript-eslint/parser": "^6.15.0",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
||||
"@typescript-eslint/parser": "^7.7.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "19.0.4",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-prettier": "^5.1.1",
|
||||
"eslint-plugin-react": "7.33.2",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-react": "7.34.1",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"glob": "^10.3.10",
|
||||
"glob": "^10.3.12",
|
||||
"handlebars": "^4.7.8",
|
||||
"jsdom": "^23.0.1",
|
||||
"postcss": "^8.4.32",
|
||||
"jsdom": "^24.0.0",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-rtl": "^2.0.0",
|
||||
"postcss-rtlcss": "^4.0.9",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-tailwindcss": "^0.5.9",
|
||||
"rollup-plugin-visualizer": "^5.11.0",
|
||||
"tailwind-scrollbar": "^3.0.5",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"postcss-rtlcss": "^5.1.2",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"tailwind-scrollbar": "^3.1.0",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tailwindcss-themer": "^4.0.0",
|
||||
"type-fest": "^4.8.3",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.13",
|
||||
"vite-plugin-checker": "^0.6.2",
|
||||
"type-fest": "^4.15.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.9",
|
||||
"vite-plugin-checker": "^0.6.4",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-plugin-pwa": "^0.17.4",
|
||||
"vite-plugin-static-copy": "^1.0.0",
|
||||
"vitest": "^1.1.0",
|
||||
"vite-plugin-pwa": "^0.19.8",
|
||||
"vite-plugin-static-copy": "^1.0.3",
|
||||
"vitest": "^1.5.0",
|
||||
"workbox-window": "^7.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
|
3590
pnpm-lock.yaml
generated
3590
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -527,8 +527,6 @@
|
||||
"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. If a source is greyed out, it means it is not available on your device.",
|
||||
"title": "Preferences"
|
||||
},
|
||||
"reset": "Reset",
|
||||
|
@@ -135,13 +135,15 @@ export async function getLegacyMetaFromId(
|
||||
throw err;
|
||||
}
|
||||
|
||||
let imdbId = data.external_ids.find((v) => v.provider === "imdb_latest")
|
||||
?.external_id;
|
||||
let imdbId = data.external_ids.find(
|
||||
(v) => v.provider === "imdb_latest",
|
||||
)?.external_id;
|
||||
if (!imdbId)
|
||||
imdbId = data.external_ids.find((v) => v.provider === "imdb")?.external_id;
|
||||
|
||||
let tmdbId = data.external_ids.find((v) => v.provider === "tmdb_latest")
|
||||
?.external_id;
|
||||
let tmdbId = data.external_ids.find(
|
||||
(v) => v.provider === "tmdb_latest",
|
||||
)?.external_id;
|
||||
if (!tmdbId)
|
||||
tmdbId = data.external_ids.find((v) => v.provider === "tmdb")?.external_id;
|
||||
|
||||
|
@@ -25,11 +25,3 @@ export function getProviders() {
|
||||
target: targets.BROWSER,
|
||||
});
|
||||
}
|
||||
|
||||
export function getAllProviders() {
|
||||
return makeProviders({
|
||||
fetcher: makeStandardFetcher(fetch),
|
||||
target: targets.BROWSER_EXTENSION,
|
||||
consistentIpForRequests: true,
|
||||
});
|
||||
}
|
||||
|
@@ -1,97 +0,0 @@
|
||||
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";
|
||||
|
||||
export interface Item {
|
||||
id: string;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function SortableItem(props: { item: Item }) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id: props.item.id });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={classNames(
|
||||
"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",
|
||||
)}
|
||||
>
|
||||
<span className="flex-1 text-white font-bold">{props.item.name}</span>
|
||||
{props.item.disabled && <Icon icon={Icons.WARNING} />}
|
||||
<Icon icon={Icons.MENU} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SortableList(props: {
|
||||
items: Item[];
|
||||
setItems: (items: Item[]) => 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.findIndex((item) => item.id === active.id);
|
||||
const newIndex = currentItems.findIndex((item) => item.id === over.id);
|
||||
const newItems = arrayMove(currentItems, oldIndex, newIndex);
|
||||
props.setItems(newItems);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={props.items}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
{props.items.map((item) => (
|
||||
<SortableItem key={item.id} item={item} />
|
||||
))}
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
}
|
@@ -46,10 +46,14 @@ function Button(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function useSeasons(mediaId: string, isLastEpisode: boolean = false) {
|
||||
function useSeasons(
|
||||
mediaId: string | undefined,
|
||||
isLastEpisode: boolean = false,
|
||||
) {
|
||||
const state = useAsync(async () => {
|
||||
if (isLastEpisode) {
|
||||
const data = await getMetaFromId(MWMediaType.SERIES, mediaId ?? "");
|
||||
if (!mediaId) return null;
|
||||
const data = await getMetaFromId(MWMediaType.SERIES, mediaId);
|
||||
if (data?.meta.type !== MWMediaType.SERIES) return null;
|
||||
return data.meta.seasons;
|
||||
}
|
||||
@@ -60,13 +64,14 @@ function useSeasons(mediaId: string, isLastEpisode: boolean = false) {
|
||||
|
||||
function useNextSeasonEpisode(
|
||||
nextSeason: MWSeasonMeta | undefined,
|
||||
mediaId: string,
|
||||
mediaId: string | undefined,
|
||||
) {
|
||||
const state = useAsync(async () => {
|
||||
if (nextSeason) {
|
||||
if (!mediaId) return null;
|
||||
const data = await getMetaFromId(
|
||||
MWMediaType.SERIES,
|
||||
mediaId ?? "",
|
||||
mediaId,
|
||||
nextSeason?.id,
|
||||
);
|
||||
if (data?.meta.type !== MWMediaType.SERIES) return null;
|
||||
@@ -106,18 +111,17 @@ export function NextEpisodeButton(props: {
|
||||
const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay);
|
||||
|
||||
const isLastEpisode =
|
||||
meta?.episode?.number === meta?.episodes?.at(-1)?.number;
|
||||
!meta?.episode?.number || !meta?.episodes?.at(-1)?.number
|
||||
? false
|
||||
: meta.episode.number === meta.episodes.at(-1)!.number;
|
||||
|
||||
const seasons = useSeasons(meta?.tmdbId ?? "", isLastEpisode);
|
||||
const seasons = useSeasons(meta?.tmdbId, isLastEpisode);
|
||||
|
||||
const nextSeason = seasons.value?.find(
|
||||
(season) => season.number === (meta?.season?.number ?? 0) + 1,
|
||||
);
|
||||
|
||||
const nextSeasonEpisode = useNextSeasonEpisode(
|
||||
nextSeason,
|
||||
meta?.tmdbId ?? "",
|
||||
);
|
||||
const nextSeasonEpisode = useNextSeasonEpisode(nextSeason, meta?.tmdbId);
|
||||
|
||||
let show = false;
|
||||
const hasAutoplayed = useRef(false);
|
||||
|
@@ -14,7 +14,6 @@ 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;
|
||||
@@ -157,8 +156,6 @@ export function useScrape() {
|
||||
startScrape,
|
||||
} = useBaseScrape();
|
||||
|
||||
const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder);
|
||||
|
||||
const startScraping = useCallback(
|
||||
async (media: ScrapeMedia) => {
|
||||
const providerApiUrl = getLoadbalancedProviderApiUrl();
|
||||
@@ -184,7 +181,6 @@ export function useScrape() {
|
||||
const providers = getProviders();
|
||||
const output = await providers.runAll({
|
||||
media,
|
||||
sourceOrder: preferredSourceOrder,
|
||||
events: {
|
||||
init: initEvent,
|
||||
start: startEvent,
|
||||
@@ -203,7 +199,6 @@ export function useScrape() {
|
||||
discoverEmbedsEvent,
|
||||
getResult,
|
||||
startScrape,
|
||||
preferredSourceOrder,
|
||||
],
|
||||
);
|
||||
|
||||
|
@@ -52,7 +52,6 @@ export function useSettingsState(
|
||||
| undefined,
|
||||
enableThumbnails: boolean,
|
||||
enableAutoplay: boolean,
|
||||
sourceOrder: string[],
|
||||
) {
|
||||
const [proxyUrlsState, setProxyUrls, resetProxyUrls, proxyUrlsChanged] =
|
||||
useDerived(proxyUrls);
|
||||
@@ -92,12 +91,6 @@ export function useSettingsState(
|
||||
resetEnableAutoplay,
|
||||
enableAutoplayChanged,
|
||||
] = useDerived(enableAutoplay);
|
||||
const [
|
||||
sourceOrderState,
|
||||
setSourceOrderState,
|
||||
resetSourceOrder,
|
||||
sourceOrderChanged,
|
||||
] = useDerived(sourceOrder);
|
||||
|
||||
function reset() {
|
||||
resetTheme();
|
||||
@@ -110,7 +103,6 @@ export function useSettingsState(
|
||||
resetProfile();
|
||||
resetEnableThumbnails();
|
||||
resetEnableAutoplay();
|
||||
resetSourceOrder();
|
||||
}
|
||||
|
||||
const changed =
|
||||
@@ -122,8 +114,7 @@ export function useSettingsState(
|
||||
proxyUrlsChanged ||
|
||||
profileChanged ||
|
||||
enableThumbnailsChanged ||
|
||||
enableAutoplayChanged ||
|
||||
sourceOrderChanged;
|
||||
enableAutoplayChanged;
|
||||
|
||||
return {
|
||||
reset,
|
||||
@@ -173,10 +164,5 @@ export function useSettingsState(
|
||||
set: setEnableAutoplayState,
|
||||
changed: enableAutoplayChanged,
|
||||
},
|
||||
sourceOrder: {
|
||||
state: sourceOrderState,
|
||||
set: setSourceOrderState,
|
||||
changed: sourceOrderChanged,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@@ -31,10 +31,6 @@ import { SettingsSyncer } from "@/stores/subtitles/SettingsSyncer";
|
||||
import { ThemeProvider } from "@/stores/theme";
|
||||
import { TurnstileProvider } from "@/stores/turnstile";
|
||||
|
||||
import {
|
||||
extensionInfo,
|
||||
isExtensionActiveCached,
|
||||
} from "./backend/extension/messaging";
|
||||
import { initializeChromecast } from "./setup/chromecast";
|
||||
import { initializeOldStores } from "./stores/__old/migrations";
|
||||
|
||||
@@ -144,15 +140,6 @@ function TheRouter(props: { children: ReactNode }) {
|
||||
return <HashRouter>{props.children}</HashRouter>;
|
||||
}
|
||||
|
||||
// Checks if the extension is installed
|
||||
function ExtensionStatus() {
|
||||
if (!isExtensionActiveCached()) {
|
||||
throw extensionInfo();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const container = document.getElementById("root");
|
||||
const root = createRoot(container!);
|
||||
|
||||
@@ -162,7 +149,6 @@ root.render(
|
||||
<TurnstileProvider />
|
||||
<HelmetProvider>
|
||||
<Suspense fallback={<LoadingScreen type="lazy" />}>
|
||||
<ExtensionStatus />
|
||||
<ThemeProvider applyGlobal>
|
||||
<ProgressSyncer />
|
||||
<BookmarkSyncer />
|
||||
|
@@ -11,7 +11,6 @@ import {
|
||||
import { getSessions, updateSession } from "@/backend/accounts/sessions";
|
||||
import { updateSettings } from "@/backend/accounts/settings";
|
||||
import { editUser } from "@/backend/accounts/user";
|
||||
import { getAllProviders } from "@/backend/providers/providers";
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
import { WideContainer } from "@/components/layout/WideContainer";
|
||||
import { UserIcons } from "@/components/UserIcon";
|
||||
@@ -126,9 +125,6 @@ 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);
|
||||
@@ -152,25 +148,8 @@ export function SettingsPage() {
|
||||
account?.profile,
|
||||
enableThumbnails,
|
||||
enableAutoplay,
|
||||
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]);
|
||||
@@ -222,7 +201,6 @@ 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);
|
||||
@@ -249,7 +227,6 @@ export function SettingsPage() {
|
||||
setEnableThumbnails,
|
||||
state,
|
||||
setEnableAutoplay,
|
||||
setSourceOrder,
|
||||
setAppLanguage,
|
||||
setTheme,
|
||||
setSubStyling,
|
||||
@@ -297,8 +274,6 @@ export function SettingsPage() {
|
||||
setEnableThumbnails={state.enableThumbnails.set}
|
||||
enableAutoplay={state.enableAutoplay.state}
|
||||
setEnableAutoplay={state.enableAutoplay.set}
|
||||
sourceOrder={availableSources}
|
||||
setSourceOrder={state.sourceOrder.set}
|
||||
/>
|
||||
</div>
|
||||
<div id="settings-appearance" className="mt-48">
|
||||
|
@@ -1,13 +1,9 @@
|
||||
import classNames from "classnames";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
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";
|
||||
import { SortableList } from "@/components/form/SortableList";
|
||||
import { Heading1 } from "@/components/utils/Text";
|
||||
import { appLanguageOptions } from "@/setup/i18n";
|
||||
import { isAutoplayAllowed } from "@/utils/autoplay";
|
||||
@@ -20,8 +16,6 @@ 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));
|
||||
@@ -40,17 +34,6 @@ 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 (
|
||||
<div className="space-y-12">
|
||||
<Heading1 border>{t("settings.preferences.title")}</Heading1>
|
||||
@@ -111,29 +94,6 @@ export function PreferencesPart(props: {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="text-white font-bold">
|
||||
{t("settings.preferences.sourceOrder")}
|
||||
</p>
|
||||
<p className="max-w-[25rem] font-medium">
|
||||
{t("settings.preferences.sourceOrderDescription")}
|
||||
</p>
|
||||
|
||||
<SortableList
|
||||
items={sourceItems}
|
||||
setItems={(items) =>
|
||||
props.setSourceOrder(items.map((item) => item.id))
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
className="max-w-[25rem]"
|
||||
theme="secondary"
|
||||
onClick={() => props.setSourceOrder(allSources.map((s) => s.id))}
|
||||
>
|
||||
{t("settings.reset")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -4,35 +4,26 @@ import { immer } from "zustand/middleware/immer";
|
||||
|
||||
export interface PreferencesStore {
|
||||
enableThumbnails: boolean;
|
||||
enableAutoplay: boolean;
|
||||
sourceOrder: string[];
|
||||
|
||||
setEnableThumbnails(v: boolean): void;
|
||||
enableAutoplay: boolean;
|
||||
setEnableAutoplay(v: boolean): void;
|
||||
setSourceOrder(v: string[]): void;
|
||||
}
|
||||
|
||||
export const usePreferencesStore = create(
|
||||
persist(
|
||||
immer<PreferencesStore>((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",
|
||||
|
Reference in New Issue
Block a user