diff --git a/src/components/Elements/Button/Button.jsx b/src/components/Elements/Button/Button.jsx index ff717a87..7515d253 100644 --- a/src/components/Elements/Button/Button.jsx +++ b/src/components/Elements/Button/Button.jsx @@ -3,7 +3,7 @@ import Tooltip from 'components/Elements/Tooltip/Tooltip'; const Button = forwardRef( ( - { icon, label, type, iconPlacement, onClick, active, disabled, tooltipTitle, tooltipKey, href, style }, + { icon, label, type, iconPlacement, onClick, active, disabled, tooltipTitle, tooltipKey, href, style, badge }, ref, ) => { let className; @@ -46,16 +46,18 @@ const Button = forwardRef( ); const linkButton = ( - {icon} {label} + {badge && {badge}} ); @@ -73,6 +75,7 @@ const Button = forwardRef( > {icon} {label} + {badge && {badge}} ); diff --git a/src/components/Elements/MainModal/components/ModalNavbar.jsx b/src/components/Elements/MainModal/components/ModalNavbar.jsx deleted file mode 100644 index 42f9ea40..00000000 --- a/src/components/Elements/MainModal/components/ModalNavbar.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import variables from 'config/variables'; -import { Button } from 'components/Elements'; -import { NAVBAR_BUTTONS } from '../constants/tabConfig'; - -const ModalNavbar = ({ currentTab, onChangeTab }) => ( -
- {NAVBAR_BUTTONS.map(({ tab, icon: Icon, messageKey }) => ( -
-); - -export default ModalNavbar; diff --git a/src/components/Elements/MainModal/components/ModalTopBar.jsx b/src/components/Elements/MainModal/components/ModalTopBar.jsx index f98ef699..ac80676a 100644 --- a/src/components/Elements/MainModal/components/ModalTopBar.jsx +++ b/src/components/Elements/MainModal/components/ModalTopBar.jsx @@ -1,7 +1,8 @@ import { useT } from 'contexts/TranslationContext'; +import { useState, useEffect } from 'react'; import { MdClose, MdChevronRight, MdArrowBack, MdArrowForward } from 'react-icons/md'; import { Tooltip, Button } from 'components/Elements'; -import { NAVBAR_BUTTONS } from '../constants/tabConfig'; +import { NAVBAR_BUTTONS, TAB_TYPES } from '../constants/tabConfig'; import mueAboutIcon from 'assets/icons/mue_about.png'; // Map marketplace types to translation keys @@ -33,6 +34,38 @@ function ModalTopBar({ }) { const t = useT(); + // Track installed addons count for badge + const [installedCount, setInstalledCount] = useState(() => { + try { + const installed = JSON.parse(localStorage.getItem('installed')) || []; + return installed.length; + } catch (e) { + return 0; + } + }); + + useEffect(() => { + const updateCount = () => { + try { + const installed = JSON.parse(localStorage.getItem('installed')) || []; + setInstalledCount(installed.length); + } catch (e) { + setInstalledCount(0); + } + }; + + // Listen for storage events (changes from other tabs) + window.addEventListener('storage', updateCount); + + // Listen for custom event for same-tab updates + window.addEventListener('installedAddonsChanged', updateCount); + + return () => { + window.removeEventListener('storage', updateCount); + window.removeEventListener('installedAddonsChanged', updateCount); + }; + }, []); + // Get the current tab label const currentTabButton = NAVBAR_BUTTONS.find(({ tab }) => tab === currentTab); const currentTabLabel = currentTabButton ? t(currentTabButton.messageKey) : ''; @@ -204,16 +237,22 @@ function ModalTopBar({
- {NAVBAR_BUTTONS.map(({ tab, icon: Icon, messageKey }) => ( -
diff --git a/src/components/Elements/MainModal/components/index.js b/src/components/Elements/MainModal/components/index.js index 0181f1fc..26285485 100644 --- a/src/components/Elements/MainModal/components/index.js +++ b/src/components/Elements/MainModal/components/index.js @@ -1,3 +1,2 @@ export { default as ModalLoader } from './ModalLoader'; -export { default as ModalNavbar } from './ModalNavbar'; export { default as ReminderInfo } from './ReminderInfo'; diff --git a/src/components/Elements/MainModal/scss/marketplace/_main.scss b/src/components/Elements/MainModal/scss/marketplace/_main.scss index 6d6deea0..fa0c1071 100644 --- a/src/components/Elements/MainModal/scss/marketplace/_main.scss +++ b/src/components/Elements/MainModal/scss/marketplace/_main.scss @@ -46,6 +46,10 @@ border-radius: 12px; transition: 0.5s; + @include themed { + background-color: t($modal-sidebarActive); + } + &.item-icon-text { display: flex; align-items: center; @@ -439,3 +443,100 @@ p.author { } } } + +.view-toggle-buttons { + display: flex; + gap: 8px; + align-items: center; + + .view-toggle-btn { + all: unset; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + -webkit-user-select: none; + user-select: none; + + @include themed { + background-color: t($modal-sidebarActive); + color: t($subColor); + } + + svg { + font-size: 20px; + } + + &:hover { + @include themed { + background-color: rgba(255, 255, 255, 0.15); + color: t($color); + } + } + + &.active { + @include themed { + background-color: #fff; + color: #000; + } + + &:hover { + @include themed { + background-color: #f0f0f0; + } + } + } + + &:focus-visible { + outline: 2px solid rgba(255, 255, 255, 0.5); + outline-offset: 2px; + } + } +} + +.items-list { + display: flex !important; + flex-direction: column !important; + grid-template-columns: unset !important; + gap: 12px !important; + + .item { + flex-direction: row !important; + align-items: center !important; + padding: 1rem 1.5rem !important; + gap: 20px !important; + + &:hover { + transform: translate3d(5px, 0, 0) !important; + } + + .item-icon { + flex-shrink: 0; + } + + .card-details { + flex: 1; + flex-direction: row !important; + align-items: center !important; + justify-content: space-between; + gap: 15px; + + .card-title { + font-size: 16px; + } + + .card-subtitle { + font-size: 13px; + } + + .card-chips { + margin-top: 0 !important; + margin-left: auto; + } + } + } +} diff --git a/src/components/Elements/MainModal/scss/modules/_buttons.scss b/src/components/Elements/MainModal/scss/modules/_buttons.scss index 165d3900..004dc6d2 100644 --- a/src/components/Elements/MainModal/scss/modules/_buttons.scss +++ b/src/components/Elements/MainModal/scss/modules/_buttons.scss @@ -98,6 +98,51 @@ color: t($color); } } + + .btn-badge { + margin-left: 3px; + padding: 5px 7px; + border-radius: 6px; + font-size: 0.75em !important; + font-weight: 700; + line-height: 1; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + + @include themed { + background-color: t($modal-sidebarActive); + color: t($color); + } + + .light & { + background-color: rgba(0, 0, 0, 0.08); + color: rgba(0, 0, 0, 0.8); + } + + .dark & { + background-color: rgba(255, 255, 255, 0.12); + color: rgba(255, 255, 255, 0.9); + } + } + + &.btn-navigation-active .btn-badge { + @include themed { + background-color: rgba(0, 0, 0, 0.08); + color: rgba(0, 0, 0, 0.8); + } + + .light & { + background-color: rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.85); + } + + .dark & { + background-color: rgba(255, 255, 255, 0.15); + color: rgba(255, 255, 255, 1); + } + } } /* safari fix */ diff --git a/src/features/marketplace/components/Items/Items.jsx b/src/features/marketplace/components/Items/Items.jsx index ee1ef7f4..7e9f9e07 100644 --- a/src/features/marketplace/components/Items/Items.jsx +++ b/src/features/marketplace/components/Items/Items.jsx @@ -84,14 +84,14 @@ function ItemCard({ item, toggleFunction, type, onCollection, isCurator, isInsta {isSideloaded && (
)} - {isInstalled && item.colour && !isSideloaded && ( + {isInstalled && item.colour && !isSideloaded && !isAdded && (
@@ -148,6 +148,7 @@ function Items({ onSortChange, isAdded = false, onUninstall, + viewType = 'grid', }) { const [selectedCategory, setSelectedCategory] = useState('all'); const [sortType, setSortType] = useState(localStorage.getItem('sortMarketplace') || 'a-z'); @@ -200,7 +201,7 @@ function Items({ />
)} -
+
{items ?.filter((item) => filterItems(item, filter, filterOptions ? selectedCategory : 'all')) .map((item, index) => ( diff --git a/src/features/marketplace/components/hooks/useMarketplaceInstall.js b/src/features/marketplace/components/hooks/useMarketplaceInstall.js index 0696c441..79222209 100644 --- a/src/features/marketplace/components/hooks/useMarketplaceInstall.js +++ b/src/features/marketplace/components/hooks/useMarketplaceInstall.js @@ -14,6 +14,7 @@ export const useMarketplaceInstall = () => { toast(variables.getMessage('toasts.installed')); variables.stats.postEvent('marketplace-item', `${data.display_name || data.name} installed`); variables.stats.postEvent('marketplace', 'Install'); + window.dispatchEvent(new Event('installedAddonsChanged')); }; const uninstallItem = (type, name) => { @@ -21,6 +22,7 @@ export const useMarketplaceInstall = () => { toast(variables.getMessage('toasts.uninstalled')); variables.stats.postEvent('marketplace-item', `${name} uninstalled`); variables.stats.postEvent('marketplace', 'Uninstall'); + window.dispatchEvent(new Event('installedAddonsChanged')); }; const installCollection = async (items) => { @@ -51,6 +53,7 @@ export const useMarketplaceInstall = () => { } toast(variables.getMessage('toasts.installed')); + window.dispatchEvent(new Event('installedAddonsChanged')); window.location.reload(); } catch (error) { if (!controllerRef.current.signal.aborted) { diff --git a/src/features/marketplace/views/Added.jsx b/src/features/marketplace/views/Added.jsx index 7a210c78..cb5950d1 100644 --- a/src/features/marketplace/views/Added.jsx +++ b/src/features/marketplace/views/Added.jsx @@ -1,6 +1,6 @@ import variables from 'config/variables'; import { memo, useState, useEffect, useCallback } from 'react'; -import { MdUpdate, MdOutlineExtensionOff, MdSendTimeExtension, MdExplore } from 'react-icons/md'; +import { MdUpdate, MdOutlineExtensionOff, MdSendTimeExtension, MdExplore, MdViewModule, MdViewList } from 'react-icons/md'; import { toast } from 'react-toastify'; import Modal from 'react-modal'; @@ -17,6 +17,7 @@ const Added = memo(() => { const [installed, setInstalled] = useState(JSON.parse(localStorage.getItem('installed'))); const [showFailed, setShowFailed] = useState(false); const [failedReason, setFailedReason] = useState(''); + const [viewType, setViewType] = useState(localStorage.getItem('addonsViewType') || 'grid'); const installAddon = useCallback((input) => { let failedReasonText = ''; @@ -55,6 +56,7 @@ const Added = memo(() => { toast(variables.getMessage('toasts.installed')); variables.stats.postEvent('marketplace', 'Sideload'); setInstalled(JSON.parse(localStorage.getItem('installed'))); + window.dispatchEvent(new Event('installedAddonsChanged')); }, []); const getSideloadButton = useCallback(() => { @@ -149,12 +151,14 @@ const Added = memo(() => { localStorage.setItem('installed', JSON.stringify([])); toast(variables.getMessage('toasts.uninstalled_all')); setInstalled([]); + window.dispatchEvent(new Event('installedAddonsChanged')); }, [installed]); const handleUninstall = useCallback((type, name) => { uninstall(type, name); toast(variables.getMessage('toasts.uninstalled')); setInstalled(JSON.parse(localStorage.getItem('installed'))); + window.dispatchEvent(new Event('installedAddonsChanged')); }, []); useEffect(() => { @@ -192,6 +196,11 @@ const Added = memo(() => { window.dispatchEvent(event); }, []); + const toggleViewType = useCallback((type) => { + setViewType(type); + localStorage.setItem('addonsViewType', type); + }, []); + if (installed.length === 0) { return ( <> @@ -240,16 +249,34 @@ const Added = memo(() => { /> - sortAddons(value)} - items={[ - { value: 'newest', text: variables.getMessage('modals.main.addons.sort.newest') }, - { value: 'a-z', text: variables.getMessage('modals.main.addons.sort.a_z') }, - { value: 'recently-updated', text: 'Recently Updated' }, - ]} - /> +
+ sortAddons(value)} + items={[ + { value: 'newest', text: variables.getMessage('modals.main.addons.sort.newest') }, + { value: 'a-z', text: variables.getMessage('modals.main.addons.sort.a_z') }, + { value: 'recently-updated', text: 'Recently Updated' }, + ]} + /> +
+ + +
+
{ toggleFunction={(input) => toggle('item', input)} showCreateYourOwn={false} onUninstall={handleUninstall} + viewType={viewType} /> ); diff --git a/src/features/misc/modals/Modals.jsx b/src/features/misc/modals/Modals.jsx index 261e260d..8b746f35 100644 --- a/src/features/misc/modals/Modals.jsx +++ b/src/features/misc/modals/Modals.jsx @@ -40,6 +40,7 @@ const tryInstallDefaultPack = async () => { ); const { data } = await response.json(); install(data.type, data, false, true); + window.dispatchEvent(new Event('installedAddonsChanged')); return true; } catch (e) { console.error('Failed to install default pack:', e);