mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
Merge develop
This commit is contained in:
@@ -1,19 +1,21 @@
|
||||
import { atom, useAtom } from "jotai";
|
||||
import {useCallback, useEffect, useMemo} from "react";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import * as ScreenOrientation from "expo-screen-orientation";
|
||||
import { storage } from "../mmkv";
|
||||
import { Platform } from "react-native";
|
||||
import {
|
||||
CultureDto,
|
||||
PluginStatus,
|
||||
SubtitlePlaybackMode,
|
||||
ItemSortBy,
|
||||
SortOrder,
|
||||
BaseItemKind,
|
||||
ItemFilter,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import {apiAtom} from "@/providers/JellyfinProvider";
|
||||
import {getPluginsApi} from "@jellyfin/sdk/lib/utils/api";
|
||||
import {writeErrorLog} from "@/utils/log";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { writeInfoLog } from "@/utils/log";
|
||||
|
||||
const STREAMYFIN_PLUGIN_ID = "1e9e5d386e6746158719e98a5c34f004"
|
||||
const STREAMYFIN_PLUGIN_SETTINGS = "STREAMYFIN_PLUGIN_SETTINGS"
|
||||
const STREAMYFIN_PLUGIN_ID = "1e9e5d386e6746158719e98a5c34f004";
|
||||
const STREAMYFIN_PLUGIN_SETTINGS = "STREAMYFIN_PLUGIN_SETTINGS";
|
||||
|
||||
export type DownloadQuality = "original" | "high" | "low";
|
||||
|
||||
@@ -68,13 +70,32 @@ export type DefaultLanguageOption = {
|
||||
|
||||
export enum DownloadMethod {
|
||||
Remux = "remux",
|
||||
Optimized = "optimized"
|
||||
Optimized = "optimized",
|
||||
}
|
||||
|
||||
export type Home = {
|
||||
sections: [Object];
|
||||
};
|
||||
|
||||
export type HomeSection = {
|
||||
orientation?: "horizontal" | "vertical";
|
||||
items?: HomeSectionItemResolver;
|
||||
};
|
||||
|
||||
export type HomeSectionItemResolver = {
|
||||
sortBy?: Array<ItemSortBy>;
|
||||
sortOrder?: Array<SortOrder>;
|
||||
includeItemTypes?: Array<BaseItemKind>;
|
||||
genres?: Array<string>;
|
||||
parentId?: string;
|
||||
limit?: number;
|
||||
filters?: Array<ItemFilter>;
|
||||
};
|
||||
|
||||
export type Settings = {
|
||||
home?: Home | null;
|
||||
autoRotate?: boolean;
|
||||
forceLandscapeInVideoPlayer?: boolean;
|
||||
usePopularPlugin?: boolean;
|
||||
deviceProfile?: "Expo" | "Native" | "Old";
|
||||
mediaListCollectionIds?: string[];
|
||||
preferedLanguage?: string;
|
||||
@@ -107,19 +128,21 @@ export type Settings = {
|
||||
|
||||
export interface Lockable<T> {
|
||||
locked: boolean;
|
||||
value: T
|
||||
value: T;
|
||||
}
|
||||
|
||||
export type PluginLockableSettings = { [K in keyof Settings]: Lockable<Settings[K]> };
|
||||
export type PluginLockableSettings = {
|
||||
[K in keyof Settings]: Lockable<Settings[K]>;
|
||||
};
|
||||
export type StreamyfinPluginConfig = {
|
||||
settings: PluginLockableSettings
|
||||
}
|
||||
settings: PluginLockableSettings;
|
||||
};
|
||||
|
||||
const loadSettings = (): Settings => {
|
||||
const defaultValues: Settings = {
|
||||
home: null,
|
||||
autoRotate: true,
|
||||
forceLandscapeInVideoPlayer: false,
|
||||
usePopularPlugin: false,
|
||||
deviceProfile: "Expo",
|
||||
mediaListCollectionIds: [],
|
||||
preferedLanguage: undefined,
|
||||
@@ -174,7 +197,9 @@ const saveSettings = (settings: Settings) => {
|
||||
};
|
||||
|
||||
export const settingsAtom = atom<Settings | null>(null);
|
||||
export const pluginSettingsAtom = atom(storage.get<PluginLockableSettings>(STREAMYFIN_PLUGIN_SETTINGS));
|
||||
export const pluginSettingsAtom = atom(
|
||||
storage.get<PluginLockableSettings>(STREAMYFIN_PLUGIN_SETTINGS)
|
||||
);
|
||||
|
||||
export const useSettings = () => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
@@ -188,62 +213,25 @@ export const useSettings = () => {
|
||||
}
|
||||
}, [_settings, setSettings]);
|
||||
|
||||
const setPluginSettings = useCallback((settings: PluginLockableSettings | undefined) => {
|
||||
storage.setAny(STREAMYFIN_PLUGIN_SETTINGS, settings)
|
||||
_setPluginSettings(settings)
|
||||
const setPluginSettings = useCallback(
|
||||
(settings: PluginLockableSettings | undefined) => {
|
||||
storage.setAny(STREAMYFIN_PLUGIN_SETTINGS, settings);
|
||||
_setPluginSettings(settings);
|
||||
},
|
||||
[_setPluginSettings]
|
||||
)
|
||||
);
|
||||
|
||||
const refreshStreamyfinPluginSettings = useCallback(
|
||||
async () => {
|
||||
if (!api)
|
||||
return
|
||||
const refreshStreamyfinPluginSettings = useCallback(async () => {
|
||||
if (!api) return;
|
||||
const settings = await api
|
||||
.getStreamyfinPluginConfig()
|
||||
.then(({ data }) => data?.settings);
|
||||
|
||||
const plugins = await getPluginsApi(api).getPlugins().then(({data}) => data);
|
||||
writeInfoLog(`Got remote settings: ${JSON.stringify(settings)}`);
|
||||
|
||||
if (plugins && plugins.length > 0) {
|
||||
const streamyfinPlugin = plugins.find(plugin => plugin.Id === STREAMYFIN_PLUGIN_ID);
|
||||
|
||||
if (!streamyfinPlugin || streamyfinPlugin.Status != PluginStatus.Active) {
|
||||
writeErrorLog(
|
||||
"Streamyfin plugin is currently not active.\n" +
|
||||
`Current status is: ${streamyfinPlugin?.Status}`
|
||||
);
|
||||
setPluginSettings(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = await api.getStreamyfinPluginConfig()
|
||||
.then(({data}) => data.settings)
|
||||
|
||||
setPluginSettings(settings);
|
||||
return settings;
|
||||
}
|
||||
},
|
||||
[api]
|
||||
)
|
||||
|
||||
// We do not want to save over users pre-existing settings in case admin ever removes/unlocks a setting.
|
||||
// If admin sets locked to false but provides a value,
|
||||
// use user settings first and fallback on admin setting if required.
|
||||
const settings: Settings = useMemo(() => {
|
||||
const overrideSettings = Object.entries(pluginSettings || {})
|
||||
.reduce((acc, [key, setting]) => {
|
||||
if (setting) {
|
||||
const {value, locked} = setting
|
||||
acc = Object.assign(acc, {
|
||||
[key]: locked ? value : _settings?.[key as keyof Settings] ?? value
|
||||
})
|
||||
}
|
||||
return acc
|
||||
}, {} as Settings)
|
||||
|
||||
return {
|
||||
..._settings,
|
||||
...overrideSettings
|
||||
}
|
||||
}, [_settings, setSettings, pluginSettings, _setPluginSettings, setPluginSettings])
|
||||
setPluginSettings(settings);
|
||||
return settings;
|
||||
}, [api]);
|
||||
|
||||
const updateSettings = (update: Partial<Settings>) => {
|
||||
if (settings) {
|
||||
@@ -254,5 +242,52 @@ export const useSettings = () => {
|
||||
}
|
||||
};
|
||||
|
||||
return [settings, updateSettings, pluginSettings, setPluginSettings, refreshStreamyfinPluginSettings] as const;
|
||||
// We do not want to save over users pre-existing settings in case admin ever removes/unlocks a setting.
|
||||
// If admin sets locked to false but provides a value,
|
||||
// use user settings first and fallback on admin setting if required.
|
||||
const settings: Settings = useMemo(() => {
|
||||
let unlockedPluginDefaults = {} as Settings;
|
||||
const overrideSettings = Object.entries(pluginSettings || {}).reduce(
|
||||
(acc, [key, setting]) => {
|
||||
if (setting) {
|
||||
const { value, locked } = setting;
|
||||
|
||||
// Make sure we override default settings with plugin settings when they are not locked.
|
||||
// Admin decided what users defaults should be and grants them the ability to change them too.
|
||||
if (
|
||||
locked === false &&
|
||||
value &&
|
||||
_settings?.[key as keyof Settings] !== value
|
||||
) {
|
||||
unlockedPluginDefaults = Object.assign(unlockedPluginDefaults, {
|
||||
[key as keyof Settings]: value,
|
||||
});
|
||||
}
|
||||
|
||||
acc = Object.assign(acc, {
|
||||
[key]: locked ? value : _settings?.[key as keyof Settings] ?? value,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Settings
|
||||
);
|
||||
|
||||
// Update settings with plugin defined defaults
|
||||
if (Object.keys(unlockedPluginDefaults).length > 0) {
|
||||
updateSettings(unlockedPluginDefaults);
|
||||
}
|
||||
return {
|
||||
..._settings,
|
||||
...overrideSettings,
|
||||
};
|
||||
}, [_settings, pluginSettings]);
|
||||
|
||||
return [
|
||||
settings,
|
||||
updateSettings,
|
||||
pluginSettings,
|
||||
setPluginSettings,
|
||||
refreshStreamyfinPluginSettings,
|
||||
] as const;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { atomWithStorage, createJSONStorage } from "jotai/utils";
|
||||
import { storage } from "./mmkv";
|
||||
import {useQuery} from "@tanstack/react-query";
|
||||
import React, {createContext, useContext} from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React, { createContext, useContext } from "react";
|
||||
|
||||
type LogLevel = "INFO" | "WARN" | "ERROR";
|
||||
|
||||
@@ -19,10 +19,12 @@ const mmkvStorage = createJSONStorage(() => ({
|
||||
}));
|
||||
const logsAtom = atomWithStorage("logs", [], mmkvStorage);
|
||||
|
||||
const LogContext = createContext<ReturnType<typeof useLogProvider> | null>(null);
|
||||
const DownloadContext = createContext<ReturnType<
|
||||
typeof useLogProvider
|
||||
> | null>(null);
|
||||
const LogContext = createContext<ReturnType<typeof useLogProvider> | null>(
|
||||
null
|
||||
);
|
||||
const DownloadContext = createContext<ReturnType<typeof useLogProvider> | null>(
|
||||
null
|
||||
);
|
||||
|
||||
function useLogProvider() {
|
||||
const { data: logs } = useQuery({
|
||||
@@ -32,11 +34,10 @@ function useLogProvider() {
|
||||
});
|
||||
|
||||
return {
|
||||
logs
|
||||
}
|
||||
logs,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export const writeToLog = (level: LogLevel, message: string, data?: any) => {
|
||||
const newEntry: LogEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -53,10 +54,13 @@ export const writeToLog = (level: LogLevel, message: string, data?: any) => {
|
||||
const recentLogs = logs.slice(Math.max(logs.length - maxLogs, 0));
|
||||
|
||||
storage.set("logs", JSON.stringify(recentLogs));
|
||||
console.log(message);
|
||||
};
|
||||
|
||||
export const writeInfoLog = (message: string, data?: any) => writeToLog("INFO", message, data);
|
||||
export const writeErrorLog = (message: string, data?: any) => writeToLog("ERROR", message, data);
|
||||
export const writeInfoLog = (message: string, data?: any) =>
|
||||
writeToLog("INFO", message, data);
|
||||
export const writeErrorLog = (message: string, data?: any) =>
|
||||
writeToLog("ERROR", message, data);
|
||||
|
||||
export const readFromLog = (): LogEntry[] => {
|
||||
const logs = storage.getString("logs");
|
||||
@@ -75,14 +79,10 @@ export function useLog() {
|
||||
return context;
|
||||
}
|
||||
|
||||
export function LogProvider({children}: { children: React.ReactNode }) {
|
||||
export function LogProvider({ children }: { children: React.ReactNode }) {
|
||||
const provider = useLogProvider();
|
||||
|
||||
return (
|
||||
<LogContext.Provider value={provider}>
|
||||
{children}
|
||||
</LogContext.Provider>
|
||||
)
|
||||
return <LogContext.Provider value={provider}>{children}</LogContext.Provider>;
|
||||
}
|
||||
|
||||
export default logsAtom;
|
||||
|
||||
Reference in New Issue
Block a user