mirror of
https://github.com/mue/mue.git
synced 2026-06-08 14:10:42 +02:00
feat: new default quotes experience, improve added page
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
.items {
|
.items {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(250px, 280px));
|
||||||
grid-gap: 1.5rem;
|
grid-gap: 1.5rem;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
@@ -62,6 +62,20 @@
|
|||||||
width: 60px !important;
|
width: 60px !important;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
transition: 0.5s;
|
transition: 0.5s;
|
||||||
|
|
||||||
|
&.item-icon-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
@include themed {
|
||||||
|
background-color: t($modal-sidebarActive);
|
||||||
|
color: t($color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-details {
|
.card-details {
|
||||||
@@ -113,6 +127,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-uninstall-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(220, 50, 50, 0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item-installed-badge {
|
.item-installed-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
@@ -135,9 +171,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-sideload-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(100, 100, 100, 0.9);
|
||||||
|
cursor: help;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: white;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover .item-installed-badge {
|
&:hover .item-installed-badge {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.item-sideloaded {
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import variables from 'config/variables';
|
import variables from 'config/variables';
|
||||||
import React, { memo, useState, useMemo } from 'react';
|
import React, { memo, useState, useMemo } from 'react';
|
||||||
import { MdAutoFixHigh, MdOutlineArrowForward, MdOutlineOpenInNew, MdCheckCircle } from 'react-icons/md';
|
import {
|
||||||
|
MdAutoFixHigh,
|
||||||
|
MdOutlineArrowForward,
|
||||||
|
MdOutlineOpenInNew,
|
||||||
|
MdCheckCircle,
|
||||||
|
MdOutlineUploadFile,
|
||||||
|
MdClose,
|
||||||
|
} from 'react-icons/md';
|
||||||
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
|
import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
|
||||||
|
|
||||||
import { Button } from 'components/Elements';
|
import { Button, Tooltip } from 'components/Elements';
|
||||||
import Dropdown from '../../../../components/Form/Settings/Dropdown/Dropdown';
|
import Dropdown from '../../../../components/Form/Settings/Dropdown/Dropdown';
|
||||||
|
|
||||||
function filterItems(item, filter, categoryFilter) {
|
function filterItems(item, filter, categoryFilter) {
|
||||||
@@ -28,7 +35,29 @@ function filterItems(item, filter, categoryFilter) {
|
|||||||
return textMatch && item.type === categoryMap[categoryFilter];
|
return textMatch && item.type === categoryMap[categoryFilter];
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemCard({ item, toggleFunction, type, onCollection, isCurator, isInstalled }) {
|
function getInitials(name) {
|
||||||
|
if (!name) return '??';
|
||||||
|
const words = name.split(' ');
|
||||||
|
if (words.length === 1) {
|
||||||
|
return name.substring(0, 2).toUpperCase();
|
||||||
|
}
|
||||||
|
return words
|
||||||
|
.slice(0, 2)
|
||||||
|
.map((word) => word[0])
|
||||||
|
.join('')
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeTranslationKey(type) {
|
||||||
|
const typeMap = {
|
||||||
|
photos: 'photo_packs',
|
||||||
|
quotes: 'quote_packs',
|
||||||
|
settings: 'preset_settings',
|
||||||
|
};
|
||||||
|
return typeMap[type] || type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ItemCard({ item, toggleFunction, type, onCollection, isCurator, isInstalled, isAdded, onUninstall }) {
|
||||||
item._onCollection = onCollection;
|
item._onCollection = onCollection;
|
||||||
|
|
||||||
// Convert hex color to RGB for gradient with opacity
|
// Convert hex color to RGB for gradient with opacity
|
||||||
@@ -73,18 +102,47 @@ function ItemCard({ item, toggleFunction, type, onCollection, isCurator, isInsta
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSideloaded = item.sideload === true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="item"
|
className={`item ${isSideloaded ? 'item-sideloaded' : ''}`}
|
||||||
onClick={() => toggleFunction(item)}
|
onClick={isSideloaded ? undefined : () => toggleFunction(item)}
|
||||||
key={item.name}
|
key={item.name}
|
||||||
style={getGradientStyle()}
|
style={getGradientStyle()}
|
||||||
>
|
>
|
||||||
{isInstalled && item.colour && (
|
{isAdded && onUninstall && (
|
||||||
|
<Tooltip
|
||||||
|
title={variables.getMessage('modals.main.marketplace.product.buttons.remove')}
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
{isSideloaded && (
|
||||||
|
<Tooltip
|
||||||
|
title={variables.getMessage('modals.main.addons.sideload.title')}
|
||||||
|
style={{ position: 'absolute', top: '12px', right: '48px', zIndex: 2 }}
|
||||||
|
>
|
||||||
|
<div className="item-sideload-badge">
|
||||||
|
<MdOutlineUploadFile />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{isInstalled && item.colour && !isSideloaded && (
|
||||||
<div className="item-installed-badge" style={getBadgeStyle()}>
|
<div className="item-installed-badge" style={getBadgeStyle()}>
|
||||||
<MdCheckCircle />
|
<MdCheckCircle />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{item.icon_url ? (
|
||||||
<img
|
<img
|
||||||
className="item-icon"
|
className="item-icon"
|
||||||
alt="icon"
|
alt="icon"
|
||||||
@@ -95,6 +153,11 @@ function ItemCard({ item, toggleFunction, type, onCollection, isCurator, isInsta
|
|||||||
e.target.src = placeholderIcon;
|
e.target.src = placeholderIcon;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="item-icon item-icon-text">
|
||||||
|
{getInitials(item.display_name || item.name)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="card-details">
|
<div className="card-details">
|
||||||
<span className="card-title">{item.display_name || item.name}</span>
|
<span className="card-title">{item.display_name || item.name}</span>
|
||||||
{!isCurator ? (
|
{!isCurator ? (
|
||||||
@@ -106,17 +169,14 @@ function ItemCard({ item, toggleFunction, type, onCollection, isCurator, isInsta
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="card-chips">
|
<div className="card-chips">
|
||||||
{type === 'all' && !onCollection ? (
|
{item.type && (
|
||||||
<span className="card-type">
|
<span className="card-type">
|
||||||
{variables.getMessage('modals.main.marketplace.' + item.type)}
|
{variables.getMessage('modals.main.marketplace.' + getTypeTranslationKey(item.type))}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
)}
|
||||||
|
{item.in_collections && item.in_collections.length > 0 && !onCollection && (
|
||||||
{/* {item.in_collections && item.in_collections.length > 0 && !onCollection ? (
|
<span className="card-collection">{item.in_collections[0]}</span>
|
||||||
<span className="card-collection">
|
)}
|
||||||
{item.in_collections[0]}
|
|
||||||
</span>
|
|
||||||
) : null} */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,6 +196,8 @@ function Items({
|
|||||||
showCreateYourOwn,
|
showCreateYourOwn,
|
||||||
filterOptions = false,
|
filterOptions = false,
|
||||||
onSortChange,
|
onSortChange,
|
||||||
|
isAdded = false,
|
||||||
|
onUninstall,
|
||||||
}) {
|
}) {
|
||||||
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');
|
||||||
@@ -239,6 +301,8 @@ function Items({
|
|||||||
type={type}
|
type={type}
|
||||||
onCollection={onCollection}
|
onCollection={onCollection}
|
||||||
isInstalled={installedNames.has(item.name)}
|
isInstalled={installedNames.has(item.name)}
|
||||||
|
isAdded={isAdded}
|
||||||
|
onUninstall={onUninstall}
|
||||||
key={index}
|
key={index}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -89,22 +89,19 @@ const Added = memo(() => {
|
|||||||
case 'newest':
|
case 'newest':
|
||||||
installedItems.reverse();
|
installedItems.reverse();
|
||||||
break;
|
break;
|
||||||
case 'oldest':
|
|
||||||
break;
|
|
||||||
case 'a-z':
|
case 'a-z':
|
||||||
installedItems.sort((a, b) => {
|
installedItems.sort((a, b) => {
|
||||||
if (a.display_name < b.display_name) {
|
const nameA = (a.display_name || a.name || '').toLowerCase();
|
||||||
return -1;
|
const nameB = (b.display_name || b.name || '').toLowerCase();
|
||||||
}
|
return nameA.localeCompare(nameB);
|
||||||
if (a.display_name > b.display_name) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'z-a':
|
case 'recently-updated':
|
||||||
installedItems.sort();
|
installedItems.sort((a, b) => {
|
||||||
installedItems.reverse();
|
const dateA = a.updated_at ? new Date(a.updated_at) : new Date(0);
|
||||||
|
const dateB = b.updated_at ? new Date(b.updated_at) : new Date(0);
|
||||||
|
return dateB - dateA;
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -154,6 +151,12 @@ const Added = memo(() => {
|
|||||||
setInstalled([]);
|
setInstalled([]);
|
||||||
}, [installed]);
|
}, [installed]);
|
||||||
|
|
||||||
|
const handleUninstall = useCallback((type, name) => {
|
||||||
|
uninstall(type, name);
|
||||||
|
toast(variables.getMessage('toasts.uninstalled'));
|
||||||
|
setInstalled(JSON.parse(localStorage.getItem('installed')));
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sortAddons(localStorage.getItem('sortAddons'), false);
|
sortAddons(localStorage.getItem('sortAddons'), false);
|
||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
@@ -243,9 +246,8 @@ const Added = memo(() => {
|
|||||||
onChange={(value) => sortAddons(value)}
|
onChange={(value) => sortAddons(value)}
|
||||||
items={[
|
items={[
|
||||||
{ value: 'newest', text: variables.getMessage('modals.main.addons.sort.newest') },
|
{ value: 'newest', text: variables.getMessage('modals.main.addons.sort.newest') },
|
||||||
{ value: 'oldest', text: variables.getMessage('modals.main.addons.sort.oldest') },
|
|
||||||
{ value: 'a-z', text: variables.getMessage('modals.main.addons.sort.a_z') },
|
{ value: 'a-z', text: variables.getMessage('modals.main.addons.sort.a_z') },
|
||||||
{ value: 'z-a', text: variables.getMessage('modals.main.addons.sort.z_a') },
|
{ value: 'recently-updated', text: 'Recently Updated' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Items
|
<Items
|
||||||
@@ -254,6 +256,7 @@ const Added = memo(() => {
|
|||||||
filter=""
|
filter=""
|
||||||
toggleFunction={(input) => toggle('item', input)}
|
toggleFunction={(input) => toggle('item', input)}
|
||||||
showCreateYourOwn={false}
|
showCreateYourOwn={false}
|
||||||
|
onUninstall={handleUninstall}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,9 +8,45 @@ import Preview from '../../helpers/preview/Preview';
|
|||||||
|
|
||||||
import EventBus from 'utils/eventbus';
|
import EventBus from 'utils/eventbus';
|
||||||
import { parseDeepLink, shouldAutoOpenModal, updateHash } from 'utils/deepLinking';
|
import { parseDeepLink, shouldAutoOpenModal, updateHash } from 'utils/deepLinking';
|
||||||
|
import { install } from 'utils/marketplace';
|
||||||
|
|
||||||
import Welcome from 'features/welcome/Welcome';
|
import Welcome from 'features/welcome/Welcome';
|
||||||
|
|
||||||
|
const DEFAULT_PACK_ID = '0c8a5bdebd13';
|
||||||
|
|
||||||
|
const isDefaultPackInstalled = () => {
|
||||||
|
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
|
||||||
|
return installed.some((item) => item.id === DEFAULT_PACK_ID);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDefaultPackUninstalled = () => {
|
||||||
|
const uninstalledPacks = JSON.parse(localStorage.getItem('uninstalledPacks') || '[]');
|
||||||
|
return uninstalledPacks.includes(DEFAULT_PACK_ID);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tryInstallDefaultPack = async () => {
|
||||||
|
// Don't install if offline mode, already installed, or explicitly uninstalled
|
||||||
|
if (
|
||||||
|
localStorage.getItem('offlineMode') === 'true' ||
|
||||||
|
isDefaultPackInstalled() ||
|
||||||
|
isDefaultPackUninstalled()
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${variables.constants.API_URL}/marketplace/item/${DEFAULT_PACK_ID}`,
|
||||||
|
);
|
||||||
|
const { data } = await response.json();
|
||||||
|
install(data.type, data, false, true);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to install default pack:', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const Modals = () => {
|
const Modals = () => {
|
||||||
const [mainModal, setMainModal] = useState(false);
|
const [mainModal, setMainModal] = useState(false);
|
||||||
const [updateModal, setUpdateModal] = useState(false);
|
const [updateModal, setUpdateModal] = useState(false);
|
||||||
@@ -60,6 +96,15 @@ const Modals = () => {
|
|||||||
localStorage.setItem('showReminder', false);
|
localStorage.setItem('showReminder', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to install default pack if it wasn't installed during welcome (e.g., no internet)
|
||||||
|
if (localStorage.getItem('showWelcome') !== 'true') {
|
||||||
|
tryInstallDefaultPack().then((installed) => {
|
||||||
|
if (installed) {
|
||||||
|
EventBus.emit('refresh', 'quote');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for EventBus modal open requests
|
// Listen for EventBus modal open requests
|
||||||
const handleModalOpen = (data) => {
|
const handleModalOpen = (data) => {
|
||||||
if (data === 'openMainModal') {
|
if (data === 'openMainModal') {
|
||||||
@@ -76,9 +121,12 @@ const Modals = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeWelcome = () => {
|
const closeWelcome = async () => {
|
||||||
localStorage.setItem('showWelcome', false);
|
localStorage.setItem('showWelcome', false);
|
||||||
setWelcomeModal(false);
|
setWelcomeModal(false);
|
||||||
|
|
||||||
|
await tryInstallDefaultPack();
|
||||||
|
|
||||||
EventBus.emit('refresh', 'widgetsWelcomeDone');
|
EventBus.emit('refresh', 'widgetsWelcomeDone');
|
||||||
EventBus.emit('refresh', 'widgets');
|
EventBus.emit('refresh', 'widgets');
|
||||||
EventBus.emit('refresh', 'backgroundwelcome');
|
EventBus.emit('refresh', 'backgroundwelcome');
|
||||||
|
|||||||
@@ -83,7 +83,13 @@ export function useQuoteLoader(updateQuote) {
|
|||||||
|
|
||||||
const getQuote = useCallback(async () => {
|
const getQuote = useCallback(async () => {
|
||||||
const offline = localStorage.getItem('offlineMode') === 'true';
|
const offline = localStorage.getItem('offlineMode') === 'true';
|
||||||
const type = localStorage.getItem('quoteType') || 'api';
|
let type = localStorage.getItem('quoteType') || 'quote_pack';
|
||||||
|
|
||||||
|
// Migrate deprecated 'api' type to 'quote_pack'
|
||||||
|
if (type === 'api') {
|
||||||
|
type = 'quote_pack';
|
||||||
|
localStorage.setItem('quoteType', 'quote_pack');
|
||||||
|
}
|
||||||
|
|
||||||
// Check for favourite quote first
|
// Check for favourite quote first
|
||||||
const favouriteQuote = localStorage.getItem('favouriteQuote');
|
const favouriteQuote = localStorage.getItem('favouriteQuote');
|
||||||
@@ -128,7 +134,8 @@ export function useQuoteLoader(updateQuote) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'quote_pack': {
|
case 'quote_pack':
|
||||||
|
default: {
|
||||||
if (offline) return doOffline();
|
if (offline) return doOffline();
|
||||||
|
|
||||||
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
|
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
|
||||||
@@ -138,55 +145,30 @@ export function useQuoteLoader(updateQuote) {
|
|||||||
...quote,
|
...quote,
|
||||||
fallbackauthorimg: item.icon_url,
|
fallbackauthorimg: item.icon_url,
|
||||||
packName: item.display_name || item.name,
|
packName: item.display_name || item.name,
|
||||||
|
noAuthorImg: item.noAuthorImg || quote.noAuthorImg,
|
||||||
})));
|
})));
|
||||||
|
|
||||||
if (quotePack.length === 0) return doOffline();
|
if (quotePack.length === 0) return doOffline();
|
||||||
|
|
||||||
const data = quotePack[Math.floor(Math.random() * quotePack.length)];
|
const data = quotePack[Math.floor(Math.random() * quotePack.length)];
|
||||||
const hasAuthor = data.author && data.author.trim() !== '';
|
const hasAuthor = data.author && data.author.trim() !== '';
|
||||||
|
const displayAuthor = hasAuthor ? data.author : data.packName;
|
||||||
|
|
||||||
|
// Try to get author image from Wikipedia unless pack disables it
|
||||||
|
let authorimgdata = { authorimg: data.fallbackauthorimg, authorimglicense: null };
|
||||||
|
if (hasAuthor && !data.noAuthorImg) {
|
||||||
|
const wikiImg = await getAuthorImg(data.author);
|
||||||
|
if (wikiImg.authorimg) {
|
||||||
|
authorimgdata = wikiImg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return updateQuote({
|
return updateQuote({
|
||||||
quote: `"${data.quote}"`,
|
quote: `"${data.quote}"`,
|
||||||
author: hasAuthor ? data.author : data.packName,
|
author: displayAuthor,
|
||||||
authorlink: hasAuthor ? getAuthorLink(data.author) : null,
|
authorlink: hasAuthor ? getAuthorLink(data.author) : null,
|
||||||
authorimg: data.fallbackauthorimg,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'api': {
|
|
||||||
if (offline) return doOffline();
|
|
||||||
|
|
||||||
const fetchAPIQuote = async () => {
|
|
||||||
const response = await fetch(
|
|
||||||
`${variables.constants.API_URL}/quotes/random`
|
|
||||||
).then(res => res.json());
|
|
||||||
|
|
||||||
if (response.statusCode === 429) return null;
|
|
||||||
|
|
||||||
const authorimgdata = await getAuthorImg(response.author);
|
|
||||||
return {
|
|
||||||
quote: `"${response.quote.replace(/\s+$/g, '')}"`,
|
|
||||||
author: response.author,
|
|
||||||
authorlink: getAuthorLink(response.author),
|
|
||||||
...authorimgdata,
|
...authorimgdata,
|
||||||
authorOccupation: response.author_occupation,
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(localStorage.getItem('nextQuote')) || await fetchAPIQuote();
|
|
||||||
localStorage.setItem('nextQuote', null);
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
updateQuote(data);
|
|
||||||
localStorage.setItem('currentQuote', JSON.stringify(data));
|
|
||||||
localStorage.setItem('nextQuote', JSON.stringify(await fetchAPIQuote()));
|
|
||||||
} else {
|
|
||||||
doOffline();
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
doOffline();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [updateQuote, getAuthorLink, getAuthorImg, doOffline]);
|
}, [updateQuote, getAuthorLink, getAuthorImg, doOffline]);
|
||||||
|
|||||||
@@ -23,7 +23,15 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [quoteType, setQuoteType] = useState(localStorage.getItem('quoteType') || 'api');
|
const [quoteType, setQuoteType] = useState(() => {
|
||||||
|
let type = localStorage.getItem('quoteType') || 'quote_pack';
|
||||||
|
// Migrate deprecated 'api' type to 'quote_pack'
|
||||||
|
if (type === 'api') {
|
||||||
|
type = 'quote_pack';
|
||||||
|
localStorage.setItem('quoteType', 'quote_pack');
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
});
|
||||||
const [customQuote, setCustomQuote] = useState(getCustom());
|
const [customQuote, setCustomQuote] = useState(getCustom());
|
||||||
|
|
||||||
const handleCustomQuote = (e, text, index, type) => {
|
const handleCustomQuote = (e, text, index, type) => {
|
||||||
@@ -93,10 +101,6 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
|
|||||||
value: 'quote_pack',
|
value: 'quote_pack',
|
||||||
text: variables.getMessage('modals.main.marketplace.title'),
|
text: variables.getMessage('modals.main.marketplace.title'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
value: 'api',
|
|
||||||
text: variables.getMessage('modals.main.settings.sections.background.type.api'),
|
|
||||||
},
|
|
||||||
{ value: 'custom', text: variables.getMessage(`${QUOTE_SECTION}.custom`) },
|
{ value: 'custom', text: variables.getMessage(`${QUOTE_SECTION}.custom`) },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -599,10 +599,9 @@
|
|||||||
},
|
},
|
||||||
"sort": {
|
"sort": {
|
||||||
"title": "Sort",
|
"title": "Sort",
|
||||||
"newest": "Installed (Newest)",
|
"newest": "Recently Added",
|
||||||
"oldest": "Installed (Oldest)",
|
"a_z": "Name (A-Z)",
|
||||||
"a_z": "Alphabetical (A-Z)",
|
"recently_updated": "Recently Updated"
|
||||||
"z_a": "Alphabetical (Z-A)"
|
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Create",
|
"title": "Create",
|
||||||
|
|||||||
@@ -209,7 +209,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "quoteType",
|
"name": "quoteType",
|
||||||
"value": "api"
|
"value": "quote_pack"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "backgroundFilter",
|
"name": "backgroundFilter",
|
||||||
|
|||||||
@@ -76,6 +76,14 @@ export function uninstall(type, name) {
|
|||||||
const installed = JSON.parse(localStorage.getItem('installed'));
|
const installed = JSON.parse(localStorage.getItem('installed'));
|
||||||
for (let i = 0; i < installed.length; i++) {
|
for (let i = 0; i < installed.length; i++) {
|
||||||
if (installed[i].name === name) {
|
if (installed[i].name === name) {
|
||||||
|
// Track uninstalled pack IDs to prevent auto-reinstall
|
||||||
|
if (installed[i].id) {
|
||||||
|
const uninstalledPacks = JSON.parse(localStorage.getItem('uninstalledPacks') || '[]');
|
||||||
|
if (!uninstalledPacks.includes(installed[i].id)) {
|
||||||
|
uninstalledPacks.push(installed[i].id);
|
||||||
|
localStorage.setItem('uninstalledPacks', JSON.stringify(uninstalledPacks));
|
||||||
|
}
|
||||||
|
}
|
||||||
installed.splice(i, 1);
|
installed.splice(i, 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user