mirror of
https://github.com/mue/mue.git
synced 2026-07-03 13:13:14 +02:00
fix: resolve QA bugs across settings, reminder banner, and modal layout
- Replace all querySelector('.reminder-info') calls with EventBus.emit('showReminder')
across 14 files — prevents crashes when the element is not mounted
- Move ReminderInfo from inside the sidebar to the top of the right content column
so it no longer obscures sidebar navigation
- Fix reminder not showing for non-hot-reload settings (Slider hook violation where
useT() was called inside a requestAnimationFrame callback)
- Skip reminder for LocationSearch since it already emits a hot-reload refresh event
- Suppress reminder for settings with hot-reload (no page refresh needed)
- Fix modal content overflowing right edge: replace width:100%!important on
.modalTabContent with flex:1 + min-width:0 + overflow-x:hidden
- Fix ReminderInfo refresh button being too small
- Fix sidebar collapse animation using max-width transition instead of width:0
- Fix PhotoInformation showing 0x0 resolution and Unknown Location before image loads
- Fix Wikipedia disambiguation links for quote authors by returning null from
getAuthorLink (Wikidata links used when available)
- Bail early in useSuggestedPacks when offline mode is enabled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import SidebarToggle from '../components/SidebarToggle';
|
||||
import ErrorBoundary from '../../../../features/misc/modals/ErrorBoundary';
|
||||
import { TAB_TYPES } from '../constants/tabConfig';
|
||||
import { SearchInput } from 'components/Form/Settings';
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
const Tabs = ({
|
||||
children,
|
||||
@@ -129,6 +130,16 @@ const Tabs = ({
|
||||
}
|
||||
}, [resetToFirst]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleShowReminder = () => {
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
setShowReminder(true);
|
||||
};
|
||||
|
||||
EventBus.on('showReminder', handleShowReminder);
|
||||
return () => EventBus.off('showReminder', handleShowReminder);
|
||||
}, []);
|
||||
|
||||
const handleHideReminder = () => {
|
||||
localStorage.setItem('showReminder', 'false');
|
||||
setShowReminder(false);
|
||||
@@ -189,21 +200,21 @@ const Tabs = ({
|
||||
{searchQuery.trim() && filteredChildren.length === 0 && (
|
||||
<div className="sidebarEmptyState">{t('widgets.weather.not_found')}</div>
|
||||
)}
|
||||
{!sidebarCollapsed && (
|
||||
<ReminderInfo isVisible={showReminder} onHide={handleHideReminder} />
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="modalTabContent" ref={contentRef}>
|
||||
{children.map((tab, index) => {
|
||||
if (tab.props.label !== currentTab) {
|
||||
return null;
|
||||
}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>
|
||||
<ReminderInfo isVisible={showReminder} onHide={handleHideReminder} />
|
||||
<div className="modalTabContent" ref={contentRef}>
|
||||
{children.map((tab, index) => {
|
||||
if (tab.props.label !== currentTab) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary key={`error-boundary-${index}`}>{tab.props.children}</ErrorBoundary>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<ErrorBoundary key={`error-boundary-${index}`}>{tab.props.children}</ErrorBoundary>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,17 +9,15 @@ const ReminderInfo = ({ isVisible, onHide }) => {
|
||||
|
||||
return (
|
||||
<div className="reminder-info">
|
||||
<div className="shareHeader">
|
||||
<span className="title">{t('modals.main.settings.reminder.title')}</span>
|
||||
<span className="closeModal" onClick={onHide}>
|
||||
<MdClose />
|
||||
</span>
|
||||
</div>
|
||||
<span className="title">{t('modals.main.settings.reminder.title')}</span>
|
||||
<span className="subtitle">{t('modals.main.settings.reminder.message')}</span>
|
||||
<button onClick={() => window.location.reload()}>
|
||||
<MdRefresh />
|
||||
{t('modals.main.error_boundary.refresh')}
|
||||
</button>
|
||||
<span className="closeModal" onClick={onHide}>
|
||||
<MdClose />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -324,27 +324,51 @@ h5 {
|
||||
|
||||
.reminder-info {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 15px;
|
||||
gap: 15px;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 16px;
|
||||
flex-shrink: 0;
|
||||
|
||||
@include themed {
|
||||
background-color: t($modal-secondaryColour);
|
||||
border-radius: t($borderRadius);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-bottom: 1px solid t($modal-sidebarActive);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
opacity: 0.7;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
@include basicIconButton(5px, 5px, modal);
|
||||
@include basicIconButton(8px 14px, 0.875rem, modal);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
margin: 0 !important;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.closeModal {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@use 'scss/variables' as *;
|
||||
|
||||
.modalTabContent {
|
||||
width: 100% !important;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
/* button {
|
||||
@include modal-button(standard);
|
||||
@@ -11,8 +12,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 2rem;
|
||||
|
||||
|
||||
@@ -110,7 +110,11 @@
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
transition: opacity 0.25s ease;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
opacity 0.25s ease,
|
||||
max-width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,8 +169,7 @@
|
||||
|
||||
span {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
max-width: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ const Checkbox = memo((props) => {
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,8 +197,9 @@ const Dropdown = memo((props) => {
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -235,9 +235,6 @@ const LocationSearch = memo((props) => {
|
||||
closeDropdown();
|
||||
|
||||
EventBus.emit('refresh', category);
|
||||
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
localStorage.setItem('showReminder', true);
|
||||
},
|
||||
[name, category, closeDropdown],
|
||||
);
|
||||
@@ -272,8 +269,6 @@ const LocationSearch = memo((props) => {
|
||||
setSearchQuery('');
|
||||
|
||||
EventBus.emit('refresh', category);
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
localStorage.setItem('showReminder', true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Auto location error:', err);
|
||||
|
||||
@@ -43,8 +43,9 @@ const Radio = memo((props) => {
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import EventBus from 'utils/eventbus';
|
||||
import './Slider.scss';
|
||||
|
||||
const SliderComponent = memo((props) => {
|
||||
const t = useT();
|
||||
const [value, setValue] = useState(localStorage.getItem(props.name) || props.default);
|
||||
const [hoverValue, setHoverValue] = useState(null);
|
||||
const [hoverPosition, setHoverPosition] = useState(0);
|
||||
@@ -32,8 +33,9 @@ const SliderComponent = memo((props) => {
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +55,6 @@ const SliderComponent = memo((props) => {
|
||||
const startTime = performance.now();
|
||||
|
||||
const animate = (currentTime) => {
|
||||
const t = useT();
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@ const Switch = memo((props) => {
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,9 @@ const Text = memo((props) => {
|
||||
|
||||
if (element) {
|
||||
if (!document.querySelector(element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,9 @@ function Header(props) {
|
||||
|
||||
if (props.element) {
|
||||
if (!document.querySelector(props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,11 +154,13 @@ function MetadataGrid({
|
||||
/>
|
||||
)}
|
||||
|
||||
<MetadataItem
|
||||
icon={<Resolution />}
|
||||
label={t('widgets.background.resolution')}
|
||||
value={`${width}x${height}`}
|
||||
/>
|
||||
{width !== null && height !== null && (
|
||||
<MetadataItem
|
||||
icon={<Resolution />}
|
||||
label={t('widgets.background.resolution')}
|
||||
value={`${width}x${height}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{colour && (
|
||||
<MetadataItem
|
||||
@@ -304,8 +306,8 @@ function ActionButtons({
|
||||
function PhotoInformation({ info, url, api }) {
|
||||
const t = useT();
|
||||
const navigate = useNavigate();
|
||||
const [width, setWidth] = useState(0);
|
||||
const [height, setHeight] = useState(0);
|
||||
const [width, setWidth] = useState(null);
|
||||
const [height, setHeight] = useState(null);
|
||||
const [shareModal, openShareModal] = useState(false);
|
||||
const [excludeModal, openExcludeModal] = useState(false);
|
||||
const [favouriteTooltipText, setFavouriteTooltipText] = useState(t('widgets.quote.favourite'));
|
||||
@@ -325,7 +327,7 @@ function PhotoInformation({ info, url, api }) {
|
||||
if (typeof info.location === 'string') {
|
||||
return info.location.split(',').slice(-2).join(', ').trim();
|
||||
}
|
||||
return info.location?.name || 'Unknown Location';
|
||||
return info.location?.name || null;
|
||||
};
|
||||
|
||||
if (info.hidden === true || !info.credit) {
|
||||
@@ -437,9 +439,11 @@ function PhotoInformation({ info, url, api }) {
|
||||
{/* PRIMARY SECTION - Always Visible */}
|
||||
<div className="primary-content">
|
||||
<div className="photoInformation-text">
|
||||
<span className="title" title={info.description || ''}>
|
||||
{getPrimaryText()}
|
||||
</span>
|
||||
{getPrimaryText() && (
|
||||
<span className="title" title={info.description || ''}>
|
||||
{getPrimaryText()}
|
||||
</span>
|
||||
)}
|
||||
<span className="subtitle attribution" id="credit">
|
||||
{photo} {credit}
|
||||
</span>
|
||||
|
||||
@@ -31,6 +31,12 @@ export function useSuggestedPacks(category, limit = 4, minToShow = 2) {
|
||||
}, [timestampKey, cacheExpiryMs]);
|
||||
|
||||
const fetchSuggestions = useCallback(async () => {
|
||||
if (localStorage.getItem('offlineMode') === 'true') {
|
||||
setSuggestions(null);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
@@ -39,8 +39,8 @@ const MessageOptions = () => {
|
||||
setMessages(updatedMessages);
|
||||
|
||||
localStorage.setItem('messages', JSON.stringify(updatedMessages));
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
};
|
||||
|
||||
const MESSAGE_SECTION = 'modals.main.settings.sections.message';
|
||||
|
||||
@@ -9,12 +9,8 @@ import { fetchAuthorFromWikidata } from 'utils/wikidata/wikidataAuthorFetcher';
|
||||
* Custom hook for loading quote data from various sources
|
||||
*/
|
||||
export function useQuoteLoader(updateQuote) {
|
||||
const getAuthorLink = useCallback((author) => {
|
||||
return localStorage.getItem('authorLink') === 'false' || author === 'Unknown'
|
||||
? null
|
||||
: `https://${variables.languagecode.split('_')[0]}.wikipedia.org/wiki/${author
|
||||
.split(' ')
|
||||
.join('_')}`;
|
||||
const getAuthorLink = useCallback((_author) => {
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,8 +72,8 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) =>
|
||||
setCustomQuote(updatedCustomQuote);
|
||||
|
||||
localStorage.setItem('customQuote', JSON.stringify(updatedCustomQuote));
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
};
|
||||
|
||||
const modifyCustomQuote = (type, index) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
function showReminder() {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
}
|
||||
|
||||
export const settingsHandler = {
|
||||
|
||||
@@ -4,10 +4,9 @@ import variables from 'config/variables';
|
||||
import { refreshAPIPackCache } from 'features/background/api/photoPackAPI';
|
||||
import { getHandler } from './handlerRegistry';
|
||||
|
||||
// todo: relocate this function
|
||||
function showReminder() {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,10 +2,9 @@ import EventBus from 'utils/eventbus';
|
||||
import { clearQueuesOnSettingChange } from 'utils/queueOperations';
|
||||
import { getHandler } from './handlerRegistry';
|
||||
|
||||
// todo: relocate this function
|
||||
function showReminder() {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
localStorage.setItem('showReminder', true);
|
||||
localStorage.setItem('showReminder', 'true');
|
||||
EventBus.emit('showReminder');
|
||||
}
|
||||
|
||||
export function uninstall(type, name) {
|
||||
|
||||
Reference in New Issue
Block a user