Move core files into new folder

This commit is contained in:
schroda
2024-10-05 16:09:34 +02:00
parent 996bb62888
commit 23a86a77f0
173 changed files with 514 additions and 481 deletions

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useLocation, useNavigate } from 'react-router-dom';
import { useContext } from 'react';
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
export const useBackButton = () => {
const navigate = useNavigate();
const location = useLocation();
const { history } = useContext(NavBarContext);
return () => {
const isHistoryEmpty = !history.length;
const isLastPageInHistoryCurrentPage = history.length === 1 && history[0] === location.pathname;
const canNavigateBack = !isHistoryEmpty && !isLastPageInHistoryCurrentPage;
if (canNavigateBack) {
navigate(-1);
return;
}
navigate('/library');
};
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useEffect, useState } from 'react';
export const useDebounce = <Value>(value: Value, delay: number): Value => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
};

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useCallback, useEffect, useState } from 'react';
import { NavigationType, useLocation, useNavigationType } from 'react-router-dom';
const MAX_DEPTH = 50;
export const useHistory = () => {
const location = useLocation();
const navigationType = useNavigationType();
const [history, setHistory] = useState<string[]>([location.pathname]);
const updateHistory = useCallback((newHistory: string[]) => {
// prevent the history from getting too large (only relevant in case the app never gets reloaded (e.g. browser F5,
// electron window gets closed))
// theoretically the history should be empty for the "base" pages (e.g. library, updates, ...), but since the browser
// navigation is used, opening another base page pushes this page to this history, as if it had a different depth
// than the current page (expected history: library -> manga -> reader,
// possible history: library -> updates -> settings -> library -> manga -> reader)
setHistory(newHistory.slice(-MAX_DEPTH));
}, []);
useEffect(() => {
const isLastPageInHistory = location.key === 'default';
const ignoreInitialPop = isLastPageInHistory && history.length === 1;
if (ignoreInitialPop) {
return;
}
switch (navigationType) {
case NavigationType.Pop:
updateHistory([...history.slice(0, -1)]);
break;
case NavigationType.Push:
updateHistory([...history, location.pathname + location.search]);
break;
case NavigationType.Replace:
updateHistory([...history.slice(0, -1), location.pathname + location.search]);
break;
default:
throw new Error(`Unexpected NavigationType "${navigationType}"`);
}
}, [location]);
return history;
};

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
export const getPersistedServerSetting = <T,>(serverValue: T | undefined, lastValue: T): T => {
const isDisabled = serverValue === 0;
if (isDisabled) {
return lastValue;
}
return serverValue ?? lastValue;
};
export const usePersistedValue = <T,>(
key: string,
defaultValue: T,
currentValue: T | undefined,
getCurrentValue: (currentValue: T | undefined, persistedValue: T) => T,
): [T, (value: T) => void] => {
const [persistedValue, setPersistedValue] = useLocalStorage(key, defaultValue);
const value = getCurrentValue(currentValue, persistedValue);
return [value, setPersistedValue];
};

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { RefObject, useLayoutEffect, useState } from 'react';
export const useResizeObserver = (
ref: RefObject<HTMLElement> | HTMLElement | undefined | null,
callback: ResizeObserverCallback,
): (() => void) => {
const [disconnect, setDisconnect] = useState<() => void>(() => {});
useLayoutEffect(() => {
const element = ref instanceof HTMLElement ? ref : ref?.current;
if (!element) {
return () => {};
}
const resizeObserver = new ResizeObserver(callback);
resizeObserver.observe(element);
setDisconnect(() => () => resizeObserver.disconnect());
return () => resizeObserver.disconnect();
}, [ref, callback]);
return disconnect;
};

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { Dispatch, Reducer, SetStateAction, useCallback, useMemo, useReducer, useSyncExternalStore } from 'react';
import { AppStorage, Storage } from '@/lib/AppStorage.ts';
const subscribeToStorageUpdates = (callback: () => void) => {
window.addEventListener('storage', callback);
return () => window.removeEventListener('storage', callback);
};
function useStorage<T>(storage: Storage, key: string, defaultValue: T | (() => T)): [T, Dispatch<SetStateAction<T>>];
function useStorage<T = undefined>(
storage: Storage,
key: string,
): [T | undefined, Dispatch<SetStateAction<T | undefined>>];
function useStorage<T>(
storage: Storage,
key: string,
defaultValue?: T | (() => T) | undefined,
): [T | undefined, Dispatch<SetStateAction<T | undefined>>] {
const initialState = defaultValue instanceof Function ? defaultValue() : defaultValue;
const storedValueRaw = useSyncExternalStore(subscribeToStorageUpdates, () => storage.getItem(key));
const setValue = useCallback<React.Dispatch<React.SetStateAction<T | undefined>>>(
(value) => {
// Allow value to be a function so we have same API as useState
const valueToStore = value instanceof Function ? value(storage.getItemParsed(key, initialState)) : value;
storage.setItem(key, valueToStore);
},
[key],
);
const storedValue = useMemo(
() => (storedValueRaw !== null ? JSON.parse(storedValueRaw) : initialState),
[storedValueRaw, key],
);
return [storedValue, setValue];
}
const useReducerStorage = <S, A>(
storage: Storage,
reducer: Reducer<S, A>,
key: string,
defaultState: S | (() => S),
) => {
const [storedValue, setValue] = useStorage(storage, key, defaultState);
return useReducer((state: S, action: A): S => {
const newState = reducer(state, action);
setValue(newState);
return newState;
}, storedValue);
};
export function useLocalStorage<T>(key: string, defaultValue: T | (() => T)): [T, Dispatch<SetStateAction<T>>];
export function useLocalStorage<T = undefined>(key: string): [T | undefined, Dispatch<SetStateAction<T | undefined>>];
export function useLocalStorage<T>(
key: string,
defaultValue?: T | undefined | (() => T | undefined),
): [T | undefined, Dispatch<SetStateAction<T | undefined>>] {
return useStorage(AppStorage.local, key, defaultValue);
}
export function useReducerLocalStorage<S, A>(reducer: Reducer<S, A>, key: string, defaultState: S | (() => S)) {
return useReducerStorage(AppStorage.local, reducer, key, defaultState);
}
export function useSessionStorage<T>(key: string, defaultValue: T | (() => T)): [T, Dispatch<SetStateAction<T>>];
export function useSessionStorage<T = undefined>(key: string): [T | undefined, Dispatch<SetStateAction<T | undefined>>];
export function useSessionStorage<T>(
key: string,
defaultValue?: T | (() => T),
): [T | undefined, Dispatch<SetStateAction<T | undefined>>] {
return useStorage(AppStorage.session, key, defaultValue);
}
export function useReducerSessionStorage<S, A>(reducer: Reducer<S, A>, key: string, defaultState: S | (() => S)) {
return useReducerStorage(AppStorage.session, reducer, key, defaultState);
}