mirror of
https://github.com/mue/mue.git
synced 2026-07-07 14:17:09 +02:00
feat(MainModal): enhance deep linking and navigation handling for collections and products
fix(Tabs): improve sidebar visibility logic and margin adjustment feat(ModalTopBar): update breadcrumb logic for collection and product views feat(Added): add button to navigate to discover page with deep linking fix(Browse): refine error handling and update hash for collection navigation
This commit is contained in:
@@ -5,7 +5,7 @@ import './scss/index.scss';
|
||||
import ModalLoader from './components/ModalLoader';
|
||||
import ModalTopBar from './components/ModalTopBar';
|
||||
import { TAB_TYPES } from './constants/tabConfig';
|
||||
import { updateHash, onHashChange } from 'utils/deepLinking';
|
||||
import { updateHash, parseDeepLink } from 'utils/deepLinking';
|
||||
|
||||
const Settings = lazy(() => import('../../../features/misc/views/Settings'));
|
||||
const Library = lazy(() => import('../../../features/misc/views/Library'));
|
||||
@@ -25,58 +25,88 @@ function MainModal({ modalClose, deepLinkData }) {
|
||||
const [currentSection, setCurrentSection] = useState('');
|
||||
const [productView, setProductView] = useState(null);
|
||||
const [resetDiscoverToAll, setResetDiscoverToAll] = useState(false);
|
||||
const [navigationHistory, setNavigationHistory] = useState([]);
|
||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||
const [navigationTrigger, setNavigationTrigger] = useState(null);
|
||||
const [skipNextHistoryAdd, setSkipNextHistoryAdd] = useState(0);
|
||||
|
||||
// Clear product view when changing tabs
|
||||
useEffect(() => {
|
||||
setProductView(null);
|
||||
}, [currentTab]);
|
||||
|
||||
// Clear hash when modal closes
|
||||
useEffect(() => {
|
||||
// Listen for hash changes while modal is open
|
||||
const cleanup = onHashChange((linkData) => {
|
||||
if (linkData && linkData.tab !== currentTab) {
|
||||
setCurrentTab(linkData.tab);
|
||||
return () => {
|
||||
// When modal unmounts, clear the hash
|
||||
if (window.location.hash) {
|
||||
window.history.replaceState(null, null, window.location.pathname);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
return cleanup;
|
||||
useEffect(() => {
|
||||
// Listen for browser back/forward navigation via popstate
|
||||
const handlePopState = () => {
|
||||
const linkData = window.location.hash ? parseDeepLink(window.location.hash) : null;
|
||||
|
||||
if (linkData) {
|
||||
// Update tab if different
|
||||
if (linkData.tab && linkData.tab !== currentTab) {
|
||||
setCurrentTab(linkData.tab);
|
||||
}
|
||||
|
||||
// Handle product and collection navigation
|
||||
if (linkData.itemId && linkData.collection && linkData.fromCollection) {
|
||||
// Product viewed from within a collection
|
||||
// First set collection state, then navigate to product
|
||||
setNavigationTrigger({
|
||||
type: 'collection',
|
||||
data: linkData.collection,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
// Small delay to ensure collection state is set before navigating to product
|
||||
setTimeout(() => {
|
||||
setNavigationTrigger({
|
||||
type: 'product',
|
||||
data: {
|
||||
id: linkData.itemId,
|
||||
type: linkData.category,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}, 100);
|
||||
} else if (linkData.itemId) {
|
||||
// Product navigation (standalone)
|
||||
setNavigationTrigger({
|
||||
type: 'product',
|
||||
data: {
|
||||
id: linkData.itemId,
|
||||
type: linkData.category,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} else if (linkData.collection) {
|
||||
// Collection page navigation
|
||||
setNavigationTrigger({
|
||||
type: 'collection',
|
||||
data: linkData.collection,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} else {
|
||||
// Back to main view (clear collection state)
|
||||
setProductView(null);
|
||||
setNavigationTrigger({
|
||||
type: 'main',
|
||||
data: { clearCollection: true },
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
}, [currentTab]);
|
||||
|
||||
const addToHistory = (state) => {
|
||||
// Check if this state is different from the current one
|
||||
const currentState = navigationHistory[historyIndex];
|
||||
const isDifferent = !currentState ||
|
||||
currentState.tab !== state.tab ||
|
||||
currentState.section !== state.section ||
|
||||
JSON.stringify(currentState.product) !== JSON.stringify(state.product);
|
||||
|
||||
if (isDifferent) {
|
||||
const newHistory = navigationHistory.slice(0, historyIndex + 1);
|
||||
newHistory.push(state);
|
||||
setNavigationHistory(newHistory);
|
||||
setHistoryIndex(newHistory.length - 1);
|
||||
console.log('Added to history:', state, 'New history:', newHistory);
|
||||
} else {
|
||||
console.log('Skipping duplicate history entry:', state);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeTab = (newTab) => {
|
||||
// Only add to history if not navigating via history
|
||||
if (skipNextHistoryAdd === 0) {
|
||||
addToHistory({
|
||||
tab: newTab,
|
||||
section: '',
|
||||
product: null,
|
||||
});
|
||||
} else {
|
||||
setSkipNextHistoryAdd(0); // Reset skip counter
|
||||
}
|
||||
|
||||
setCurrentTab(newTab);
|
||||
// Update URL hash when tab changes
|
||||
if (newTab === TAB_TYPES.DISCOVER) {
|
||||
@@ -112,23 +142,8 @@ function MainModal({ modalClose, deepLinkData }) {
|
||||
|
||||
const handleProductView = (product) => {
|
||||
setProductView(product);
|
||||
|
||||
// Add to navigation history only if not skipping
|
||||
// Store only essential product info, not full object
|
||||
if (product && skipNextHistoryAdd === 0) {
|
||||
addToHistory({
|
||||
tab: currentTab,
|
||||
section: currentSection,
|
||||
product: {
|
||||
type: product.type,
|
||||
name: product.name,
|
||||
id: product.id,
|
||||
},
|
||||
});
|
||||
} else if (skipNextHistoryAdd > 0) {
|
||||
console.log('Skipping product view history add');
|
||||
setSkipNextHistoryAdd(0); // Reset after use
|
||||
}
|
||||
// URL hash is already updated by child components (Browse.jsx)
|
||||
// Browser history automatically tracks hash changes
|
||||
};
|
||||
|
||||
const handleResetDiscoverToAll = () => {
|
||||
@@ -137,115 +152,17 @@ function MainModal({ modalClose, deepLinkData }) {
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (historyIndex > 0) {
|
||||
const newIndex = historyIndex - 1;
|
||||
const previousState = navigationHistory[newIndex];
|
||||
console.log('Going back to:', previousState);
|
||||
|
||||
setHistoryIndex(newIndex);
|
||||
|
||||
// Set skip flag BEFORE changing any state
|
||||
setSkipNextHistoryAdd(1);
|
||||
|
||||
// Change tab if different
|
||||
if (previousState.tab !== currentTab) {
|
||||
setCurrentTab(previousState.tab);
|
||||
}
|
||||
|
||||
setCurrentSection(previousState.section);
|
||||
setProductView(previousState.product);
|
||||
|
||||
// Trigger navigation in child components
|
||||
if (previousState.product) {
|
||||
// Viewing a product
|
||||
setNavigationTrigger({
|
||||
type: 'product',
|
||||
data: previousState.product,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
updateHash(`#${previousState.tab}/${previousState.product.type}/${previousState.product.id}`);
|
||||
} else {
|
||||
// Viewing main view
|
||||
setNavigationTrigger({
|
||||
type: 'main',
|
||||
data: null,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
if (previousState.tab === TAB_TYPES.DISCOVER) {
|
||||
const sectionMap = {
|
||||
[variables.getMessage('modals.main.marketplace.all')]: 'all',
|
||||
[variables.getMessage('modals.main.marketplace.photo_packs')]: 'photo_packs',
|
||||
[variables.getMessage('modals.main.marketplace.quote_packs')]: 'quote_packs',
|
||||
[variables.getMessage('modals.main.marketplace.preset_settings')]: 'preset_settings',
|
||||
[variables.getMessage('modals.main.marketplace.collections')]: 'collections',
|
||||
};
|
||||
const sectionKey = sectionMap[previousState.section] || 'all';
|
||||
updateHash(`#${previousState.tab}/${sectionKey}`);
|
||||
} else if (previousState.tab === TAB_TYPES.LIBRARY) {
|
||||
updateHash(`#${previousState.tab}/added`);
|
||||
} else {
|
||||
updateHash(`#${previousState.tab}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
const handleForward = () => {
|
||||
if (historyIndex < navigationHistory.length - 1) {
|
||||
const newIndex = historyIndex + 1;
|
||||
const nextState = navigationHistory[newIndex];
|
||||
console.log('Going forward to:', nextState);
|
||||
|
||||
setHistoryIndex(newIndex);
|
||||
|
||||
// Set skip flag BEFORE changing any state
|
||||
setSkipNextHistoryAdd(1);
|
||||
|
||||
// Change tab if different
|
||||
if (nextState.tab !== currentTab) {
|
||||
setCurrentTab(nextState.tab);
|
||||
}
|
||||
|
||||
setCurrentSection(nextState.section);
|
||||
setProductView(nextState.product);
|
||||
|
||||
// Trigger navigation in child components
|
||||
if (nextState.product) {
|
||||
setNavigationTrigger({
|
||||
type: 'product',
|
||||
data: nextState.product,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
updateHash(`#${nextState.tab}/${nextState.product.type}/${nextState.product.id}`);
|
||||
} else {
|
||||
setNavigationTrigger({
|
||||
type: 'main',
|
||||
data: null,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
if (nextState.tab === TAB_TYPES.DISCOVER) {
|
||||
const sectionMap = {
|
||||
[variables.getMessage('modals.main.marketplace.all')]: 'all',
|
||||
[variables.getMessage('modals.main.marketplace.photo_packs')]: 'photo_packs',
|
||||
[variables.getMessage('modals.main.marketplace.quote_packs')]: 'quote_packs',
|
||||
[variables.getMessage('modals.main.marketplace.preset_settings')]: 'preset_settings',
|
||||
[variables.getMessage('modals.main.marketplace.collections')]: 'collections',
|
||||
};
|
||||
const sectionKey = sectionMap[nextState.section] || 'all';
|
||||
updateHash(`#${nextState.tab}/${sectionKey}`);
|
||||
} else if (nextState.tab === TAB_TYPES.LIBRARY) {
|
||||
updateHash(`#${nextState.tab}/added`);
|
||||
} else {
|
||||
updateHash(`#${nextState.tab}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
window.history.forward();
|
||||
};
|
||||
|
||||
const canGoBack = historyIndex > 0;
|
||||
const canGoForward = historyIndex < navigationHistory.length - 1;
|
||||
// Browser manages history state, so we always show buttons enabled
|
||||
// Browser will handle whether there's actually history to go back/forward
|
||||
const canGoBack = true;
|
||||
const canGoForward = true;
|
||||
|
||||
const TabComponent = TAB_COMPONENTS[currentTab] || Settings;
|
||||
|
||||
@@ -264,6 +181,7 @@ function MainModal({ modalClose, deepLinkData }) {
|
||||
/>
|
||||
<Suspense fallback={<ModalLoader />}>
|
||||
<TabComponent
|
||||
key={currentTab}
|
||||
changeTab={handleChangeTab}
|
||||
deepLinkData={deepLinkData}
|
||||
currentTab={currentTab}
|
||||
|
||||
@@ -52,7 +52,7 @@ const Tabs = ({ children, navbar = false, currentTab: activeTab, onSectionChange
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', width: '100%', minHeight: '100%' }}>
|
||||
{showSidebar && (
|
||||
{showSidebar ? (
|
||||
<div className="modalSidebar">
|
||||
{children.map((tab, index) => (
|
||||
<Tab
|
||||
@@ -65,8 +65,8 @@ const Tabs = ({ children, navbar = false, currentTab: activeTab, onSectionChange
|
||||
))}
|
||||
<ReminderInfo isVisible={showReminder} onHide={handleHideReminder} />
|
||||
</div>
|
||||
)}
|
||||
<div className="modalTabContent">
|
||||
) : null}
|
||||
<div className="modalTabContent" style={{ marginLeft: showSidebar ? '1rem' : '0' }}>
|
||||
{children.map((tab, index) => {
|
||||
if (tab.props.label !== currentTab) {
|
||||
return null;
|
||||
|
||||
@@ -43,18 +43,43 @@ function ModalTopBar({
|
||||
});
|
||||
|
||||
if (productView) {
|
||||
// Show: Discover > Category > Product
|
||||
const categoryKey = MARKETPLACE_TYPE_TO_KEY[productView.type];
|
||||
if (categoryKey) {
|
||||
console.log('ModalTopBar productView:', productView);
|
||||
console.log('fromCollection:', productView.fromCollection, 'isCollection:', productView.isCollection, 'collectionTitle:', productView.collectionTitle);
|
||||
|
||||
// If viewing a collection page itself (not a product within it)
|
||||
if (productView.isCollection) {
|
||||
// Show: Discover > Collection Name
|
||||
breadcrumbPath.push({
|
||||
label: variables.getMessage(categoryKey),
|
||||
onClick: productView.onBack || null,
|
||||
label: productView.collectionTitle || productView.name,
|
||||
onClick: null, // Current page - not clickable
|
||||
});
|
||||
} else {
|
||||
// Viewing a product
|
||||
// Show: Discover > Collection/Category > Product
|
||||
if (productView.fromCollection && productView.collectionTitle) {
|
||||
console.log('Showing collection breadcrumb:', productView.collectionTitle);
|
||||
// If from a collection, show collection name
|
||||
breadcrumbPath.push({
|
||||
label: productView.collectionTitle,
|
||||
onClick: productView.onBack || null,
|
||||
});
|
||||
} else {
|
||||
console.log('Showing category breadcrumb');
|
||||
// Otherwise show category
|
||||
const categoryKey = MARKETPLACE_TYPE_TO_KEY[productView.type];
|
||||
if (categoryKey) {
|
||||
breadcrumbPath.push({
|
||||
label: variables.getMessage(categoryKey),
|
||||
onClick: productView.onBack || null,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Add product name as final breadcrumb
|
||||
breadcrumbPath.push({
|
||||
label: productView.name,
|
||||
onClick: null, // Current item - not clickable
|
||||
});
|
||||
}
|
||||
breadcrumbPath.push({
|
||||
label: productView.name,
|
||||
onClick: null, // Current item - not clickable
|
||||
});
|
||||
} else if (currentSection) {
|
||||
// Show: Tab > Section
|
||||
breadcrumbPath.push({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import variables from 'config/variables';
|
||||
import { memo, useState, useEffect, useCallback } from 'react';
|
||||
import { MdUpdate, MdOutlineExtensionOff, MdSendTimeExtension } from 'react-icons/md';
|
||||
import { MdUpdate, MdOutlineExtensionOff, MdSendTimeExtension, MdExplore } from 'react-icons/md';
|
||||
import { toast } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Header, CustomActions } from 'components/Layout/Settings';
|
||||
import { Button } from 'components/Elements';
|
||||
|
||||
import { install, uninstall, urlParser } from 'utils/marketplace';
|
||||
import { updateHash } from 'utils/deepLinking';
|
||||
|
||||
const Added = memo(() => {
|
||||
const [installed, setInstalled] = useState(JSON.parse(localStorage.getItem('installed')));
|
||||
@@ -203,6 +204,13 @@ const Added = memo(() => {
|
||||
</>
|
||||
);
|
||||
|
||||
const goToDiscover = useCallback(() => {
|
||||
updateHash('#discover/all');
|
||||
// Trigger a popstate event to update the UI
|
||||
const event = new window.Event('popstate');
|
||||
window.dispatchEvent(event);
|
||||
}, []);
|
||||
|
||||
if (installed.length === 0) {
|
||||
return (
|
||||
<>
|
||||
@@ -219,6 +227,12 @@ const Added = memo(() => {
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.addons.empty.description')}
|
||||
</span>
|
||||
<Button
|
||||
type="collection"
|
||||
onClick={goToDiscover}
|
||||
icon={<MdExplore />}
|
||||
label="Get Some"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
|
||||
import ItemPage from './ItemPage';
|
||||
import Items from '../components/Items/Items';
|
||||
import { Header } from 'components/Layout/Settings';
|
||||
import { Button } from 'components/Elements';
|
||||
|
||||
import { install, urlParser, uninstall } from 'utils/marketplace';
|
||||
@@ -99,20 +98,28 @@ class Marketplace extends PureComponent {
|
||||
// Error caught but only used for flow control
|
||||
if (this.controller.signal.aborted === false) {
|
||||
console.error('Failed to fetch item:', error);
|
||||
return toast(variables.getMessage('toasts.error'));
|
||||
toast(variables.getMessage('toasts.error'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify we have valid data before proceeding
|
||||
if (!info || !info.data) {
|
||||
console.error('Invalid item data received:', info);
|
||||
toast(variables.getMessage('toasts.error'));
|
||||
return;
|
||||
}
|
||||
|
||||
// check if already installed
|
||||
let button = this.buttons.install;
|
||||
let addonInstalled = false;
|
||||
let addonInstalledVersion;
|
||||
|
||||
const installed = JSON.parse(localStorage.getItem('installed'));
|
||||
const installed = JSON.parse(localStorage.getItem('installed')) || [];
|
||||
|
||||
if (installed.some((item) => item.name === info.data.name)) {
|
||||
button = this.buttons.uninstall;
|
||||
@@ -146,24 +153,49 @@ class Marketplace extends PureComponent {
|
||||
|
||||
// Update URL hash with item ID for deep linking
|
||||
if (info.data?.id) {
|
||||
updateHash(`#discover/${info.data.type}/${info.data.id}`);
|
||||
// If viewing from a collection, include collection in URL
|
||||
if (this.state.collection && this.state.collectionName) {
|
||||
updateHash(`#discover/collection/${this.state.collectionName}/${info.data.id}`);
|
||||
} else {
|
||||
updateHash(`#discover/${info.data.type}/${info.data.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify parent about product view for breadcrumbs
|
||||
if (this.props.onProductView) {
|
||||
this.props.onProductView({
|
||||
const productViewData = {
|
||||
id: info.data.id,
|
||||
type: info.data.type,
|
||||
name: info.data.display_name || info.data.name,
|
||||
fromCollection: this.state.collection,
|
||||
collectionName: this.state.collectionName,
|
||||
collectionTitle: this.state.collectionTitle,
|
||||
collectionDescription: this.state.collectionDescription,
|
||||
collectionImg: this.state.collectionImg,
|
||||
onBack: () => this.toggle('main'),
|
||||
onBackToAll: () => {
|
||||
this.toggle('main');
|
||||
updateHash('#discover/all');
|
||||
if (this.props.onResetToAll) {
|
||||
this.props.onResetToAll();
|
||||
}
|
||||
// Clear collection state before toggling to main
|
||||
this.setState(
|
||||
{
|
||||
collection: false,
|
||||
collectionName: null,
|
||||
collectionTitle: null,
|
||||
collectionDescription: null,
|
||||
collectionImg: null,
|
||||
},
|
||||
() => {
|
||||
// Call after state is updated
|
||||
updateHash('#discover/all');
|
||||
this.toggle('main');
|
||||
if (this.props.onResetToAll) {
|
||||
this.props.onResetToAll();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
console.log('Product view data being sent to parent:', productViewData);
|
||||
this.props.onProductView(productViewData);
|
||||
}
|
||||
|
||||
document.querySelector('#modal').scrollTop = 0;
|
||||
@@ -177,21 +209,82 @@ class Marketplace extends PureComponent {
|
||||
})
|
||||
).json();
|
||||
|
||||
this.setState({
|
||||
const collectionState = {
|
||||
items: collection.data.items,
|
||||
collectionName: data, // Store the collection name/slug for navigation
|
||||
collectionTitle: collection.data.display_name,
|
||||
collectionDescription: collection.data.description,
|
||||
collectionImg: collection.data.img,
|
||||
collection: true,
|
||||
done: true,
|
||||
});
|
||||
};
|
||||
console.log('Setting collection state:', collectionState);
|
||||
this.setState(collectionState);
|
||||
|
||||
// Update hash for collection deep linking
|
||||
updateHash(`#discover/collection/${data}`);
|
||||
|
||||
// Notify parent about collection view for breadcrumbs
|
||||
if (this.props.onProductView) {
|
||||
const collectionViewData = {
|
||||
id: data,
|
||||
type: 'collection',
|
||||
name: collection.data.display_name,
|
||||
isCollection: true,
|
||||
fromCollection: false,
|
||||
collectionName: data,
|
||||
collectionTitle: collection.data.display_name,
|
||||
onBack: () => {
|
||||
this.setState(
|
||||
{
|
||||
collection: false,
|
||||
collectionName: null,
|
||||
collectionTitle: null,
|
||||
collectionDescription: null,
|
||||
collectionImg: null,
|
||||
},
|
||||
() => {
|
||||
// Call toggle after state is updated
|
||||
updateHash('#discover/all');
|
||||
this.toggle('main');
|
||||
},
|
||||
);
|
||||
},
|
||||
onBackToAll: () => {
|
||||
// Clear collection state and return to main view
|
||||
this.setState(
|
||||
{
|
||||
collection: false,
|
||||
collectionName: null,
|
||||
collectionTitle: null,
|
||||
collectionDescription: null,
|
||||
collectionImg: null,
|
||||
},
|
||||
() => {
|
||||
// Call toggle after state is updated
|
||||
updateHash('#discover/all');
|
||||
this.toggle('main');
|
||||
if (this.props.onResetToAll) {
|
||||
this.props.onResetToAll();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
console.log('Collection view data being sent to parent:', collectionViewData);
|
||||
this.props.onProductView(collectionViewData);
|
||||
}
|
||||
} else {
|
||||
this.setState({ item: {}, relatedItems: [] });
|
||||
// Clear hash when returning to main view
|
||||
updateHash(`#discover/${this.props.type}`);
|
||||
|
||||
// Update hash based on current view state
|
||||
if (this.state.collection && this.state.collectionName) {
|
||||
// If returning to a collection view, preserve collection hash
|
||||
updateHash(`#discover/collection/${this.state.collectionName}`);
|
||||
} else {
|
||||
// Clear hash when returning to main view
|
||||
updateHash(`#discover/${this.props.type}`);
|
||||
}
|
||||
|
||||
// Clear product view for breadcrumbs
|
||||
if (this.props.onProductView) {
|
||||
@@ -325,7 +418,15 @@ class Marketplace extends PureComponent {
|
||||
}
|
||||
|
||||
returnToMain() {
|
||||
this.setState({ items: this.state.oldItems, collection: false });
|
||||
this.setState({
|
||||
items: this.state.oldItems,
|
||||
collection: false,
|
||||
collectionName: null,
|
||||
collectionTitle: null,
|
||||
collectionDescription: null,
|
||||
collectionImg: null,
|
||||
});
|
||||
updateHash(`#discover/${this.props.type}`);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -363,9 +464,28 @@ class Marketplace extends PureComponent {
|
||||
if (type === 'product' && data) {
|
||||
// Navigate to product
|
||||
this.toggle('item', { id: data.id, type: data.type });
|
||||
} else if (type === 'collection' && data) {
|
||||
// Navigate to collection
|
||||
this.toggle('collection', data);
|
||||
} else if (type === 'main' || type === 'section') {
|
||||
// Navigate to main view (browse/listing page)
|
||||
this.toggle('main');
|
||||
// Check if we need to clear collection state first
|
||||
if (data && data.clearCollection && this.state.collection) {
|
||||
this.setState(
|
||||
{
|
||||
collection: false,
|
||||
collectionName: null,
|
||||
collectionTitle: null,
|
||||
collectionDescription: null,
|
||||
collectionImg: null,
|
||||
},
|
||||
() => {
|
||||
this.toggle('main');
|
||||
},
|
||||
);
|
||||
} else {
|
||||
this.toggle('main');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,12 +565,6 @@ class Marketplace extends PureComponent {
|
||||
<>
|
||||
{this.state.collection === true ? (
|
||||
<>
|
||||
<Header
|
||||
title={variables.getMessage('modals.main.navbar.marketplace')}
|
||||
secondaryTitle={this.state.collectionTitle}
|
||||
report={false}
|
||||
goBack={() => this.returnToMain()}
|
||||
/>
|
||||
<div
|
||||
className="collectionPage"
|
||||
style={{
|
||||
|
||||
@@ -10,14 +10,16 @@ import { TAB_TYPES } from '../components/Elements/MainModal/constants/tabConfig'
|
||||
/**
|
||||
* Parse hash from URL
|
||||
* Examples (NEW API v2 format):
|
||||
* #marketplace/f41219846700 -> { tab: 'marketplace', itemId: 'f41219846700' }
|
||||
* #marketplace/preset_settings/f41219846700 -> { tab: 'marketplace', category: 'preset_settings', itemId: 'f41219846700' }
|
||||
* #marketplace/collection/featured -> { tab: 'marketplace', collection: 'featured' }
|
||||
* #discover/f41219846700 -> { tab: 'discover', itemId: 'f41219846700' }
|
||||
* #discover/preset_settings/f41219846700 -> { tab: 'discover', category: 'preset_settings', itemId: 'f41219846700' }
|
||||
* #discover/collection/featured -> { tab: 'discover', collection: 'featured' }
|
||||
* #discover/collection/featured/f41219846700 -> { tab: 'discover', collection: 'featured', itemId: 'f41219846700' }
|
||||
* #marketplace/74ef53ceed0b -> { tab: 'discover', itemId: '74ef53ceed0b' } (marketplace is aliased to discover)
|
||||
* #settings/appearance -> { tab: 'settings', section: 'appearance' }
|
||||
* #addons -> { tab: 'addons' }
|
||||
*
|
||||
* Legacy format (still supported):
|
||||
* #marketplace/quote_packs/digital-stoicism -> converted to item lookup
|
||||
* #discover/quote_packs/digital-stoicism -> converted to item lookup
|
||||
*/
|
||||
export const parseDeepLink = (hash = window.location.hash) => {
|
||||
if (!hash || hash === '#') {
|
||||
@@ -34,25 +36,36 @@ export const parseDeepLink = (hash = window.location.hash) => {
|
||||
itemId: parts[2],
|
||||
};
|
||||
|
||||
// Handle marketplace as an alias for discover (for backward compatibility with external URLs)
|
||||
if (result.tab === 'marketplace') {
|
||||
result.tab = 'discover';
|
||||
}
|
||||
|
||||
// Validate tab
|
||||
const validTabs = Object.values(TAB_TYPES);
|
||||
if (!validTabs.includes(result.tab)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle marketplace-specific parsing
|
||||
if (result.tab === 'marketplace') {
|
||||
// Handle discover-specific parsing (marketplace URLs are aliased to discover)
|
||||
if (result.tab === 'discover') {
|
||||
// Check if it's a collection
|
||||
if (result.section === 'collection') {
|
||||
result.collection = result.itemId;
|
||||
result.itemId = null;
|
||||
// Check if there's a 4th part (item ID within collection)
|
||||
if (parts[3]) {
|
||||
result.itemId = parts[3];
|
||||
result.fromCollection = true;
|
||||
} else {
|
||||
result.itemId = null;
|
||||
}
|
||||
}
|
||||
// Check if section is a category (preset_settings, photo_packs, quote_packs)
|
||||
else if (['preset_settings', 'photo_packs', 'quote_packs', 'all'].includes(result.section)) {
|
||||
result.category = result.section;
|
||||
// Third part is the item ID (already in result.itemId)
|
||||
}
|
||||
// If only one part after marketplace, assume it's an item ID
|
||||
// If only one part after tab, assume it's an item ID
|
||||
else if (result.section && !result.itemId) {
|
||||
result.itemId = result.section;
|
||||
result.section = null;
|
||||
@@ -64,20 +77,25 @@ export const parseDeepLink = (hash = window.location.hash) => {
|
||||
|
||||
/**
|
||||
* Create a deep link hash
|
||||
* @param {string} tab - The main tab (settings, marketplace, addons)
|
||||
* @param {string} tab - The main tab (settings, discover, addons)
|
||||
* @param {object} options - Additional options
|
||||
* @param {string} options.itemId - Item ID for marketplace items (v2 format)
|
||||
* @param {string} options.category - Category for marketplace items (optional)
|
||||
* @param {string} options.collection - Collection name for marketplace
|
||||
* @param {string} options.itemId - Item ID for discover/marketplace items (v2 format)
|
||||
* @param {string} options.category - Category for discover/marketplace items (optional)
|
||||
* @param {string} options.collection - Collection name for discover/marketplace
|
||||
* @param {boolean} options.fromCollection - If item is being viewed from within a collection
|
||||
* @param {string} options.section - Section within the tab
|
||||
* @returns {string} Hash string
|
||||
*/
|
||||
export const createDeepLink = (tab, options = {}) => {
|
||||
let hash = `#${tab}`;
|
||||
|
||||
if (tab === 'marketplace') {
|
||||
// Collection link
|
||||
if (options.collection) {
|
||||
if (tab === 'discover') {
|
||||
// Collection with item (item viewed from within a collection)
|
||||
if (options.collection && options.itemId && options.fromCollection) {
|
||||
hash += `/collection/${options.collection}/${options.itemId}`;
|
||||
}
|
||||
// Collection link (just the collection page)
|
||||
else if (options.collection) {
|
||||
hash += `/collection/${options.collection}`;
|
||||
}
|
||||
// Item with category
|
||||
@@ -101,10 +119,16 @@ export const createDeepLink = (tab, options = {}) => {
|
||||
|
||||
/**
|
||||
* Update URL hash without triggering page reload
|
||||
* @param {string} hash - The hash to set
|
||||
* @param {boolean} pushToHistory - If true, adds to browser history (default: true)
|
||||
*/
|
||||
export const updateHash = (hash) => {
|
||||
export const updateHash = (hash, pushToHistory = true) => {
|
||||
if (window.history.pushState) {
|
||||
window.history.pushState(null, null, hash);
|
||||
if (pushToHistory) {
|
||||
window.history.pushState(null, null, hash);
|
||||
} else {
|
||||
window.history.replaceState(null, null, hash);
|
||||
}
|
||||
} else {
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user