Fix settings toggles layout and add processing-log retention controls
This commit is contained in:
@@ -12,6 +12,7 @@ import type {
|
||||
DisplaySettings,
|
||||
HandwritingStyleClusteringSettings,
|
||||
OcrTaskSettings,
|
||||
ProcessingLogRetentionSettings,
|
||||
PredefinedPathEntry,
|
||||
PredefinedTagEntry,
|
||||
ProviderSettings,
|
||||
@@ -47,6 +48,24 @@ function parseCardsPerPageInput(input: string, fallback: number): number {
|
||||
return clampCardsPerPage(parsed);
|
||||
}
|
||||
|
||||
const DEFAULT_PROCESSING_LOG_RETENTION: ProcessingLogRetentionSettings = {
|
||||
keep_document_sessions: 2,
|
||||
keep_unbound_entries: 80,
|
||||
};
|
||||
|
||||
const PROCESSING_LOG_SESSION_MIN = 0;
|
||||
const PROCESSING_LOG_SESSION_MAX = 20;
|
||||
const PROCESSING_LOG_UNBOUND_MIN = 0;
|
||||
const PROCESSING_LOG_UNBOUND_MAX = 400;
|
||||
|
||||
function clampProcessingLogDocumentSessions(value: number): number {
|
||||
return Math.max(PROCESSING_LOG_SESSION_MIN, Math.min(PROCESSING_LOG_SESSION_MAX, value));
|
||||
}
|
||||
|
||||
function clampProcessingLogUnboundEntries(value: number): number {
|
||||
return Math.max(PROCESSING_LOG_UNBOUND_MIN, Math.min(PROCESSING_LOG_UNBOUND_MAX, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders compact human-oriented settings controls.
|
||||
*/
|
||||
@@ -69,6 +88,7 @@ export default function SettingsScreen({
|
||||
const [newPredefinedTag, setNewPredefinedTag] = useState<string>('');
|
||||
const [uploadDefaults, setUploadDefaults] = useState<UploadDefaultsSettings | null>(null);
|
||||
const [displaySettings, setDisplaySettings] = useState<DisplaySettings | null>(null);
|
||||
const [processingLogRetention, setProcessingLogRetention] = useState<ProcessingLogRetentionSettings | null>(null);
|
||||
const [cardsPerPageInput, setCardsPerPageInput] = useState<string>('12');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -92,6 +112,15 @@ export default function SettingsScreen({
|
||||
setPredefinedTags(settings.predefined_tags);
|
||||
setUploadDefaults(settings.upload_defaults);
|
||||
setDisplaySettings(settings.display);
|
||||
setProcessingLogRetention({
|
||||
keep_document_sessions: clampProcessingLogDocumentSessions(
|
||||
settings.processing_log_retention?.keep_document_sessions ??
|
||||
DEFAULT_PROCESSING_LOG_RETENTION.keep_document_sessions,
|
||||
),
|
||||
keep_unbound_entries: clampProcessingLogUnboundEntries(
|
||||
settings.processing_log_retention?.keep_unbound_entries ?? DEFAULT_PROCESSING_LOG_RETENTION.keep_unbound_entries,
|
||||
),
|
||||
});
|
||||
setCardsPerPageInput(String(settings.display.cards_per_page));
|
||||
setError(null);
|
||||
}, [settings]);
|
||||
@@ -163,7 +192,15 @@ export default function SettingsScreen({
|
||||
};
|
||||
|
||||
const handleSave = useCallback(async (): Promise<void> => {
|
||||
if (!ocrTask || !summaryTask || !routingTask || !handwritingStyle || !uploadDefaults || !displaySettings) {
|
||||
if (
|
||||
!ocrTask ||
|
||||
!summaryTask ||
|
||||
!routingTask ||
|
||||
!handwritingStyle ||
|
||||
!uploadDefaults ||
|
||||
!displaySettings ||
|
||||
!processingLogRetention
|
||||
) {
|
||||
setError('Settings are not fully loaded yet');
|
||||
return;
|
||||
}
|
||||
@@ -175,7 +212,12 @@ export default function SettingsScreen({
|
||||
setError(null);
|
||||
try {
|
||||
const resolvedCardsPerPage = parseCardsPerPageInput(cardsPerPageInput, displaySettings.cards_per_page);
|
||||
const resolvedProcessingLogRetention: ProcessingLogRetentionSettings = {
|
||||
keep_document_sessions: clampProcessingLogDocumentSessions(processingLogRetention.keep_document_sessions),
|
||||
keep_unbound_entries: clampProcessingLogUnboundEntries(processingLogRetention.keep_unbound_entries),
|
||||
};
|
||||
setDisplaySettings({ ...displaySettings, cards_per_page: resolvedCardsPerPage });
|
||||
setProcessingLogRetention(resolvedProcessingLogRetention);
|
||||
setCardsPerPageInput(String(resolvedCardsPerPage));
|
||||
|
||||
await onSave({
|
||||
@@ -187,6 +229,7 @@ export default function SettingsScreen({
|
||||
cards_per_page: resolvedCardsPerPage,
|
||||
log_typing_animation_enabled: displaySettings.log_typing_animation_enabled,
|
||||
},
|
||||
processing_log_retention: resolvedProcessingLogRetention,
|
||||
predefined_paths: predefinedPaths,
|
||||
predefined_tags: predefinedTags,
|
||||
handwriting_style_clustering: {
|
||||
@@ -252,21 +295,51 @@ export default function SettingsScreen({
|
||||
routingTask,
|
||||
summaryTask,
|
||||
uploadDefaults,
|
||||
processingLogRetention,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!onRegisterSaveAction) {
|
||||
return;
|
||||
}
|
||||
if (!settings || !ocrTask || !summaryTask || !routingTask || !handwritingStyle || !uploadDefaults || !displaySettings) {
|
||||
if (
|
||||
!settings ||
|
||||
!ocrTask ||
|
||||
!summaryTask ||
|
||||
!routingTask ||
|
||||
!handwritingStyle ||
|
||||
!uploadDefaults ||
|
||||
!displaySettings ||
|
||||
!processingLogRetention
|
||||
) {
|
||||
onRegisterSaveAction(null);
|
||||
return;
|
||||
}
|
||||
onRegisterSaveAction(() => handleSave());
|
||||
return () => onRegisterSaveAction(null);
|
||||
}, [displaySettings, handleSave, handwritingStyle, ocrTask, onRegisterSaveAction, routingTask, settings, summaryTask, uploadDefaults]);
|
||||
}, [
|
||||
displaySettings,
|
||||
handleSave,
|
||||
handwritingStyle,
|
||||
ocrTask,
|
||||
onRegisterSaveAction,
|
||||
processingLogRetention,
|
||||
routingTask,
|
||||
settings,
|
||||
summaryTask,
|
||||
uploadDefaults,
|
||||
]);
|
||||
|
||||
if (!settings || !ocrTask || !summaryTask || !routingTask || !handwritingStyle || !uploadDefaults || !displaySettings) {
|
||||
if (
|
||||
!settings ||
|
||||
!ocrTask ||
|
||||
!summaryTask ||
|
||||
!routingTask ||
|
||||
!handwritingStyle ||
|
||||
!uploadDefaults ||
|
||||
!displaySettings ||
|
||||
!processingLogRetention
|
||||
) {
|
||||
return (
|
||||
<section className="settings-layout">
|
||||
<div className="settings-card">
|
||||
@@ -313,6 +386,42 @@ export default function SettingsScreen({
|
||||
onChange={(event) => setCardsPerPageInput(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label className="settings-field">
|
||||
Keep document sessions
|
||||
<input
|
||||
type="number"
|
||||
min={PROCESSING_LOG_SESSION_MIN}
|
||||
max={PROCESSING_LOG_SESSION_MAX}
|
||||
value={processingLogRetention.keep_document_sessions}
|
||||
onChange={(event) => {
|
||||
const nextValue = Number.parseInt(event.target.value, 10);
|
||||
if (!Number.isNaN(nextValue)) {
|
||||
setProcessingLogRetention({
|
||||
...processingLogRetention,
|
||||
keep_document_sessions: clampProcessingLogDocumentSessions(nextValue),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<label className="settings-field">
|
||||
Keep unbound entries
|
||||
<input
|
||||
type="number"
|
||||
min={PROCESSING_LOG_UNBOUND_MIN}
|
||||
max={PROCESSING_LOG_UNBOUND_MAX}
|
||||
value={processingLogRetention.keep_unbound_entries}
|
||||
onChange={(event) => {
|
||||
const nextValue = Number.parseInt(event.target.value, 10);
|
||||
if (!Number.isNaN(nextValue)) {
|
||||
setProcessingLogRetention({
|
||||
...processingLogRetention,
|
||||
keep_unbound_entries: clampProcessingLogUnboundEntries(nextValue),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<label className="inline-checkbox settings-checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -323,6 +432,9 @@ export default function SettingsScreen({
|
||||
/>
|
||||
Processing log typing animation enabled
|
||||
</label>
|
||||
<p className="small settings-helper-text">
|
||||
Processing-log retention values are used by backend trim routines when pruning historical entries.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
min-height: 2rem;
|
||||
}
|
||||
|
||||
input,
|
||||
input:not([type='checkbox']):not([type='radio']),
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
@@ -86,24 +86,39 @@ textarea {
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
input:not([type='checkbox']):not([type='radio'])::placeholder,
|
||||
textarea::placeholder {
|
||||
color: #72819e;
|
||||
}
|
||||
|
||||
input:hover,
|
||||
input:not([type='checkbox']):not([type='radio']):hover,
|
||||
select:hover,
|
||||
textarea:hover {
|
||||
border-color: var(--color-border-strong);
|
||||
}
|
||||
|
||||
input:focus,
|
||||
input:not([type='checkbox']):not([type='radio']):focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 2px rgba(63, 141, 255, 0.2);
|
||||
}
|
||||
|
||||
input[type='checkbox'],
|
||||
input[type='radio'] {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 0;
|
||||
accent-color: var(--color-accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type='checkbox']:focus-visible,
|
||||
input[type='radio']:focus-visible {
|
||||
outline: 2px solid rgba(63, 141, 255, 0.6);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
padding-right: 1.9rem;
|
||||
@@ -966,12 +981,23 @@ button:disabled {
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
min-height: 1.95rem;
|
||||
padding: 0.35rem 0.45rem;
|
||||
border: 1px solid rgba(70, 89, 122, 0.55);
|
||||
border-radius: var(--radius-xs);
|
||||
background: rgba(18, 27, 41, 0.62);
|
||||
font-size: 0.79rem;
|
||||
color: #cad7ed;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inline-checkbox input {
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.inline-checkbox input:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-toggle {
|
||||
|
||||
@@ -176,6 +176,14 @@ export interface DisplaySettings {
|
||||
log_typing_animation_enabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents retention targets used when trimming persisted processing logs.
|
||||
*/
|
||||
export interface ProcessingLogRetentionSettings {
|
||||
keep_document_sessions: number;
|
||||
keep_unbound_entries: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents one predefined logical path and discoverability scope.
|
||||
*/
|
||||
@@ -220,6 +228,7 @@ export interface TaskSettings {
|
||||
export interface AppSettings {
|
||||
upload_defaults: UploadDefaultsSettings;
|
||||
display: DisplaySettings;
|
||||
processing_log_retention: ProcessingLogRetentionSettings;
|
||||
handwriting_style_clustering: HandwritingStyleClusteringSettings;
|
||||
predefined_paths: PredefinedPathEntry[];
|
||||
predefined_tags: PredefinedTagEntry[];
|
||||
@@ -265,6 +274,14 @@ export interface DisplaySettingsUpdate {
|
||||
log_typing_animation_enabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents processing-log retention update payload.
|
||||
*/
|
||||
export interface ProcessingLogRetentionSettingsUpdate {
|
||||
keep_document_sessions?: number;
|
||||
keep_unbound_entries?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents handwriting-style clustering settings update payload.
|
||||
*/
|
||||
@@ -284,6 +301,7 @@ export interface HandwritingStyleClusteringSettingsUpdate {
|
||||
export interface AppSettingsUpdate {
|
||||
upload_defaults?: UploadDefaultsSettingsUpdate;
|
||||
display?: DisplaySettingsUpdate;
|
||||
processing_log_retention?: ProcessingLogRetentionSettingsUpdate;
|
||||
handwriting_style_clustering?: HandwritingStyleClusteringSettingsUpdate;
|
||||
predefined_paths?: PredefinedPathEntry[];
|
||||
predefined_tags?: PredefinedTagEntry[];
|
||||
|
||||
Reference in New Issue
Block a user