mirror of
https://github.com/mue/mue.git
synced 2026-06-08 14:10:42 +02:00
fix: lightbox, breadcrumbs, navbar, uninstall
This commit is contained in:
@@ -49,8 +49,8 @@ function ModalTopBar({
|
|||||||
// Get the last breadcrumb item (the item name)
|
// Get the last breadcrumb item (the item name)
|
||||||
const lastCrumb = iframeBreadcrumbs[iframeBreadcrumbs.length - 1];
|
const lastCrumb = iframeBreadcrumbs[iframeBreadcrumbs.length - 1];
|
||||||
|
|
||||||
// Add current section if available
|
// Add current section if available and different from the last crumb
|
||||||
if (currentSection) {
|
if (currentSection && currentSection !== lastCrumb.label) {
|
||||||
breadcrumbPath.push({
|
breadcrumbPath.push({
|
||||||
label: currentSection,
|
label: currentSection,
|
||||||
onClick: () => onBack(), // Clickable to go back
|
onClick: () => onBack(), // Clickable to go back
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
@use 'scss/variables' as *;
|
@use 'scss/variables' as *;
|
||||||
|
|
||||||
.modalTopBar {
|
.modalTopBar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 1.5rem 1.5rem;
|
padding: 1.5rem 1.5rem;
|
||||||
// width: 100%;
|
// width: 100%;
|
||||||
|
|
||||||
|
@include themed {
|
||||||
|
background: t($modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
.topBarLeft {
|
.topBarLeft {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const OPENSTREETMAP_URL = 'https://www.openstreetmap.org';
|
|||||||
|
|
||||||
// Mue URLs
|
// Mue URLs
|
||||||
export const WEBSITE_URL = 'https://muetab.com';
|
export const WEBSITE_URL = 'https://muetab.com';
|
||||||
export const MARKETPLACE_URL = 'https://muetab.com/marketplace';
|
export const MARKETPLACE_URL = 'http://localhost:3000/marketplace';
|
||||||
export const PRIVACY_URL = 'https://muetab.com/privacy';
|
export const PRIVACY_URL = 'https://muetab.com/privacy';
|
||||||
export const TRANSLATIONS_URL = 'https://muetab.com/docs/translations';
|
export const TRANSLATIONS_URL = 'https://muetab.com/docs/translations';
|
||||||
export const WEBLATE_URL = 'https://hosted.weblate.org/projects/mue/mue-tab/';
|
export const WEBLATE_URL = 'https://hosted.weblate.org/projects/mue/mue-tab/';
|
||||||
|
|||||||
@@ -227,12 +227,6 @@ class About extends PureComponent {
|
|||||||
icon={<BiDonateHeart />}
|
icon={<BiDonateHeart />}
|
||||||
label={variables.getMessage('modals.main.settings.sections.about.support_donate')}
|
label={variables.getMessage('modals.main.settings.sections.about.support_donate')}
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
type="linkIconButton"
|
|
||||||
href={'https://github.com/sponsors/' + variables.constants.ORG_NAME}
|
|
||||||
icon={<SiGithubsponsors />}
|
|
||||||
tooltipTitle="Github Sponsors"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,169 +1,60 @@
|
|||||||
import variables from 'config/variables';
|
import variables from 'config/variables';
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState } from 'react';
|
||||||
import { MdOutlineWifiOff } from 'react-icons/md';
|
import { MdOutlineWifiOff } from 'react-icons/md';
|
||||||
import Modal from 'react-modal';
|
|
||||||
|
|
||||||
import Lightbox from '../../marketplace/components/Elements/Lightbox/Lightbox';
|
|
||||||
|
|
||||||
const Changelog = () => {
|
const Changelog = () => {
|
||||||
const [title, setTitle] = useState(null);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [content, setContent] = useState(null);
|
|
||||||
const [date, setDate] = useState(null);
|
|
||||||
const [image, setImage] = useState(null);
|
|
||||||
const [error, setError] = useState(false);
|
|
||||||
const [showLightbox, setShowLightbox] = useState(false);
|
|
||||||
const [lightboxImg, setLightboxImg] = useState(null);
|
|
||||||
|
|
||||||
const offlineMode = localStorage.getItem('offlineMode') === 'true';
|
const offlineMode = localStorage.getItem('offlineMode') === 'true';
|
||||||
const controllerRef = useRef(new AbortController());
|
const isOffline = navigator.onLine === false || offlineMode;
|
||||||
const changelog = useRef();
|
|
||||||
|
|
||||||
const parseMarkdown = (text) => {
|
const handleLoad = () => {
|
||||||
if (typeof text !== 'string') {
|
setIsLoading(false);
|
||||||
throw new Error('Input must be a string');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace markdown syntax
|
|
||||||
text = text
|
|
||||||
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
||||||
.replace(/^## (.*$)/gm, '<span class="title">$1</span>')
|
|
||||||
.replace(
|
|
||||||
/((http|https):\/\/[^\s]+)/g,
|
|
||||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
|
|
||||||
)
|
|
||||||
// resolve @ to github user link
|
|
||||||
.replace(
|
|
||||||
/@([a-zA-Z0-9-_]+)/g,
|
|
||||||
'<a href="https://github.com/$1" target="_blank" class="changelogAt">@$1</a>',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Replace list items
|
|
||||||
text = text.replace(/^\* (.*$)/gm, '<li>$1</li>');
|
|
||||||
|
|
||||||
// Wrap list items in <ul></ul>
|
|
||||||
text = text.replace(/((<li>.*<\/li>\s*)+)/g, '<ul>$1</ul>');
|
|
||||||
|
|
||||||
return text;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUpdate = async () => {
|
// Show offline error message if offline
|
||||||
const releases = await fetch(
|
if (isOffline) {
|
||||||
`https://api.github.com/repos/${variables.constants.ORG_NAME}/${variables.constants.REPO_NAME}/releases`,
|
|
||||||
{
|
|
||||||
signal: controllerRef.current.signal,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (controllerRef.current.signal.aborted === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the release which tag_name is the same as the current version
|
|
||||||
const data = await releases.json();
|
|
||||||
let release = data.find((release) => release.tag_name === variables.constants.VERSION);
|
|
||||||
|
|
||||||
if (!release) {
|
|
||||||
release = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// request the changelog
|
|
||||||
const res = await fetch(release.url, { signal: controllerRef.current.signal });
|
|
||||||
|
|
||||||
if (res.status === 404) {
|
|
||||||
setError(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controllerRef.current.signal.aborted === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const changelog = await res.json();
|
|
||||||
setTitle(changelog.name);
|
|
||||||
setContent(parseMarkdown(changelog.body));
|
|
||||||
setDate(new Date(changelog.published_at).toLocaleDateString());
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (navigator.onLine === false || offlineMode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUpdate();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// stop making requests
|
|
||||||
controllerRef.current.abort();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const errorMessage = (msg) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="emptyItems">
|
<div className="emptyItems">
|
||||||
<div className="emptyMessage">{msg}</div>
|
<div className="emptyMessage">
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (navigator.onLine === false || offlineMode) {
|
|
||||||
return errorMessage(
|
|
||||||
<>
|
|
||||||
<MdOutlineWifiOff />
|
<MdOutlineWifiOff />
|
||||||
<h1>{variables.getMessage('modals.main.marketplace.offline.title')}</h1>
|
<h1>{variables.getMessage('modals.main.marketplace.offline.title')}</h1>
|
||||||
<p className="description">
|
<p className="description">
|
||||||
{variables.getMessage('modals.main.marketplace.offline.description')}
|
{variables.getMessage('modals.main.marketplace.offline.description')}
|
||||||
</p>
|
</p>
|
||||||
</>,
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
if (error === true) {
|
|
||||||
return errorMessage(
|
|
||||||
<>
|
|
||||||
<MdOutlineWifiOff />
|
|
||||||
<span className="title">{variables.getMessage('modals.main.error_boundary.title')}</span>
|
|
||||||
<span className="subtitle">
|
|
||||||
{variables.getMessage('modals.main.error_boundary.message')}
|
|
||||||
</span>
|
|
||||||
</>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
return errorMessage(
|
|
||||||
<div className="loaderHolder">
|
|
||||||
<div id="loader"></div>
|
|
||||||
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
|
|
||||||
</div>,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modalInfoPage changelogtab" ref={changelog}>
|
<div style={{ position: 'relative', width: '100%', minHeight: '100vh' }}>
|
||||||
<span className="mainTitle">{title}</span>
|
{isLoading && (
|
||||||
<span className="subtitle">Released on {date}</span>
|
<div className="loaderHolder" style={{
|
||||||
{image && (
|
position: 'absolute',
|
||||||
<img
|
top: '50%',
|
||||||
draggable={false}
|
left: '50%',
|
||||||
src={image}
|
transform: 'translate(-50%, -50%)',
|
||||||
alt={title}
|
zIndex: 10
|
||||||
className="updateImage"
|
}}>
|
||||||
/>
|
<div id="loader"></div>
|
||||||
|
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="updateChangelog" dangerouslySetInnerHTML={{ __html: content }} />
|
<iframe
|
||||||
<Modal
|
src="http://localhost:3000/blog/changelog?embed=true"
|
||||||
closeTimeoutMS={100}
|
onLoad={handleLoad}
|
||||||
onRequestClose={() => setShowLightbox(false)}
|
scrolling="no"
|
||||||
isOpen={showLightbox}
|
style={{
|
||||||
className="Modal lightBoxModal"
|
width: '100%',
|
||||||
overlayClassName="Overlay resetoverlay"
|
height: '2000px',
|
||||||
ariaHideApp={false}
|
minHeight: '100vh',
|
||||||
>
|
border: 'none',
|
||||||
<Lightbox
|
opacity: isLoading ? 0 : 1,
|
||||||
modalClose={() => setShowLightbox(false)}
|
transition: 'opacity 0.2s ease-in-out',
|
||||||
img={lightboxImg}
|
}}
|
||||||
|
title="Changelog"
|
||||||
/>
|
/>
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,18 +2,31 @@ import variables from 'config/variables';
|
|||||||
import { MARKETPLACE_URL } from 'config/constants';
|
import { MARKETPLACE_URL } from 'config/constants';
|
||||||
import { memo, useEffect, useRef, useState } from 'react';
|
import { memo, useEffect, useRef, useState } from 'react';
|
||||||
import { MdOutlineWifiOff } from 'react-icons/md';
|
import { MdOutlineWifiOff } from 'react-icons/md';
|
||||||
|
import Modal from 'react-modal';
|
||||||
import Tabs from 'components/Elements/MainModal/backend/Tabs';
|
import Tabs from 'components/Elements/MainModal/backend/Tabs';
|
||||||
import { useMarketplaceInstall } from 'features/marketplace/components/hooks/useMarketplaceInstall';
|
import { useMarketplaceInstall } from 'features/marketplace/components/hooks/useMarketplaceInstall';
|
||||||
|
import Lightbox from 'features/marketplace/components/Elements/Lightbox/Lightbox';
|
||||||
|
|
||||||
function DiscoverContent({ category, onBreadcrumbsChange }) {
|
function DiscoverContent({ category, onBreadcrumbsChange }) {
|
||||||
const iframeRef = useRef(null);
|
const iframeRef = useRef(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [showLightbox, setShowLightbox] = useState(false);
|
||||||
|
const [lightboxImg, setLightboxImg] = useState(null);
|
||||||
const { installItem, uninstallItem } = useMarketplaceInstall();
|
const { installItem, uninstallItem } = useMarketplaceInstall();
|
||||||
|
|
||||||
// Check for offline mode
|
// Check for offline mode
|
||||||
const offlineMode = localStorage.getItem('offlineMode') === 'true';
|
const offlineMode = localStorage.getItem('offlineMode') === 'true';
|
||||||
const isOffline = navigator.onLine === false || offlineMode;
|
const isOffline = navigator.onLine === false || offlineMode;
|
||||||
|
|
||||||
|
// Clear breadcrumbs when component unmounts (navigating away from discover)
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (onBreadcrumbsChange) {
|
||||||
|
onBreadcrumbsChange([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [onBreadcrumbsChange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Show loader when category changes
|
// Show loader when category changes
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -84,7 +97,8 @@ function DiscoverContent({ category, onBreadcrumbsChange }) {
|
|||||||
// Listen for postMessage events from the iframe
|
// Listen for postMessage events from the iframe
|
||||||
const handleMessage = (event) => {
|
const handleMessage = (event) => {
|
||||||
// Verify the origin if needed
|
// Verify the origin if needed
|
||||||
if (event.origin !== MARKETPLACE_URL) {
|
const marketplaceOrigin = new URL(MARKETPLACE_URL).origin;
|
||||||
|
if (event.origin !== marketplaceOrigin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +123,7 @@ function DiscoverContent({ category, onBreadcrumbsChange }) {
|
|||||||
|
|
||||||
case 'marketplace:item:uninstall':
|
case 'marketplace:item:uninstall':
|
||||||
if (payload?.item) {
|
if (payload?.item) {
|
||||||
uninstallItem(payload.item.type, payload.item.display_name || payload.item.name);
|
uninstallItem(payload.item.type, payload.item.name || payload.item.display_name);
|
||||||
// Send confirmation back to iframe
|
// Send confirmation back to iframe
|
||||||
if (iframeRef.current?.contentWindow) {
|
if (iframeRef.current?.contentWindow) {
|
||||||
iframeRef.current.contentWindow.postMessage(
|
iframeRef.current.contentWindow.postMessage(
|
||||||
@@ -148,6 +162,13 @@ function DiscoverContent({ category, onBreadcrumbsChange }) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'marketplace:lightbox':
|
||||||
|
if (payload?.photo) {
|
||||||
|
setLightboxImg(payload.photo.url);
|
||||||
|
setShowLightbox(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -181,10 +202,23 @@ function DiscoverContent({ category, onBreadcrumbsChange }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative', width: '100%', minHeight: '100vh' }}>
|
<div style={{ position: 'relative', width: '100%', minHeight: '100vh' }}>
|
||||||
|
<Modal
|
||||||
|
closeTimeoutMS={300}
|
||||||
|
onRequestClose={() => setShowLightbox(false)}
|
||||||
|
isOpen={showLightbox}
|
||||||
|
className="Modal lightBoxModal"
|
||||||
|
overlayClassName="Overlay"
|
||||||
|
ariaHideApp={false}
|
||||||
|
>
|
||||||
|
<Lightbox
|
||||||
|
modalClose={() => setShowLightbox(false)}
|
||||||
|
img={lightboxImg}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="loaderHolder" style={{
|
<div className="loaderHolder" style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '50%',
|
top: '20%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
zIndex: 10
|
zIndex: 10
|
||||||
@@ -200,12 +234,11 @@ function DiscoverContent({ category, onBreadcrumbsChange }) {
|
|||||||
scrolling="no"
|
scrolling="no"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '2000px',
|
height: '1500px',
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
opacity: isLoading ? 0 : 1,
|
opacity: isLoading ? 0 : 1,
|
||||||
transition: 'opacity 0.2s ease-in-out',
|
transition: 'opacity 0.2s ease-in-out',
|
||||||
overflow: 'hidden',
|
|
||||||
}}
|
}}
|
||||||
title="Marketplace"
|
title="Marketplace"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user