mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-14 16:28:59 +00:00
Wrapstodon: Add nav modal (#37210)
This commit is contained in:
3
app/javascript/images/icons/icon_planet.svg
Normal file
3
app/javascript/images/icons/icon_planet.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
||||||
|
<path fill="currentColor" d="M9.2 8.525c.35 0 .646-.12.888-.363.241-.241.362-.537.362-.887s-.12-.646-.362-.888a1.207 1.207 0 0 0-.888-.362c-.35 0-.646.12-.887.362a1.207 1.207 0 0 0-.363.888c0 .35.12.646.362.887.242.242.538.363.888.363ZM18.525 20c-.7 0-1.642-.292-2.825-.875-1.183-.583-2.45-1.375-3.8-2.375a7.564 7.564 0 0 1-1.95.25C8 17 6.35 16.325 5 14.975c-1.35-1.35-2.025-3-2.025-4.95 0-.333.025-.667.075-1a9.18 9.18 0 0 1 .2-.975C2.267 6.7 1.48 5.437.888 4.262.296 3.089 0 2.15 0 1.45 0 1 .125.646.375.387.625.13.967 0 1.4 0c.433 0 .996.15 1.688.45.691.3 1.645.808 2.862 1.525-.35.183-.675.375-.975.575-.3.2-.592.417-.875.65a9.777 9.777 0 0 0-.925-.475c-.3-.133-.617-.275-.95-.425.3.633.62 1.25.962 1.85.342.6.705 1.192 1.088 1.775A6.775 6.775 0 0 1 6.7 3.8c.983-.517 2.067-.775 3.25-.775 1.95 0 3.604.68 4.963 2.038 1.358 1.358 2.037 3.012 2.037 4.962 0 1.183-.262 2.267-.787 3.25a6.89 6.89 0 0 1-2.138 2.425c.583.383 1.18.75 1.787 1.1.609.35 1.23.667 1.863.95-.133-.317-.27-.625-.413-.925-.141-.3-.304-.608-.487-.925.25-.283.475-.583.675-.9.2-.317.383-.642.55-.975.767 1.3 1.288 2.27 1.563 2.912.274.642.412 1.18.412 1.613 0 .483-.133.846-.4 1.087-.267.242-.617.363-1.05.363ZM11.7 13.025c.283 0 .52-.096.713-.287a.968.968 0 0 0 .287-.713.968.968 0 0 0-.287-.713.968.968 0 0 0-.713-.287.968.968 0 0 0-.712.287.968.968 0 0 0-.288.713c0 .283.096.52.288.713.191.191.429.287.712.287Zm1.25-3.5a.728.728 0 0 0 .75-.75.728.728 0 0 0-.75-.75.728.728 0 0 0-.75.75.728.728 0 0 0 .75.75Zm-3.275 5.45a45.451 45.451 0 0 1-2.45-2.275 39.166 39.166 0 0 1-2.25-2.45 4.944 4.944 0 0 0 1.45 3.275c.433.433.925.775 1.475 1.025.55.25 1.142.392 1.775.425Zm2.575-.525c.8-.417 1.45-1.02 1.95-1.813.5-.791.75-1.67.75-2.637 0-1.383-.487-2.558-1.462-3.525-.976-.967-2.155-1.45-3.538-1.45-.967 0-1.842.25-2.625.75a5.052 5.052 0 0 0-1.8 1.95 33.58 33.58 0 0 0 3.125 3.6 33.583 33.583 0 0 0 3.6 3.125Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -11,7 +11,7 @@ export interface AnnualReportAnnouncementProps {
|
|||||||
year: string;
|
year: string;
|
||||||
state: Exclude<ApiAnnualReportState, 'ineligible'>;
|
state: Exclude<ApiAnnualReportState, 'ineligible'>;
|
||||||
onRequestBuild: () => void;
|
onRequestBuild: () => void;
|
||||||
onOpen: () => void;
|
onOpen?: () => void; // This is optional when inside the modal, as it won't be shown then.
|
||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
var(--color-bg-primary);
|
var(--color-bg-primary);
|
||||||
border-bottom: 1px solid var(--color-border-primary);
|
border-bottom: 1px solid var(--color-border-primary);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@@ -34,4 +35,8 @@
|
|||||||
right: 8px;
|
right: 8px;
|
||||||
margin-inline: 0;
|
margin-inline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.modal-root__modal) & {
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -332,3 +332,7 @@ $mobile-breakpoint: 540px;
|
|||||||
left: 0;
|
left: 0;
|
||||||
mix-blend-mode: screen;
|
mix-blend-mode: screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navItemBadge {
|
||||||
|
background: var(--color-bg-brand-soft);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import { closeModal } from '@/mastodon/actions/modal';
|
|||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
import { IconButton } from '@/mastodon/components/icon_button';
|
||||||
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||||
import { me } from '@/mastodon/initial_state';
|
import { me } from '@/mastodon/initial_state';
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import {
|
||||||
|
createAppSelector,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '@/mastodon/store';
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
|
||||||
import { Archetype } from './archetype';
|
import { Archetype } from './archetype';
|
||||||
@@ -23,21 +27,26 @@ import { NewPosts } from './new_posts';
|
|||||||
|
|
||||||
const moduleClassNames = classNames.bind(styles);
|
const moduleClassNames = classNames.bind(styles);
|
||||||
|
|
||||||
|
const accountSelector = createAppSelector(
|
||||||
|
[(state) => state.accounts, (state) => state.annualReport.report],
|
||||||
|
(accounts, report) => {
|
||||||
|
if (me) {
|
||||||
|
return accounts.get(me);
|
||||||
|
}
|
||||||
|
if (report?.schema_version === 2) {
|
||||||
|
return accounts.get(report.account_id);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({
|
export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({
|
||||||
context = 'standalone',
|
context = 'standalone',
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const report = useAppSelector((state) => state.annualReport.report);
|
const report = useAppSelector((state) => state.annualReport.report);
|
||||||
const account = useAppSelector((state) => {
|
const account = useAppSelector(accountSelector);
|
||||||
if (me) {
|
|
||||||
return state.accounts.get(me);
|
|
||||||
}
|
|
||||||
if (report?.schema_version === 2) {
|
|
||||||
return state.accounts.get(report.account_id);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
const close = useCallback(() => {
|
const close = useCallback(() => {
|
||||||
dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }));
|
dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }));
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
|
import type { MouseEventHandler } from 'react';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { closeModal } from '@/mastodon/actions/modal';
|
import { closeModal } from '@/mastodon/actions/modal';
|
||||||
import { useAppDispatch } from '@/mastodon/store';
|
import {
|
||||||
|
generateReport,
|
||||||
|
selectWrapstodonYear,
|
||||||
|
} from '@/mastodon/reducers/slices/annual_report';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
import { AnnualReport } from '.';
|
import { AnnualReport } from '.';
|
||||||
|
import { AnnualReportAnnouncement } from './announcement';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const AnnualReportModal: React.FC<{
|
const AnnualReportModal: React.FC<{
|
||||||
@@ -15,17 +21,42 @@ const AnnualReportModal: React.FC<{
|
|||||||
onChangeBackgroundColor('var(--color-bg-media-base)');
|
onChangeBackgroundColor('var(--color-bg-media-base)');
|
||||||
}, [onChangeBackgroundColor]);
|
}, [onChangeBackgroundColor]);
|
||||||
|
|
||||||
|
const { state } = useAppSelector((state) => state.annualReport);
|
||||||
|
const year = useAppSelector(selectWrapstodonYear);
|
||||||
|
|
||||||
|
const showAnnouncement = year && state && state !== 'available';
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const handleCloseModal = useCallback<React.MouseEventHandler<HTMLDivElement>>(
|
|
||||||
|
const handleBuildRequest = useCallback(() => {
|
||||||
|
void dispatch(generateReport());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleCloseModal: MouseEventHandler = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (e.target === e.currentTarget)
|
if (e.target === e.currentTarget) {
|
||||||
dispatch(
|
handleClose();
|
||||||
closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }),
|
}
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[dispatch],
|
[handleClose],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Auto-close if ineligible
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === 'ineligible') {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
}, [handleClose, state]);
|
||||||
|
|
||||||
|
if (state === 'ineligible') {
|
||||||
|
// Not sure how you got here, but don't show anything.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// It's fine not to provide a keyboard handler here since there is a global
|
// It's fine not to provide a keyboard handler here since there is a global
|
||||||
// [Esc] key listener that will close open modals.
|
// [Esc] key listener that will close open modals.
|
||||||
@@ -40,7 +71,16 @@ const AnnualReportModal: React.FC<{
|
|||||||
)}
|
)}
|
||||||
onClick={handleCloseModal}
|
onClick={handleCloseModal}
|
||||||
>
|
>
|
||||||
|
{!showAnnouncement ? (
|
||||||
<AnnualReport context='modal' />
|
<AnnualReport context='modal' />
|
||||||
|
) : (
|
||||||
|
<AnnualReportAnnouncement
|
||||||
|
year={year.toString()}
|
||||||
|
state={state}
|
||||||
|
onDismiss={handleClose}
|
||||||
|
onRequestBuild={handleBuildRequest}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
55
app/javascript/mastodon/features/annual_report/nav_item.tsx
Normal file
55
app/javascript/mastodon/features/annual_report/nav_item.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import IconPlanet from '@/images/icons/icon_planet.svg?react';
|
||||||
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
|
import { Icon } from '@/mastodon/components/icon';
|
||||||
|
import { selectWrapstodonYear } from '@/mastodon/reducers/slices/annual_report';
|
||||||
|
import {
|
||||||
|
createAppSelector,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '@/mastodon/store';
|
||||||
|
|
||||||
|
import classes from './index.module.scss';
|
||||||
|
|
||||||
|
const selectReportModalOpen = createAppSelector(
|
||||||
|
[(state) => state.modal.getIn(['stack', 0, 'modalType'])],
|
||||||
|
(modalType) => modalType === 'ANNUAL_REPORT',
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AnnualReportNavItem: FC = () => {
|
||||||
|
const { state } = useAppSelector((state) => state.annualReport);
|
||||||
|
const year = useAppSelector(selectWrapstodonYear);
|
||||||
|
const active = useAppSelector(selectReportModalOpen);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
dispatch(openModal({ modalType: 'ANNUAL_REPORT', modalProps: {} }));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
if (!year || !state || state === 'ineligible') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className={classNames('column-link column-link--transparent', { active })}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<Icon icon={IconPlanet} id='wrapstodon-planet' width='24' height='24' />
|
||||||
|
<span>Wrapstodon {year}</span>
|
||||||
|
<span className={classNames('column-link__badge', classes.navItemBadge)}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.nav_item.badge'
|
||||||
|
defaultMessage='New'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -46,6 +46,8 @@ import { canViewFeed } from 'mastodon/permissions';
|
|||||||
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
||||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
|
import { AnnualReportNavItem } from '../annual_report/nav_item';
|
||||||
|
|
||||||
import { DisabledAccountBanner } from './components/disabled_account_banner';
|
import { DisabledAccountBanner } from './components/disabled_account_banner';
|
||||||
import { FollowedTagsPanel } from './components/followed_tags_panel';
|
import { FollowedTagsPanel } from './components/followed_tags_panel';
|
||||||
import { ListPanel } from './components/list_panel';
|
import { ListPanel } from './components/list_panel';
|
||||||
@@ -294,6 +296,8 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
|||||||
|
|
||||||
<FollowRequestsLink />
|
<FollowRequestsLink />
|
||||||
|
|
||||||
|
<AnnualReportNavItem />
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<ListPanel />
|
<ListPanel />
|
||||||
|
|||||||
@@ -118,6 +118,7 @@
|
|||||||
"annual_report.announcement.action_view": "View my Wrapstodon",
|
"annual_report.announcement.action_view": "View my Wrapstodon",
|
||||||
"annual_report.announcement.description": "Discover more about your engagement on Mastodon over the past year.",
|
"annual_report.announcement.description": "Discover more about your engagement on Mastodon over the past year.",
|
||||||
"annual_report.announcement.title": "Wrapstodon {year} has arrived",
|
"annual_report.announcement.title": "Wrapstodon {year} has arrived",
|
||||||
|
"annual_report.nav_item.badge": "New",
|
||||||
"annual_report.shared_page.donate": "Donate",
|
"annual_report.shared_page.donate": "Donate",
|
||||||
"annual_report.shared_page.footer": "Generated with {heart} by the Mastodon team",
|
"annual_report.shared_page.footer": "Generated with {heart} by the Mastodon team",
|
||||||
"annual_report.shared_page.sign_up": "Sign up",
|
"annual_report.shared_page.sign_up": "Sign up",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
'files': ['app/javascript/styles/entrypoints/mailer.scss'],
|
files: ['app/javascript/styles/entrypoints/mailer.scss'],
|
||||||
rules: {
|
rules: {
|
||||||
'property-no-unknown': [
|
'property-no-unknown': [
|
||||||
true,
|
true,
|
||||||
@@ -42,5 +42,14 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ['app/javascript/**/*.module.scss'],
|
||||||
|
rules: {
|
||||||
|
'selector-pseudo-class-no-unknown': [
|
||||||
|
true,
|
||||||
|
{ ignorePseudoClasses: ['global'] },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user