Patch: Various feature additions (#625)

- Add admin config for self-settings options visibility. Remove delivery
preferences or notifications from the view.
- Add option to use Booklore's Bookdrop API destination instead of a
specific library
- Add download path options for all torrent clients
This commit is contained in:
Alex
2026-02-20 09:53:47 +00:00
committed by GitHub
parent 8ff2d776ae
commit 554f5fcbe7
21 changed files with 1410 additions and 484 deletions
@@ -9,7 +9,12 @@ import {
import { SelectField } from './fields';
import { FieldWrapper } from './shared';
import { UserAccountCardContent, UserEditActions, UserIdentityHeader } from './users/UserCard';
import { UserOverridesSections } from './users/UserOverridesSections';
import {
DEFAULT_SELF_USER_OVERRIDE_SECTIONS,
normalizeUserOverrideSections,
UserOverridesSections,
type UserOverrideSectionId,
} from './users';
import { PerUserSettings } from './users/types';
import { useUserOverridesState } from './users/useUserOverridesState';
import { getStoredThemePreference, setThemePreference, THEME_FIELD } from '../../utils/themePreference';
@@ -52,6 +57,9 @@ export const SelfSettingsModal = ({ isOpen, onClose, onShowToast }: SelfSettings
const [originalUser, setOriginalUser] = useState<AdminUser | null>(null);
const [deliveryPreferences, setDeliveryPreferences] = useState<DeliveryPreferencesResponse | null>(null);
const [notificationPreferences, setNotificationPreferences] = useState<DeliveryPreferencesResponse | null>(null);
const [visibleSections, setVisibleSections] = useState<UserOverrideSectionId[]>(
DEFAULT_SELF_USER_OVERRIDE_SECTIONS
);
const [editPassword, setEditPassword] = useState('');
const [editPasswordConfirm, setEditPasswordConfirm] = useState('');
@@ -81,6 +89,9 @@ export const SelfSettingsModal = ({ isOpen, onClose, onShowToast }: SelfSettings
setOriginalUser(context.user);
setDeliveryPreferences(context.deliveryPreferences || null);
setNotificationPreferences(context.notificationPreferences || null);
setVisibleSections(
normalizeUserOverrideSections(context.visibleUserSettingsSections, 'self')
);
applyUserOverridesContext({
settings: (context.user.settings || {}) as PerUserSettings,
userOverridableKeys: context.userOverridableKeys || [],
@@ -309,6 +320,7 @@ export const SelfSettingsModal = ({ isOpen, onClose, onShowToast }: SelfSettings
children: (
<UserOverridesSections
scope="self"
sections={visibleSections}
deliveryPreferences={deliveryPreferences}
notificationPreferences={notificationPreferences}
isUserOverridable={isUserOverridable}
@@ -41,6 +41,7 @@ const USER_OVERRIDE_SECTION_DEFINITIONS: UserOverrideSectionDefinition[] = [
const USER_OVERRIDE_SECTION_ORDER: UserOverrideSectionId[] =
USER_OVERRIDE_SECTION_DEFINITIONS.map((section) => section.id);
const USER_OVERRIDE_SECTION_ID_SET = new Set<UserOverrideSectionId>(USER_OVERRIDE_SECTION_ORDER);
const USER_OVERRIDE_SECTION_META: Record<UserOverrideSectionId, UserOverrideSectionDefinition> = {
delivery: { id: 'delivery', adminOnly: false },
@@ -48,6 +49,53 @@ const USER_OVERRIDE_SECTION_META: Record<UserOverrideSectionId, UserOverrideSect
requestPolicy: { id: 'requestPolicy', adminOnly: true },
};
export const DEFAULT_SELF_USER_OVERRIDE_SECTIONS: UserOverrideSectionId[] =
USER_OVERRIDE_SECTION_ORDER.filter((sectionId) => !USER_OVERRIDE_SECTION_META[sectionId].adminOnly);
const isUserOverrideSectionId = (value: string): value is UserOverrideSectionId => (
USER_OVERRIDE_SECTION_ID_SET.has(value as UserOverrideSectionId)
);
export const normalizeUserOverrideSections = (
sections: Iterable<unknown> | null | undefined,
scope: UserOverrideScope,
): UserOverrideSectionId[] => {
const fallbackSections = scope === 'self'
? DEFAULT_SELF_USER_OVERRIDE_SECTIONS
: USER_OVERRIDE_SECTION_ORDER;
if (!sections) {
return fallbackSections;
}
const requestedValues = Array.from(sections);
if (requestedValues.length === 0) {
return [];
}
const requestedIds = new Set<UserOverrideSectionId>();
requestedValues.forEach((value) => {
const normalizedValue = String(value ?? '').trim();
if (isUserOverrideSectionId(normalizedValue)) {
requestedIds.add(normalizedValue);
}
});
if (requestedIds.size === 0) {
return fallbackSections;
}
return USER_OVERRIDE_SECTION_ORDER.filter((sectionId) => {
if (!requestedIds.has(sectionId)) {
return false;
}
if (scope === 'self' && USER_OVERRIDE_SECTION_META[sectionId].adminOnly) {
return false;
}
return true;
});
};
export const UserOverridesSections = ({
scope,
sections,
@@ -60,13 +108,7 @@ export const UserOverridesSections = ({
globalUsersSettingsValues,
onTestNotificationRoutes,
}: UserOverridesSectionsProps) => {
const requestedSections = sections ?? USER_OVERRIDE_SECTION_ORDER;
const activeSections = requestedSections.filter((sectionId) => {
if (scope === 'self' && USER_OVERRIDE_SECTION_META[sectionId].adminOnly) {
return false;
}
return true;
});
const activeSections = normalizeUserOverrideSections(sections, scope);
const sectionNodes: UserOverrideSectionNode[] = [];
@@ -11,7 +11,11 @@ export { UserListView } from './UserListView';
export { RequestPolicyGrid } from './RequestPolicyGrid';
export { UserNotificationOverridesSection } from './UserNotificationOverridesSection';
export { UserOverridesSection } from './UserOverridesSection';
export { UserOverridesSections } from './UserOverridesSections';
export {
UserOverridesSections,
DEFAULT_SELF_USER_OVERRIDE_SECTIONS,
normalizeUserOverrideSections,
} from './UserOverridesSections';
export { UserOverridesView } from './UserOverridesView';
export { useUserForm } from './useUserForm';
export { useUserMutations } from './useUserMutations';
@@ -27,4 +31,5 @@ export {
export type {
RequestPolicyContentType,
} from './requestPolicyGridUtils';
export type { UserOverrideSectionId } from './UserOverridesSections';
export type { RequestPolicyMode } from '../../../types';
+1
View File
@@ -600,6 +600,7 @@ export interface SelfUserEditContext {
deliveryPreferences: DeliveryPreferencesResponse | null;
notificationPreferences: DeliveryPreferencesResponse | null;
userOverridableKeys: string[];
visibleUserSettingsSections?: string[];
}
export const getAdminUsers = async (): Promise<AdminUser[]> => {