mirror of
https://github.com/mue/mue.git
synced 2026-07-08 21:24:57 +02:00
feat(Discover, ModalTopBar, MainModal): enhance navigation and iframe handling with hash updates
This commit is contained in:
@@ -79,6 +79,22 @@ function MainModal({ modalClose, deepLinkData }) {
|
||||
setCurrentTab(linkData.tab);
|
||||
}
|
||||
|
||||
// Handle discover category navigation - update section state to match hash
|
||||
if (linkData.tab === TAB_TYPES.DISCOVER && linkData.category) {
|
||||
const categoryToLabel = {
|
||||
all: variables.getMessage('modals.main.marketplace.all'),
|
||||
photo_packs: variables.getMessage('modals.main.marketplace.photo_packs'),
|
||||
quote_packs: variables.getMessage('modals.main.marketplace.quote_packs'),
|
||||
preset_settings: variables.getMessage('modals.main.marketplace.preset_settings'),
|
||||
collections: variables.getMessage('modals.main.marketplace.collections'),
|
||||
};
|
||||
const sectionLabel = categoryToLabel[linkData.category];
|
||||
if (sectionLabel) {
|
||||
setCurrentSection(sectionLabel);
|
||||
setCurrentSectionName(linkData.category);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle settings section navigation
|
||||
if (linkData.tab === TAB_TYPES.SETTINGS && linkData.section) {
|
||||
setNavigationTrigger({
|
||||
@@ -232,6 +248,7 @@ function MainModal({ modalClose, deepLinkData }) {
|
||||
productView={productView}
|
||||
iframeBreadcrumbs={iframeBreadcrumbs}
|
||||
onTabChange={handleChangeTab}
|
||||
onSectionChange={handleSectionChange}
|
||||
onSubSectionChange={handleSubSectionChange}
|
||||
onClose={modalClose}
|
||||
onBack={handleBack}
|
||||
|
||||
@@ -9,15 +9,27 @@ import mueAboutIcon from 'assets/icons/mue_about.png';
|
||||
// Map marketplace types to translation keys
|
||||
const MARKETPLACE_TYPE_TO_KEY = {
|
||||
photo_packs: 'modals.main.marketplace.photo_packs',
|
||||
'photo packs': 'modals.main.marketplace.photo_packs',
|
||||
photos: 'modals.main.marketplace.photo_packs',
|
||||
quote_packs: 'modals.main.marketplace.quote_packs',
|
||||
'quote packs': 'modals.main.marketplace.quote_packs',
|
||||
quotes: 'modals.main.marketplace.quote_packs',
|
||||
preset_settings: 'modals.main.marketplace.preset_settings',
|
||||
'preset settings': 'modals.main.marketplace.preset_settings',
|
||||
settings: 'modals.main.marketplace.preset_settings',
|
||||
collections: 'modals.main.marketplace.collections',
|
||||
all: 'modals.main.marketplace.all',
|
||||
};
|
||||
|
||||
// Map breadcrumb labels (from website) to category keys for navigation
|
||||
const BREADCRUMB_LABEL_TO_CATEGORY = {
|
||||
'photo packs': 'photo_packs',
|
||||
'quote packs': 'quote_packs',
|
||||
'preset settings': 'preset_settings',
|
||||
collections: 'collections',
|
||||
marketplace: 'all',
|
||||
};
|
||||
|
||||
function ModalTopBar({
|
||||
currentTab,
|
||||
currentSection,
|
||||
@@ -26,6 +38,7 @@ function ModalTopBar({
|
||||
productView,
|
||||
iframeBreadcrumbs,
|
||||
onTabChange,
|
||||
onSectionChange,
|
||||
onSubSectionChange,
|
||||
onClose,
|
||||
onBack,
|
||||
@@ -94,7 +107,13 @@ function ModalTopBar({
|
||||
if (currentTabLabel) {
|
||||
breadcrumbPath.push({
|
||||
label: currentTabLabel,
|
||||
onClick: productView ? productView.onBackToAll : null, // Clickable if viewing a product
|
||||
// Make "Discover" clickable when viewing items/categories to go back to "All"
|
||||
onClick:
|
||||
(iframeBreadcrumbs && iframeBreadcrumbs.length > 0) || productView
|
||||
? () => {
|
||||
updateHash('#discover/all');
|
||||
}
|
||||
: null,
|
||||
});
|
||||
|
||||
// Check if we have iframe breadcrumbs (from Discover iframe)
|
||||
@@ -106,24 +125,42 @@ function ModalTopBar({
|
||||
relevantCrumbs.forEach((crumb, index) => {
|
||||
const isLast = index === relevantCrumbs.length - 1;
|
||||
|
||||
// Translate the breadcrumb label if it's a known category
|
||||
const lowerLabel = crumb.label.toLowerCase();
|
||||
const translationKey = MARKETPLACE_TYPE_TO_KEY[lowerLabel];
|
||||
const displayLabel = translationKey ? t(translationKey) : crumb.label;
|
||||
|
||||
// Get the category key for navigation
|
||||
const categoryKey = BREADCRUMB_LABEL_TO_CATEGORY[lowerLabel];
|
||||
|
||||
breadcrumbPath.push({
|
||||
label: crumb.label,
|
||||
label: displayLabel,
|
||||
// Make it clickable if it has an href and it's not the last item
|
||||
onClick:
|
||||
crumb.clickable && !isLast && crumb.href
|
||||
? () => {
|
||||
// Convert website href to extension hash format
|
||||
// e.g., /marketplace/packs/123 -> #discover/photo_packs/123
|
||||
// e.g., /marketplace/collections -> #discover/collections
|
||||
const href = crumb.href;
|
||||
|
||||
if (href.includes('/collections')) {
|
||||
// Try to extract type from URL parameters first (most reliable)
|
||||
if (href.includes('type=')) {
|
||||
const urlParams = new URLSearchParams(href.split('?')[1]);
|
||||
const typeParam = urlParams.get('type');
|
||||
if (typeParam) {
|
||||
updateHash(`#discover/${typeParam}`);
|
||||
}
|
||||
}
|
||||
// Otherwise check specific paths
|
||||
else if (href.includes('/collections')) {
|
||||
updateHash('#discover/collections');
|
||||
} else if (href.includes('/collection/')) {
|
||||
const collectionId = href.split('/collection/')[1]?.split('?')[0];
|
||||
if (collectionId) {
|
||||
updateHash(`#discover/collection/${collectionId}`);
|
||||
}
|
||||
} else if (categoryKey) {
|
||||
// If we recognized the category from the label, navigate to it
|
||||
updateHash(`#discover/${categoryKey}`);
|
||||
} else if (href === '/marketplace' || href === '/marketplace/') {
|
||||
updateHash('#discover/all');
|
||||
}
|
||||
|
||||
@@ -22,6 +22,92 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) {
|
||||
const previewParam = isPreviewMode ? '&preview=true' : '';
|
||||
const isOffline = navigator.onLine === false || offlineMode;
|
||||
|
||||
// Helper function to update iframe based on hash
|
||||
const updateIframeFromHash = useRef(() => {
|
||||
if (!iframeRef.current) return;
|
||||
|
||||
const hash = window.location.hash;
|
||||
if (!hash || !hash.startsWith('#discover')) return;
|
||||
|
||||
const parts = hash.slice(1).split('/');
|
||||
if (parts.length < 2) return;
|
||||
|
||||
const theme = getResolvedTheme();
|
||||
const themeParam = `&theme=${theme}`;
|
||||
let targetUrl = '';
|
||||
|
||||
if (parts[1] === 'collections') {
|
||||
targetUrl = `${MARKETPLACE_URL}/collections?embed=true${previewParam}${themeParam}`;
|
||||
} else if (parts[1] === 'collection' && parts[2]) {
|
||||
targetUrl = `${MARKETPLACE_URL}/collection/${parts[2]}?embed=true${previewParam}${themeParam}`;
|
||||
} else if (parts[2]) {
|
||||
// Item view - map category to path
|
||||
const pathMap = {
|
||||
photo_packs: 'packs',
|
||||
quote_packs: 'packs',
|
||||
preset_settings: 'presets',
|
||||
};
|
||||
const pathSegment = pathMap[parts[1]] || 'packs';
|
||||
targetUrl = `${MARKETPLACE_URL}/${pathSegment}/${parts[2]}?embed=true${previewParam}${themeParam}`;
|
||||
} else if (parts[1] === 'all') {
|
||||
// All items
|
||||
targetUrl = `${MARKETPLACE_URL}?embed=true${previewParam}${themeParam}`;
|
||||
} else {
|
||||
// Category filter (photo_packs, quote_packs, preset_settings)
|
||||
targetUrl = `${MARKETPLACE_URL}?embed=true&type=${parts[1]}${previewParam}${themeParam}`;
|
||||
}
|
||||
|
||||
// Update iframe src directly
|
||||
if (targetUrl && iframeRef.current.src !== targetUrl) {
|
||||
setIsLoading(true);
|
||||
iframeRef.current.src = targetUrl;
|
||||
}
|
||||
});
|
||||
|
||||
// Update the ref function when previewParam changes
|
||||
useEffect(() => {
|
||||
updateIframeFromHash.current = () => {
|
||||
if (!iframeRef.current) return;
|
||||
|
||||
const hash = window.location.hash;
|
||||
if (!hash || !hash.startsWith('#discover')) return;
|
||||
|
||||
const parts = hash.slice(1).split('/');
|
||||
if (parts.length < 2) return;
|
||||
|
||||
const theme = getResolvedTheme();
|
||||
const themeParam = `&theme=${theme}`;
|
||||
let targetUrl = '';
|
||||
|
||||
if (parts[1] === 'collections') {
|
||||
targetUrl = `${MARKETPLACE_URL}/collections?embed=true${previewParam}${themeParam}`;
|
||||
} else if (parts[1] === 'collection' && parts[2]) {
|
||||
targetUrl = `${MARKETPLACE_URL}/collection/${parts[2]}?embed=true${previewParam}${themeParam}`;
|
||||
} else if (parts[2]) {
|
||||
// Item view - map category to path
|
||||
const pathMap = {
|
||||
photo_packs: 'packs',
|
||||
quote_packs: 'packs',
|
||||
preset_settings: 'presets',
|
||||
};
|
||||
const pathSegment = pathMap[parts[1]] || 'packs';
|
||||
targetUrl = `${MARKETPLACE_URL}/${pathSegment}/${parts[2]}?embed=true${previewParam}${themeParam}`;
|
||||
} else if (parts[1] === 'all') {
|
||||
// All items
|
||||
targetUrl = `${MARKETPLACE_URL}?embed=true${previewParam}${themeParam}`;
|
||||
} else {
|
||||
// Category filter (photo_packs, quote_packs, preset_settings)
|
||||
targetUrl = `${MARKETPLACE_URL}?embed=true&type=${parts[1]}${previewParam}${themeParam}`;
|
||||
}
|
||||
|
||||
// Update iframe src directly
|
||||
if (targetUrl && iframeRef.current.src !== targetUrl) {
|
||||
setIsLoading(true);
|
||||
iframeRef.current.src = targetUrl;
|
||||
}
|
||||
};
|
||||
}, [previewParam]);
|
||||
|
||||
// Clear breadcrumbs when component unmounts (navigating away from discover)
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -142,57 +228,29 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) {
|
||||
}
|
||||
}, [deepLinkData, previewParam]);
|
||||
|
||||
// Send navigation commands to iframe when hash changes externally (e.g., browser back/forward)
|
||||
// Send navigation commands to iframe when hash changes externally (e.g., browser back/forward, breadcrumb clicks)
|
||||
useEffect(() => {
|
||||
const handleHashChange = () => {
|
||||
if (!iframeRef.current?.contentWindow) return;
|
||||
updateIframeFromHash.current();
|
||||
};
|
||||
|
||||
const hash = window.location.hash;
|
||||
if (!hash || !hash.startsWith('#discover')) return;
|
||||
|
||||
// Parse hash to determine target path
|
||||
// e.g., #discover/photo_packs/123 -> /marketplace/packs/123
|
||||
// e.g., #discover/preset_settings/456 -> /marketplace/presets/456
|
||||
// e.g., #discover/collections -> /marketplace/collections
|
||||
// e.g., #discover/collection/featured -> /marketplace/collection/featured
|
||||
|
||||
const parts = hash.slice(1).split('/');
|
||||
if (parts.length < 2) return;
|
||||
|
||||
let targetPath = '/marketplace';
|
||||
|
||||
if (parts[1] === 'collections') {
|
||||
targetPath = '/marketplace/collections';
|
||||
} else if (parts[1] === 'collection' && parts[2]) {
|
||||
targetPath = `/marketplace/collection/${parts[2]}`;
|
||||
} else if (parts[2]) {
|
||||
// Item view - map category to path
|
||||
const pathMap = {
|
||||
photo_packs: 'packs',
|
||||
quote_packs: 'packs',
|
||||
preset_settings: 'presets',
|
||||
};
|
||||
const pathSegment = pathMap[parts[1]] || 'packs';
|
||||
targetPath = `/marketplace/${pathSegment}/${parts[2]}`;
|
||||
} else if (parts[1] !== 'all') {
|
||||
// Category filter
|
||||
targetPath = `/marketplace?type=${parts[1]}`;
|
||||
}
|
||||
|
||||
// Send navigation command to iframe
|
||||
const theme = getResolvedTheme();
|
||||
iframeRef.current.contentWindow.postMessage(
|
||||
{
|
||||
type: 'marketplace:navigate',
|
||||
payload: { path: targetPath },
|
||||
},
|
||||
MARKETPLACE_URL,
|
||||
);
|
||||
const handlePopState = () => {
|
||||
updateIframeFromHash.current();
|
||||
};
|
||||
|
||||
// Listen for hash changes from browser navigation
|
||||
window.addEventListener('hashchange', handleHashChange);
|
||||
return () => window.removeEventListener('hashchange', handleHashChange);
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
|
||||
// Also trigger immediately if hash exists
|
||||
if (window.location.hash.startsWith('#discover')) {
|
||||
updateIframeFromHash.current();
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('hashchange', handleHashChange);
|
||||
window.removeEventListener('popstate', handlePopState);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -152,6 +152,8 @@ export const updateHash = (hash, pushToHistory = true) => {
|
||||
} else {
|
||||
window.history.replaceState(null, null, hash);
|
||||
}
|
||||
// Dispatch a custom event so components can react to programmatic hash changes
|
||||
window.dispatchEvent(new PopStateEvent('popstate'));
|
||||
} else {
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user