feat: enhance BackgroundOptions and PhotoPackSettings for improved offline handling and validation

This commit is contained in:
alexsparkes
2026-02-03 12:01:07 +00:00
parent 09f2e0519d
commit a9ab5d9651
2 changed files with 53 additions and 43 deletions

View File

@@ -27,9 +27,12 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section
const [backgroundFilter, setBackgroundFilter] = useState(
localStorage.getItem('backgroundFilter') || 'none',
);
const [backgroundCategories, setBackgroundCategories] = useState([
variables.getMessage('modals.main.loading'),
]);
const [backgroundCategories, setBackgroundCategories] = useState(() => {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return [variables.getMessage('modals.update.offline.title')];
}
return [variables.getMessage('modals.main.loading')];
});
const [backgroundCategoriesOG, setBackgroundCategoriesOG] = useState([]);
const [backgroundAPI, setBackgroundAPI] = useState(
localStorage.getItem('backgroundAPI') || 'mue',
@@ -109,7 +112,6 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section
controllerRef.current = new AbortController();
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
setBackgroundCategories([variables.getMessage('modals.update.offline.title')]);
return;
}

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import variables from 'config/variables';
import EventBus from 'utils/eventbus';
import { Dropdown, Text, Switch, Slider, ChipSelect } from 'components/Form/Settings';
@@ -9,10 +9,6 @@ import { refreshAPIPackCache } from 'features/background/api/photoPackAPI';
import { MdRefresh, MdWarning, MdExpandMore, MdExpandLess } from 'react-icons/md';
const PhotoPackSettings = ({ pack }) => {
if (!pack.settings_schema || pack.settings_schema.length === 0) {
return null;
}
const [settings, setSettings] = useState(() => {
const saved = localStorage.getItem(`photopack_settings_${pack.id}`);
return saved ? JSON.parse(saved) : {};
@@ -23,36 +19,7 @@ const PhotoPackSettings = ({ pack }) => {
const [validationErrors, setValidationErrors] = useState([]);
const [isExpanded, setIsExpanded] = useState(false);
// Load dynamic options (e.g., categories from API)
useEffect(() => {
pack.settings_schema.forEach((field) => {
if (field.dynamic && field.options_source) {
loadDynamicOptions(field);
}
});
}, [pack.id]);
// Validate settings
useEffect(() => {
validateSettings();
}, [settings]);
const loadDynamicOptions = async (field) => {
if (field.options_source === 'api:categories') {
try {
const response = await fetch(`${variables.constants.API_URL}/images/categories`);
const categories = await response.json();
setDynamicOptions((prev) => ({
...prev,
[field.key]: categories,
}));
} catch (error) {
console.error('Failed to load categories:', error);
}
}
};
const validateSettings = () => {
const validateSettings = useCallback(() => {
const errors = [];
pack.settings_schema.forEach((field) => {
if (field.required && !settings[field.key]) {
@@ -73,6 +40,41 @@ const PhotoPackSettings = ({ pack }) => {
const filtered = apiPacksReady.filter((id) => id !== pack.id);
localStorage.setItem('api_packs_ready', JSON.stringify(filtered));
}
}, [pack.id, pack.settings_schema, settings]);
// Load dynamic options (e.g., categories from API)
useEffect(() => {
if (!pack.settings_schema || pack.settings_schema.length === 0) {
return;
}
pack.settings_schema.forEach((field) => {
if (field.dynamic && field.options_source) {
loadDynamicOptions(field);
}
});
}, [pack.id, pack.settings_schema]);
// Validate settings
useEffect(() => {
if (!pack.settings_schema || pack.settings_schema.length === 0) {
return;
}
validateSettings();
}, [settings, validateSettings, pack.settings_schema]);
const loadDynamicOptions = async (field) => {
if (field.options_source === 'api:categories') {
try {
const response = await fetch(`${variables.constants.API_URL}/images/categories`);
const categories = await response.json();
setDynamicOptions((prev) => ({
...prev,
[field.key]: categories,
}));
} catch (error) {
console.error('Failed to load categories:', error);
}
}
};
const handleSettingChange = (key, value, secure = false) => {
@@ -90,14 +92,14 @@ const PhotoPackSettings = ({ pack }) => {
EventBus.emit('refresh', 'background');
};
const renderField = (field, index) => {
const renderField = (field) => {
const value =
field.secure && settings[field.key]
? atob(settings[field.key])
: settings[field.key] || field.default;
switch (field.type) {
case 'dropdown':
case 'dropdown': {
const dropdownItems = field.options.map((opt) => ({
value: opt.value,
text: opt.label,
@@ -111,8 +113,9 @@ const PhotoPackSettings = ({ pack }) => {
onChange={(newValue) => handleSettingChange(field.key, newValue)}
/>
);
}
case 'chipselect':
case 'chipselect': {
const options = field.dynamic ? dynamicOptions[field.key] || [] : field.options;
return (
<ChipSelect
@@ -122,6 +125,7 @@ const PhotoPackSettings = ({ pack }) => {
onChange={(newValue) => handleSettingChange(field.key, newValue)}
/>
);
}
case 'text':
return (
@@ -164,6 +168,10 @@ const PhotoPackSettings = ({ pack }) => {
}
};
if (!pack.settings_schema || pack.settings_schema.length === 0) {
return null;
}
return (
<>
<Section
@@ -215,7 +223,7 @@ const PhotoPackSettings = ({ pack }) => {
{pack.settings_schema.map((field, index) => (
<Row key={field.key} final={index === pack.settings_schema.length - 1}>
<Content title="" />
<Action>{renderField(field, index)}</Action>
<Action>{renderField(field)}</Action>
</Row>
))}
</>