feat: implement pack enable/disable functionality and improve UI responsiveness

This commit is contained in:
alexsparkes
2026-02-03 15:45:04 +00:00
parent 0acac6dcee
commit bafa2ecbe7
8 changed files with 158 additions and 31 deletions

View File

@@ -162,12 +162,17 @@ export async function checkAndRefreshAPIPacks() {
export function buildPhotoPool() {
const pool = [];
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}');
const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]');
const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}');
installed.forEach((pack) => {
if (pack.type !== 'photos') return;
// Filter by enabled status - default to enabled if not in enabledPacks object
const packId = pack.id || pack.name;
if (enabledPacks[packId] === false) return;
if (pack.api_enabled) {
// API pack - check if configured and ready
if (apiPacksReady.includes(pack.id)) {

View File

@@ -65,7 +65,9 @@ const SourceSection = ({
toggleFunction={onToggle}
showCreateYourOwn={false}
onUninstall={onPhotoPackUninstall}
onTogglePack={() => {}}
viewType="grid"
showChips={false}
/>
{/* Settings for API packs */}

View File

@@ -4,7 +4,10 @@ import { MdCheckCircle, MdOutlineUploadFile, MdClose } from 'react-icons/md';
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { Tooltip } from 'components/Elements';
import { Button } from 'components/Elements';
import Switch from 'components/Form/Settings/Switch/Switch';
import Dropdown from '../../../../components/Form/Settings/Dropdown/Dropdown';
import EventBus from 'utils/eventbus';
import { getProxiedImageUrl } from 'utils/marketplace';
function filterItems(item, filter, categoryFilter) {
@@ -60,32 +63,92 @@ function ItemCard({
isInstalled,
isAdded,
onUninstall,
onTogglePack,
showChips = true,
}) {
item._onCollection = onCollection;
const isSideloaded = item.sideload === true;
const packId = item.id || item.name;
// Use React state to manage enabled status for immediate UI updates
const [isEnabled, setIsEnabled] = useState(() => {
const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}');
return enabledPacks[packId] !== false; // Default to enabled if not set
});
const handleTogglePack = (e) => {
e.stopPropagation();
const newState = !isEnabled;
// Update local state immediately for UI responsiveness
setIsEnabled(newState);
// Update localStorage
const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}');
enabledPacks[packId] = newState;
localStorage.setItem('enabledPacks', JSON.stringify(enabledPacks));
if (onTogglePack) {
onTogglePack(packId, newState);
}
// Emit refresh event for quotes only (background will update on next interval)
if (item.type === 'quotes') {
EventBus.emit('refresh', 'quote');
}
};
return (
<div
className={`item ${isSideloaded ? 'item-sideloaded' : ''}`}
className={`item ${isSideloaded ? 'item-sideloaded' : ''} ${!isEnabled && isAdded ? 'item-disabled' : ''}`}
onClick={isSideloaded ? undefined : () => toggleFunction(item)}
key={item.name}
>
{isAdded && onUninstall && (
<Tooltip
title={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
{isAdded && onTogglePack && (
<div
className="item-toggle-switch"
onClick={(e) => e.stopPropagation()}
style={{ position: 'absolute', top: '12px', right: '12px', zIndex: 3 }}
>
<button
className="item-uninstall-btn"
onClick={(e) => {
e.stopPropagation();
onUninstall(item.type, item.name);
}}
>
<MdClose />
</button>
</Tooltip>
<label className="switch-track" style={{ cursor: 'pointer' }}>
<input
type="checkbox"
checked={isEnabled}
onChange={handleTogglePack}
style={{ display: 'none' }}
/>
<div
className={`switch-track ${isEnabled ? 'checked' : ''}`}
style={{
width: '52px',
height: '32px',
borderRadius: '16px',
backgroundColor: isEnabled
? 'var(--linkColor, #5298ff)'
: 'rgba(128, 128, 128, 0.3)',
position: 'relative',
transition: 'background-color 0.2s',
cursor: 'pointer',
}}
>
<div
className="switch-thumb"
style={{
width: '24px',
height: '24px',
borderRadius: '50%',
backgroundColor: 'white',
position: 'absolute',
top: '4px',
left: isEnabled ? '24px' : '4px',
transition: 'left 0.2s',
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
}}
/>
</div>
</label>
</div>
)}
{isSideloaded && (
<Tooltip
@@ -128,18 +191,33 @@ function ItemCard({
''
)}
<div className="card-chips">
{item.type && (
<span className="card-type">
{variables.getMessage('modals.main.marketplace.' + getTypeTranslationKey(item.type))}
</span>
)}
{item.in_collections && item.in_collections.length > 0 && !onCollection && (
<span className="card-collection">
{item.in_collections[0].display_name || item.in_collections[0].name}
</span>
)}
</div>
{showChips && (
<div className="card-chips">
{item.type && (
<span className="card-type">
{variables.getMessage('modals.main.marketplace.' + getTypeTranslationKey(item.type))}
</span>
)}
{item.in_collections && item.in_collections.length > 0 && !onCollection && (
<span className="card-collection">
{item.in_collections[0].display_name || item.in_collections[0].name}
</span>
)}
</div>
)}
{isAdded && onUninstall && (
<Button
type="settings"
onClick={(e) => {
e.stopPropagation();
onUninstall(item.type, item.name);
}}
icon={<MdClose />}
label={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
style={{ marginTop: '10px', width: '100%' }}
/>
)}
</div>
</div>
);
@@ -156,7 +234,9 @@ function Items({
onSortChange,
isAdded = false,
onUninstall,
onTogglePack,
viewType = 'grid',
showChips = true,
}) {
const [selectedCategory, setSelectedCategory] = useState('all');
const [sortType, setSortType] = useState(localStorage.getItem('sortMarketplace') || 'a-z');
@@ -222,6 +302,8 @@ function Items({
isInstalled={installedNames.has(item.name)}
isAdded={isAdded}
onUninstall={onUninstall}
onTogglePack={onTogglePack}
showChips={showChips}
key={index}
/>
))}

View File

@@ -114,9 +114,7 @@ export function useQuoteLoader(updateQuote) {
}
// Filter out incomplete quotes (empty quote text)
const validQuotes = customQuote?.filter(
(q) => q.quote && q.quote.trim() !== ''
) || [];
const validQuotes = customQuote?.filter((q) => q.quote && q.quote.trim() !== '') || [];
if (validQuotes.length === 0) {
return { noQuote: true };
@@ -144,8 +142,14 @@ export function useQuoteLoader(updateQuote) {
}
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}');
const quotePack = installed
.filter((item) => item.type === 'quotes')
.filter((item) => {
if (item.type !== 'quotes') return false;
const packId = item.id || item.name;
// Default to enabled if not in enabledPacks object
return enabledPacks[packId] !== false;
})
.flatMap((item) =>
item.quotes.map((quote) => ({
...quote,

View File

@@ -359,7 +359,9 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
<>
<Row final={true}>
<Content
title={variables.getMessage('modals.main.settings.sections.quote.installed_packs_title')}
title={variables.getMessage(
'modals.main.settings.sections.quote.installed_packs_title',
)}
subtitle={`${installedQuotePacks.length} ${installedQuotePacks.length === 1 ? 'pack' : 'packs'}${totalQuotes} ${totalQuotes === 1 ? 'quote' : 'quotes'}`}
/>
<Action>
@@ -378,7 +380,9 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
toggleFunction={handleToggle}
showCreateYourOwn={false}
onUninstall={handleUninstall}
onTogglePack={() => {}}
viewType="grid"
showChips={false}
/>
</>
);