Move core files into new folder
This commit is contained in:
30
src/modules/core/hooks/useBackButton.ts
Normal file
30
src/modules/core/hooks/useBackButton.ts
Normal 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');
|
||||
};
|
||||
};
|
||||
25
src/modules/core/hooks/useDebounce.ts
Normal file
25
src/modules/core/hooks/useDebounce.ts
Normal 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;
|
||||
};
|
||||
53
src/modules/core/hooks/useHistory.ts
Normal file
53
src/modules/core/hooks/useHistory.ts
Normal 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;
|
||||
};
|
||||
31
src/modules/core/hooks/usePersistedValue.tsx
Normal file
31
src/modules/core/hooks/usePersistedValue.tsx
Normal 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];
|
||||
};
|
||||
33
src/modules/core/hooks/useResizeObserver.tsx
Normal file
33
src/modules/core/hooks/useResizeObserver.tsx
Normal 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;
|
||||
};
|
||||
88
src/modules/core/hooks/useStorage.tsx
Normal file
88
src/modules/core/hooks/useStorage.tsx
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user