mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
feat: add autoplay preference for extension users
This commit is contained in:
parent
1e0b86badf
commit
20cec61eac
9 changed files with 1784 additions and 35 deletions
|
@ -103,6 +103,7 @@ module.exports = {
|
|||
allowSeparatedGroups: true
|
||||
}
|
||||
],
|
||||
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
|
||||
...a11yOff
|
||||
}
|
||||
};
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
"@types/crypto-js": "^4.2.1",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/fscreen": "^1.0.4",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"@types/lodash.isequal": "^4.5.8",
|
||||
"@types/lodash.merge": "^4.6.9",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
|
|
1698
pnpm-lock.yaml
1698
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -523,6 +523,9 @@
|
|||
"thumbnail": "Generate thumbnails",
|
||||
"thumbnailDescription": "Most of the time, videos don't have thumbnails. You can enable this setting to generate them on the fly but they can make your video slower.",
|
||||
"thumbnailLabel": "Generate thumbnails",
|
||||
"autoplay": "Autoplay",
|
||||
"autoplayDescription": "Automatically play the next episode in a series after reaching the end. This feature is reserved only for extension users.",
|
||||
"autoplayLabel": "Autoplay",
|
||||
"title": "Preferences"
|
||||
},
|
||||
"reset": "Reset",
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import classNames from "classnames";
|
||||
import { useCallback } from "react";
|
||||
import { debounce, throttle } from "lodash";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { isExtensionActiveCached } from "@/backend/extension/messaging";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta";
|
||||
import { Transition } from "@/components/utils/Transition";
|
||||
import { PlayerMeta } from "@/stores/player/slices/source";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
import { usePreferencesStore } from "@/stores/preferences";
|
||||
import { useProgressStore } from "@/stores/progress";
|
||||
|
||||
function shouldShowNextEpisodeButton(
|
||||
|
@ -57,6 +60,7 @@ export function NextEpisodeButton(props: {
|
|||
(s) => s.setShouldStartFromBeginning,
|
||||
);
|
||||
const updateItem = useProgressStore((s) => s.updateItem);
|
||||
const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay);
|
||||
|
||||
let show = false;
|
||||
if (showingState === "always") show = true;
|
||||
|
@ -95,6 +99,19 @@ export function NextEpisodeButton(props: {
|
|||
updateItem,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableAutoplay || !meta || !nextEp || metaType !== "show") return;
|
||||
const halfPercent = duration / 100;
|
||||
const isEnding = time >= duration - halfPercent && duration !== 0;
|
||||
|
||||
const debouncedLoadNextEpisode = throttle(debounce(loadNextEpisode), 300);
|
||||
if (isEnding && isExtensionActiveCached()) debouncedLoadNextEpisode();
|
||||
|
||||
return () => {
|
||||
debouncedLoadNextEpisode.cancel();
|
||||
};
|
||||
}, [duration, enableAutoplay, loadNextEpisode, meta, metaType, nextEp, time]);
|
||||
|
||||
if (!meta?.episode || !nextEp) return null;
|
||||
if (metaType !== "show") return null;
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ export function useSettingsState(
|
|||
}
|
||||
| undefined,
|
||||
enableThumbnails: boolean,
|
||||
enableAutoplay: boolean,
|
||||
) {
|
||||
const [proxyUrlsState, setProxyUrls, resetProxyUrls, proxyUrlsChanged] =
|
||||
useDerived(proxyUrls);
|
||||
|
@ -84,6 +85,12 @@ export function useSettingsState(
|
|||
resetEnableThumbnails,
|
||||
enableThumbnailsChanged,
|
||||
] = useDerived(enableThumbnails);
|
||||
const [
|
||||
enableAutoplayState,
|
||||
setEnableAutoplayState,
|
||||
resetEnableAutoplay,
|
||||
enableAutoplayChanged,
|
||||
] = useDerived(enableAutoplay);
|
||||
|
||||
function reset() {
|
||||
resetTheme();
|
||||
|
@ -95,6 +102,7 @@ export function useSettingsState(
|
|||
resetDeviceName();
|
||||
resetProfile();
|
||||
resetEnableThumbnails();
|
||||
resetEnableAutoplay();
|
||||
}
|
||||
|
||||
const changed =
|
||||
|
@ -105,7 +113,8 @@ export function useSettingsState(
|
|||
backendUrlChanged ||
|
||||
proxyUrlsChanged ||
|
||||
profileChanged ||
|
||||
enableThumbnailsChanged;
|
||||
enableThumbnailsChanged ||
|
||||
enableAutoplayChanged;
|
||||
|
||||
return {
|
||||
reset,
|
||||
|
@ -150,5 +159,10 @@ export function useSettingsState(
|
|||
set: setEnableThumbnailsState,
|
||||
changed: enableThumbnailsChanged,
|
||||
},
|
||||
enableAutoplay: {
|
||||
state: enableAutoplayState,
|
||||
set: setEnableAutoplayState,
|
||||
changed: enableAutoplayChanged,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -122,6 +122,9 @@ export function SettingsPage() {
|
|||
const enableThumbnails = usePreferencesStore((s) => s.enableThumbnails);
|
||||
const setEnableThumbnails = usePreferencesStore((s) => s.setEnableThumbnails);
|
||||
|
||||
const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay);
|
||||
const setEnableAutoplay = usePreferencesStore((s) => s.setEnableAutoplay);
|
||||
|
||||
const account = useAuthStore((s) => s.account);
|
||||
const updateProfile = useAuthStore((s) => s.setAccountProfile);
|
||||
const updateDeviceName = useAuthStore((s) => s.updateDeviceName);
|
||||
|
@ -144,6 +147,7 @@ export function SettingsPage() {
|
|||
backendUrlSetting,
|
||||
account?.profile,
|
||||
enableThumbnails,
|
||||
enableAutoplay,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -196,6 +200,7 @@ export function SettingsPage() {
|
|||
}
|
||||
|
||||
setEnableThumbnails(state.enableThumbnails.state);
|
||||
setEnableAutoplay(state.enableAutoplay.state);
|
||||
setAppLanguage(state.appLanguage.state);
|
||||
setTheme(state.theme.state);
|
||||
setSubStyling(state.subtitleStyling.state);
|
||||
|
@ -217,18 +222,33 @@ export function SettingsPage() {
|
|||
setBackendUrl(url);
|
||||
}
|
||||
}, [
|
||||
state,
|
||||
account,
|
||||
backendUrl,
|
||||
setEnableThumbnails,
|
||||
state.enableThumbnails.state,
|
||||
state.enableAutoplay.state,
|
||||
state.appLanguage.state,
|
||||
state.appLanguage.changed,
|
||||
state.theme.state,
|
||||
state.theme.changed,
|
||||
state.subtitleStyling.state,
|
||||
state.proxyUrls.state,
|
||||
state.proxyUrls.changed,
|
||||
state.profile.state,
|
||||
state.profile.changed,
|
||||
state.backendUrl.changed,
|
||||
state.backendUrl.state,
|
||||
state.deviceName.changed,
|
||||
state.deviceName.state,
|
||||
setEnableAutoplay,
|
||||
setAppLanguage,
|
||||
setTheme,
|
||||
setSubStyling,
|
||||
setProxySet,
|
||||
updateDeviceName,
|
||||
updateProfile,
|
||||
setProxySet,
|
||||
setBackendUrl,
|
||||
logout,
|
||||
setBackendUrl,
|
||||
]);
|
||||
return (
|
||||
<SubPageLayout>
|
||||
|
@ -266,6 +286,8 @@ export function SettingsPage() {
|
|||
setLanguage={state.appLanguage.set}
|
||||
enableThumbnails={state.enableThumbnails.state}
|
||||
setEnableThumbnails={state.enableThumbnails.set}
|
||||
enableAutoplay={state.enableAutoplay.state}
|
||||
setEnableAutoplay={state.enableAutoplay.set}
|
||||
/>
|
||||
</div>
|
||||
<div id="settings-appearance" className="mt-48">
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useAsync } from "react-use";
|
||||
|
||||
import { isExtensionActive } from "@/backend/extension/messaging";
|
||||
import { Toggle } from "@/components/buttons/Toggle";
|
||||
import { FlagIcon } from "@/components/FlagIcon";
|
||||
import { Dropdown } from "@/components/form/Dropdown";
|
||||
|
@ -7,14 +9,31 @@ import { Heading1 } from "@/components/utils/Text";
|
|||
import { appLanguageOptions } from "@/setup/i18n";
|
||||
import { getLocaleInfo, sortLangCodes } from "@/utils/language";
|
||||
|
||||
function useIsExtensionActive() {
|
||||
const { loading, value } = useAsync(async () => {
|
||||
const extensionStatus = (await isExtensionActive()) ? "success" : "unset";
|
||||
return extensionStatus === "success";
|
||||
}, []);
|
||||
|
||||
return {
|
||||
loading,
|
||||
active: value,
|
||||
};
|
||||
}
|
||||
|
||||
export function PreferencesPart(props: {
|
||||
language: string;
|
||||
setLanguage: (l: string) => void;
|
||||
enableThumbnails: boolean;
|
||||
setEnableThumbnails: (v: boolean) => void;
|
||||
enableAutoplay: boolean;
|
||||
setEnableAutoplay: (v: boolean) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const sorted = sortLangCodes(appLanguageOptions.map((item) => item.code));
|
||||
const { loading, active } = useIsExtensionActive();
|
||||
|
||||
const extensionActive = active && !loading;
|
||||
|
||||
const options = appLanguageOptions
|
||||
.sort((a, b) => sorted.indexOf(a.code) - sorted.indexOf(b.code))
|
||||
|
@ -62,6 +81,32 @@ export function PreferencesPart(props: {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white font-bold mb-3">
|
||||
{t("settings.preferences.autoplay")}
|
||||
</p>
|
||||
<p className="max-w-[25rem] font-medium">
|
||||
{t("settings.preferences.autoplayDescription")}
|
||||
</p>
|
||||
<div
|
||||
onClick={() =>
|
||||
extensionActive
|
||||
? props.setEnableAutoplay(!props.enableAutoplay)
|
||||
: null
|
||||
}
|
||||
className="bg-dropdown-background hover:bg-dropdown-hoverBackground select-none my-4 cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg"
|
||||
style={{
|
||||
pointerEvents: extensionActive ? "auto" : "none",
|
||||
opacity: extensionActive ? 1 : 0.5,
|
||||
cursor: extensionActive ? "pointer" : "not-allowed",
|
||||
}}
|
||||
>
|
||||
<Toggle enabled={props.enableAutoplay && extensionActive} />
|
||||
<p className="flex-1 text-white font-bold">
|
||||
{t("settings.preferences.autoplayLabel")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import { immer } from "zustand/middleware/immer";
|
|||
export interface PreferencesStore {
|
||||
enableThumbnails: boolean;
|
||||
setEnableThumbnails(v: boolean): void;
|
||||
enableAutoplay: boolean;
|
||||
setEnableAutoplay(v: boolean): void;
|
||||
}
|
||||
|
||||
export const usePreferencesStore = create(
|
||||
|
@ -16,6 +18,12 @@ export const usePreferencesStore = create(
|
|||
s.enableThumbnails = v;
|
||||
});
|
||||
},
|
||||
enableAutoplay: false,
|
||||
setEnableAutoplay(v) {
|
||||
set((s) => {
|
||||
s.enableAutoplay = v;
|
||||
});
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: "__MW::preferences",
|
||||
|
|
Loading…
Reference in a new issue