mirror of
https://github.com/mue/mue.git
synced 2026-07-23 16:57:25 +02:00
feat: implement pack enable/disable functionality and improve UI responsiveness
This commit is contained in:
@@ -114,6 +114,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disabled pack styling
|
||||||
|
&.item-disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
.card-title,
|
||||||
|
.card-subtitle {
|
||||||
|
@include themed {
|
||||||
|
color: t($subColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item-uninstall-btn {
|
.item-uninstall-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -162,12 +162,17 @@ export async function checkAndRefreshAPIPacks() {
|
|||||||
export function buildPhotoPool() {
|
export function buildPhotoPool() {
|
||||||
const pool = [];
|
const pool = [];
|
||||||
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
|
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
|
||||||
|
const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}');
|
||||||
const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]');
|
const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]');
|
||||||
const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}');
|
const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}');
|
||||||
|
|
||||||
installed.forEach((pack) => {
|
installed.forEach((pack) => {
|
||||||
if (pack.type !== 'photos') return;
|
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) {
|
if (pack.api_enabled) {
|
||||||
// API pack - check if configured and ready
|
// API pack - check if configured and ready
|
||||||
if (apiPacksReady.includes(pack.id)) {
|
if (apiPacksReady.includes(pack.id)) {
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ const SourceSection = ({
|
|||||||
toggleFunction={onToggle}
|
toggleFunction={onToggle}
|
||||||
showCreateYourOwn={false}
|
showCreateYourOwn={false}
|
||||||
onUninstall={onPhotoPackUninstall}
|
onUninstall={onPhotoPackUninstall}
|
||||||
|
onTogglePack={() => {}}
|
||||||
viewType="grid"
|
viewType="grid"
|
||||||
|
showChips={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Settings for API packs */}
|
{/* Settings for API packs */}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import { MdCheckCircle, MdOutlineUploadFile, MdClose } from 'react-icons/md';
|
|||||||
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
|
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
|
||||||
|
|
||||||
import { Tooltip } from 'components/Elements';
|
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 Dropdown from '../../../../components/Form/Settings/Dropdown/Dropdown';
|
||||||
|
import EventBus from 'utils/eventbus';
|
||||||
import { getProxiedImageUrl } from 'utils/marketplace';
|
import { getProxiedImageUrl } from 'utils/marketplace';
|
||||||
|
|
||||||
function filterItems(item, filter, categoryFilter) {
|
function filterItems(item, filter, categoryFilter) {
|
||||||
@@ -60,32 +63,92 @@ function ItemCard({
|
|||||||
isInstalled,
|
isInstalled,
|
||||||
isAdded,
|
isAdded,
|
||||||
onUninstall,
|
onUninstall,
|
||||||
|
onTogglePack,
|
||||||
|
showChips = true,
|
||||||
}) {
|
}) {
|
||||||
item._onCollection = onCollection;
|
item._onCollection = onCollection;
|
||||||
|
|
||||||
const isSideloaded = item.sideload === true;
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`item ${isSideloaded ? 'item-sideloaded' : ''}`}
|
className={`item ${isSideloaded ? 'item-sideloaded' : ''} ${!isEnabled && isAdded ? 'item-disabled' : ''}`}
|
||||||
onClick={isSideloaded ? undefined : () => toggleFunction(item)}
|
onClick={isSideloaded ? undefined : () => toggleFunction(item)}
|
||||||
key={item.name}
|
key={item.name}
|
||||||
>
|
>
|
||||||
{isAdded && onUninstall && (
|
{isAdded && onTogglePack && (
|
||||||
<Tooltip
|
<div
|
||||||
title={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
|
className="item-toggle-switch"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
style={{ position: 'absolute', top: '12px', right: '12px', zIndex: 3 }}
|
style={{ position: 'absolute', top: '12px', right: '12px', zIndex: 3 }}
|
||||||
>
|
>
|
||||||
<button
|
<label className="switch-track" style={{ cursor: 'pointer' }}>
|
||||||
className="item-uninstall-btn"
|
<input
|
||||||
onClick={(e) => {
|
type="checkbox"
|
||||||
e.stopPropagation();
|
checked={isEnabled}
|
||||||
onUninstall(item.type, item.name);
|
onChange={handleTogglePack}
|
||||||
}}
|
style={{ display: 'none' }}
|
||||||
>
|
/>
|
||||||
<MdClose />
|
<div
|
||||||
</button>
|
className={`switch-track ${isEnabled ? 'checked' : ''}`}
|
||||||
</Tooltip>
|
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 && (
|
{isSideloaded && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -128,18 +191,33 @@ function ItemCard({
|
|||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="card-chips">
|
{showChips && (
|
||||||
{item.type && (
|
<div className="card-chips">
|
||||||
<span className="card-type">
|
{item.type && (
|
||||||
{variables.getMessage('modals.main.marketplace.' + getTypeTranslationKey(item.type))}
|
<span className="card-type">
|
||||||
</span>
|
{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 && item.in_collections.length > 0 && !onCollection && (
|
||||||
{item.in_collections[0].display_name || item.in_collections[0].name}
|
<span className="card-collection">
|
||||||
</span>
|
{item.in_collections[0].display_name || item.in_collections[0].name}
|
||||||
)}
|
</span>
|
||||||
</div>
|
)}
|
||||||
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -156,7 +234,9 @@ function Items({
|
|||||||
onSortChange,
|
onSortChange,
|
||||||
isAdded = false,
|
isAdded = false,
|
||||||
onUninstall,
|
onUninstall,
|
||||||
|
onTogglePack,
|
||||||
viewType = 'grid',
|
viewType = 'grid',
|
||||||
|
showChips = true,
|
||||||
}) {
|
}) {
|
||||||
const [selectedCategory, setSelectedCategory] = useState('all');
|
const [selectedCategory, setSelectedCategory] = useState('all');
|
||||||
const [sortType, setSortType] = useState(localStorage.getItem('sortMarketplace') || 'a-z');
|
const [sortType, setSortType] = useState(localStorage.getItem('sortMarketplace') || 'a-z');
|
||||||
@@ -222,6 +302,8 @@ function Items({
|
|||||||
isInstalled={installedNames.has(item.name)}
|
isInstalled={installedNames.has(item.name)}
|
||||||
isAdded={isAdded}
|
isAdded={isAdded}
|
||||||
onUninstall={onUninstall}
|
onUninstall={onUninstall}
|
||||||
|
onTogglePack={onTogglePack}
|
||||||
|
showChips={showChips}
|
||||||
key={index}
|
key={index}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -114,9 +114,7 @@ export function useQuoteLoader(updateQuote) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter out incomplete quotes (empty quote text)
|
// Filter out incomplete quotes (empty quote text)
|
||||||
const validQuotes = customQuote?.filter(
|
const validQuotes = customQuote?.filter((q) => q.quote && q.quote.trim() !== '') || [];
|
||||||
(q) => q.quote && q.quote.trim() !== ''
|
|
||||||
) || [];
|
|
||||||
|
|
||||||
if (validQuotes.length === 0) {
|
if (validQuotes.length === 0) {
|
||||||
return { noQuote: true };
|
return { noQuote: true };
|
||||||
@@ -144,8 +142,14 @@ export function useQuoteLoader(updateQuote) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
|
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
|
||||||
|
const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}');
|
||||||
const quotePack = installed
|
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) =>
|
.flatMap((item) =>
|
||||||
item.quotes.map((quote) => ({
|
item.quotes.map((quote) => ({
|
||||||
...quote,
|
...quote,
|
||||||
|
|||||||
@@ -359,7 +359,9 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
|
|||||||
<>
|
<>
|
||||||
<Row final={true}>
|
<Row final={true}>
|
||||||
<Content
|
<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'}`}
|
subtitle={`${installedQuotePacks.length} ${installedQuotePacks.length === 1 ? 'pack' : 'packs'} • ${totalQuotes} ${totalQuotes === 1 ? 'quote' : 'quotes'}`}
|
||||||
/>
|
/>
|
||||||
<Action>
|
<Action>
|
||||||
@@ -378,7 +380,9 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
|
|||||||
toggleFunction={handleToggle}
|
toggleFunction={handleToggle}
|
||||||
showCreateYourOwn={false}
|
showCreateYourOwn={false}
|
||||||
onUninstall={handleUninstall}
|
onUninstall={handleUninstall}
|
||||||
|
onTogglePack={() => {}}
|
||||||
viewType="grid"
|
viewType="grid"
|
||||||
|
showChips={false}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -129,6 +129,14 @@ export function install(type, input, sideload, collection) {
|
|||||||
|
|
||||||
localStorage.setItem('installed', JSON.stringify(installed));
|
localStorage.setItem('installed', JSON.stringify(installed));
|
||||||
|
|
||||||
|
// Auto-enable pack on install
|
||||||
|
if (isNewInstall) {
|
||||||
|
const packId = input.id || input.name;
|
||||||
|
const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}');
|
||||||
|
enabledPacks[packId] = true;
|
||||||
|
localStorage.setItem('enabledPacks', JSON.stringify(enabledPacks));
|
||||||
|
}
|
||||||
|
|
||||||
// Track download for new installs (not re-installs)
|
// Track download for new installs (not re-installs)
|
||||||
if (isNewInstall && input.id) {
|
if (isNewInstall && input.id) {
|
||||||
trackDownload(input.id);
|
trackDownload(input.id);
|
||||||
|
|||||||
@@ -119,6 +119,16 @@ export function uninstall(type, name) {
|
|||||||
|
|
||||||
localStorage.setItem('installed', JSON.stringify(installed));
|
localStorage.setItem('installed', JSON.stringify(installed));
|
||||||
|
|
||||||
|
// Cleanup enabledPacks entry
|
||||||
|
const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}');
|
||||||
|
const installedItems = JSON.parse(localStorage.getItem('installed') || '[]');
|
||||||
|
const packToRemove = installedItems.find((item) => item.name === name);
|
||||||
|
if (packToRemove) {
|
||||||
|
const packId = packToRemove.id || packToRemove.name;
|
||||||
|
delete enabledPacks[packId];
|
||||||
|
localStorage.setItem('enabledPacks', JSON.stringify(enabledPacks));
|
||||||
|
}
|
||||||
|
|
||||||
// Emit refresh event after all data is saved
|
// Emit refresh event after all data is saved
|
||||||
if (refreshEvent) {
|
if (refreshEvent) {
|
||||||
EventBus.emit('refresh', refreshEvent);
|
EventBus.emit('refresh', refreshEvent);
|
||||||
|
|||||||
Reference in New Issue
Block a user