174 lines
6.5 KiB
TypeScript
174 lines
6.5 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { NotificationSoundSection } from "@/components/settings/notification-sound-section";
|
|
import { NotificationEventsTable } from "@/components/settings/notification-events-table";
|
|
import { SettingsPageTemplate } from "@/components/settings/settings-page-template";
|
|
import { DEFAULT_NOTIFICATION_EVENTS } from "@/lib/notifications/events";
|
|
import {
|
|
DesktopNotificationsSection,
|
|
useNotificationPermission,
|
|
} from "@/components/settings/notification-permission-section";
|
|
import {
|
|
useNotificationsState,
|
|
useSaveRequest,
|
|
useNotificationsActions,
|
|
useIsDirty,
|
|
type NotificationsState,
|
|
} from "@/components/settings/notifications-settings-actions";
|
|
import { ExternalProvidersSection } from "@/components/settings/notifications-settings-external-providers";
|
|
import { SettingsGroup } from "@/components/settings/settings-group";
|
|
import { GENERAL_SETTINGS_TARGETS } from "@/lib/settings-discovery/catalog/preferences";
|
|
|
|
function useTableData(state: NotificationsState) {
|
|
const { providers, notificationEvents } = state;
|
|
const tableProviders = useMemo(
|
|
() =>
|
|
[...providers].sort((a, b) => {
|
|
if (a.type === b.type) return a.name.localeCompare(b.name);
|
|
if (a.type === "local") return -1;
|
|
if (b.type === "local") return 1;
|
|
return a.type.localeCompare(b.type);
|
|
}),
|
|
[providers],
|
|
);
|
|
const tableEvents = useMemo(() => {
|
|
if (notificationEvents.length > 0) return notificationEvents;
|
|
const eventSet = new Set<string>();
|
|
for (const provider of providers) {
|
|
for (const event of provider.events ?? []) eventSet.add(event);
|
|
}
|
|
return eventSet.size ? Array.from(eventSet) : DEFAULT_NOTIFICATION_EVENTS;
|
|
}, [notificationEvents, providers]);
|
|
return { tableProviders, tableEvents };
|
|
}
|
|
|
|
function useNotificationPageSaveState(state: NotificationsState, soundIsDirty: boolean) {
|
|
const { t } = useTranslation();
|
|
const providerIsDirty = useIsDirty(state);
|
|
const creatingApprise = state.showAppriseForm && state.appriseFormMode === "create";
|
|
const canSave = !creatingApprise || state.appriseUrls.trim().length > 0;
|
|
const revision = useMemo(
|
|
() =>
|
|
JSON.stringify({
|
|
providers: state.providers,
|
|
appriseEdits: state.appriseEdits,
|
|
appriseNameEdits: state.appriseNameEdits,
|
|
pendingDeletes: [...state.pendingDeletes].sort(),
|
|
createDraft: creatingApprise ? { name: state.appriseName, urls: state.appriseUrls } : null,
|
|
}),
|
|
[
|
|
creatingApprise,
|
|
state.providers,
|
|
state.appriseEdits,
|
|
state.appriseNameEdits,
|
|
state.pendingDeletes,
|
|
state.appriseName,
|
|
state.appriseUrls,
|
|
],
|
|
);
|
|
return {
|
|
providerIsDirty,
|
|
cardIsDirty: providerIsDirty || soundIsDirty,
|
|
canSave,
|
|
invalidReason: canSave ? undefined : t("settings:appriseUrlRequired"),
|
|
revision,
|
|
};
|
|
}
|
|
|
|
export function NotificationsSettings() {
|
|
const { t } = useTranslation();
|
|
const state = useNotificationsState();
|
|
const { notificationPermission, refreshPermission } = useNotificationPermission();
|
|
const saveRequest = useSaveRequest(state);
|
|
const actions = useNotificationsActions(state, refreshPermission);
|
|
const [soundIsDirty, setSoundIsDirty] = useState(false);
|
|
const saveState = useNotificationPageSaveState(state, soundIsDirty);
|
|
const { tableProviders, tableEvents } = useTableData(state);
|
|
const {
|
|
providers,
|
|
baselineProviders,
|
|
appriseAvailable,
|
|
loaded: notificationProvidersLoaded,
|
|
loading: notificationProvidersLoading,
|
|
rescanApprise,
|
|
appriseRescanPending,
|
|
appriseRescanError,
|
|
appriseRescanResult,
|
|
appriseName,
|
|
setAppriseName,
|
|
appriseUrls,
|
|
setAppriseUrls,
|
|
showAppriseForm,
|
|
appriseFormMode,
|
|
activeAppriseId,
|
|
} = state;
|
|
const appriseProviders = providers.filter((provider) => provider.type === "apprise");
|
|
return (
|
|
<SettingsPageTemplate
|
|
title={t("settings:notifications")}
|
|
description={t("settings:notificationsDescription")}
|
|
isDirty={saveState.providerIsDirty}
|
|
cardIsDirty={saveState.cardIsDirty}
|
|
saveStatus={saveRequest.status}
|
|
saveRevision={saveState.revision}
|
|
canSave={saveState.canSave}
|
|
invalidReason={saveState.invalidReason}
|
|
onSave={() => saveRequest.run()}
|
|
onDiscard={actions.discard}
|
|
contentFrame="none"
|
|
>
|
|
<div className="space-y-6">
|
|
<DesktopNotificationsSection
|
|
notificationPermission={notificationPermission}
|
|
onRequestPermission={actions.handleRequestPermission}
|
|
onRefreshPermission={actions.handleRefreshPermission}
|
|
onTestNotification={actions.handleTestNotification}
|
|
/>
|
|
<NotificationSoundSection onDirtyChange={setSoundIsDirty} />
|
|
<ExternalProvidersSection
|
|
appriseAvailable={appriseAvailable}
|
|
appriseProviders={appriseProviders}
|
|
baselineProviders={baselineProviders}
|
|
appriseFormMode={appriseFormMode}
|
|
activeAppriseId={activeAppriseId}
|
|
appriseName={appriseName}
|
|
appriseUrls={appriseUrls}
|
|
showAppriseForm={showAppriseForm}
|
|
notificationProvidersLoaded={notificationProvidersLoaded}
|
|
notificationProvidersLoading={notificationProvidersLoading}
|
|
appriseRescanPending={appriseRescanPending}
|
|
appriseRescanError={appriseRescanError}
|
|
appriseRescanResult={appriseRescanResult}
|
|
setAppriseName={setAppriseName}
|
|
setAppriseUrls={setAppriseUrls}
|
|
onAppriseNameEdit={actions.handleAppriseNameEdit}
|
|
onAppriseEdit={actions.handleAppriseEdit}
|
|
onOpenForm={actions.openAppriseForm}
|
|
onCloseForm={actions.closeAppriseForm}
|
|
onCancelForm={actions.cancelAppriseForm}
|
|
onDeleteProvider={actions.handleDeleteProvider}
|
|
onTestProvider={actions.handleTestProvider}
|
|
onRescanApprise={rescanApprise}
|
|
/>
|
|
<SettingsGroup
|
|
title={t("settings:notificationEvents")}
|
|
description={t("settings:notificationEventsDescription")}
|
|
discoveryTargetId={GENERAL_SETTINGS_TARGETS.notificationEvents}
|
|
>
|
|
{tableProviders.length > 0 && (
|
|
<NotificationEventsTable
|
|
tableProviders={tableProviders}
|
|
baselineProviders={baselineProviders}
|
|
tableEvents={tableEvents}
|
|
onToggleEvent={actions.handleToggleEvent}
|
|
onTestProvider={actions.handleTestProvider}
|
|
/>
|
|
)}
|
|
</SettingsGroup>
|
|
</div>
|
|
</SettingsPageTemplate>
|
|
);
|
|
}
|