mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-15 08:48:53 +00:00
[Glitch] Update Wrapstodon design
Port 9d81561bb2 to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
@@ -115,6 +115,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
muted: PropTypes.bool,
|
muted: PropTypes.bool,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
unread: PropTypes.bool,
|
unread: PropTypes.bool,
|
||||||
|
showActions: PropTypes.bool,
|
||||||
prepend: PropTypes.string,
|
prepend: PropTypes.string,
|
||||||
withDismiss: PropTypes.bool,
|
withDismiss: PropTypes.bool,
|
||||||
isQuotedPost: PropTypes.bool,
|
isQuotedPost: PropTypes.bool,
|
||||||
@@ -465,7 +466,8 @@ class Status extends ImmutablePureComponent {
|
|||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
notification,
|
notification,
|
||||||
history,
|
history,
|
||||||
isQuotedPost,
|
showActions = true,
|
||||||
|
isQuotedPost = false,
|
||||||
...other
|
...other
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let attachments = null;
|
let attachments = null;
|
||||||
@@ -763,7 +765,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
{/* This is a glitch-soc addition to have a placeholder */}
|
{/* This is a glitch-soc addition to have a placeholder */}
|
||||||
{!expanded && <MentionsPlaceholder status={status} />}
|
{!expanded && <MentionsPlaceholder status={status} />}
|
||||||
|
|
||||||
{!isQuotedPost &&
|
{(showActions && !isQuotedPost) &&
|
||||||
<StatusActionBar
|
<StatusActionBar
|
||||||
status={status}
|
status={status}
|
||||||
account={status.get('account')}
|
account={status.get('account')}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { importFetchedStatuses } from '@/flavours/glitch/actions/importer';
|
|||||||
import { hydrateStore } from '@/flavours/glitch/actions/store';
|
import { hydrateStore } from '@/flavours/glitch/actions/store';
|
||||||
import type { ApiAnnualReportResponse } from '@/flavours/glitch/api/annual_report';
|
import type { ApiAnnualReportResponse } from '@/flavours/glitch/api/annual_report';
|
||||||
import { Router } from '@/flavours/glitch/components/router';
|
import { Router } from '@/flavours/glitch/components/router';
|
||||||
import { WrapstodonShare } from '@/flavours/glitch/features/annual_report/share';
|
import { WrapstodonSharedPage } from '@/flavours/glitch/features/annual_report/shared_page';
|
||||||
import { IntlProvider, loadLocale } from '@/flavours/glitch/locales';
|
import { IntlProvider, loadLocale } from '@/flavours/glitch/locales';
|
||||||
import { loadPolyfills } from '@/flavours/glitch/polyfills';
|
import { loadPolyfills } from '@/flavours/glitch/polyfills';
|
||||||
import ready from '@/flavours/glitch/ready';
|
import ready from '@/flavours/glitch/ready';
|
||||||
@@ -48,7 +48,7 @@ function loaded() {
|
|||||||
<IntlProvider>
|
<IntlProvider>
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<Router>
|
<Router>
|
||||||
<WrapstodonShare />
|
<WrapstodonSharedPage />
|
||||||
</Router>
|
</Router>
|
||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { Button } from '@/flavours/glitch/components/button';
|
import { Button } from '@/flavours/glitch/components/button';
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
@@ -12,7 +14,7 @@ export const AnnualReportAnnouncement: React.FC<{
|
|||||||
onOpen: () => void;
|
onOpen: () => void;
|
||||||
}> = ({ year, hasData, isLoading, onRequestBuild, onOpen }) => {
|
}> = ({ year, hasData, isLoading, onRequestBuild, onOpen }) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={classNames('theme-dark', styles.wrapper)}>
|
||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='annual_report.announcement.title'
|
id='annual_report.announcement.title'
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--color-text-on-media);
|
color: var(--color-text-primary);
|
||||||
background: var(--color-bg-media-base);
|
background: var(--color-bg-primary);
|
||||||
background:
|
background:
|
||||||
radial-gradient(at 40% 87%, #240c9a99 0, transparent 50%),
|
radial-gradient(at 40% 87%, #240c9a99 0, transparent 50%),
|
||||||
radial-gradient(at 19% 10%, #6b0c9a99 0, transparent 50%),
|
radial-gradient(at 19% 10%, #6b0c9a99 0, transparent 50%),
|
||||||
radial-gradient(at 90% 27%, #9a0c8299 0, transparent 50%),
|
radial-gradient(at 90% 27%, #9a0c8299 0, transparent 50%),
|
||||||
radial-gradient(at 16% 95%, #1e948299 0, transparent 50%)
|
radial-gradient(at 16% 95%, #1e948299 0, transparent 50%)
|
||||||
var(--color-bg-media-base);
|
var(--color-bg-primary);
|
||||||
border-bottom: 1px solid var(--color-border-primary);
|
border-bottom: 1px solid var(--color-border-primary);
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
|||||||
@@ -1,65 +1,215 @@
|
|||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
|
|
||||||
import type { Archetype as ArchetypeData } from '@/flavours/glitch/models/annual_report';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { Avatar } from '@/flavours/glitch/components/avatar';
|
||||||
|
import { Button } from '@/flavours/glitch/components/button';
|
||||||
|
import { me } from '@/flavours/glitch/initial_state';
|
||||||
|
import type { Account } from '@/flavours/glitch/models/account';
|
||||||
|
import type {
|
||||||
|
AnnualReport,
|
||||||
|
Archetype as ArchetypeData,
|
||||||
|
} from '@/flavours/glitch/models/annual_report';
|
||||||
import booster from '@/images/archetypes/booster.png';
|
import booster from '@/images/archetypes/booster.png';
|
||||||
import lurker from '@/images/archetypes/lurker.png';
|
import lurker from '@/images/archetypes/lurker.png';
|
||||||
import oracle from '@/images/archetypes/oracle.png';
|
import oracle from '@/images/archetypes/oracle.png';
|
||||||
import pollster from '@/images/archetypes/pollster.png';
|
import pollster from '@/images/archetypes/pollster.png';
|
||||||
import replier from '@/images/archetypes/replier.png';
|
import replier from '@/images/archetypes/replier.png';
|
||||||
|
import space_elements from '@/images/archetypes/space_elements.png';
|
||||||
|
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
import { ShareButton } from './share_button';
|
||||||
|
|
||||||
export const archetypeNames = defineMessages<ArchetypeData>({
|
export const archetypeNames = defineMessages<ArchetypeData>({
|
||||||
booster: {
|
booster: {
|
||||||
id: 'annual_report.summary.archetype.booster',
|
id: 'annual_report.summary.archetype.booster.name',
|
||||||
defaultMessage: 'The cool-hunter',
|
defaultMessage: 'The Archer',
|
||||||
},
|
},
|
||||||
replier: {
|
replier: {
|
||||||
id: 'annual_report.summary.archetype.replier',
|
id: 'annual_report.summary.archetype.replier.name',
|
||||||
defaultMessage: 'The social butterfly',
|
defaultMessage: 'The Butterfly',
|
||||||
},
|
},
|
||||||
pollster: {
|
pollster: {
|
||||||
id: 'annual_report.summary.archetype.pollster',
|
id: 'annual_report.summary.archetype.pollster.name',
|
||||||
defaultMessage: 'The pollster',
|
defaultMessage: 'The Wonderer',
|
||||||
},
|
},
|
||||||
lurker: {
|
lurker: {
|
||||||
id: 'annual_report.summary.archetype.lurker',
|
id: 'annual_report.summary.archetype.lurker.name',
|
||||||
defaultMessage: 'The lurker',
|
defaultMessage: 'The Stoic',
|
||||||
},
|
},
|
||||||
oracle: {
|
oracle: {
|
||||||
id: 'annual_report.summary.archetype.oracle',
|
id: 'annual_report.summary.archetype.oracle.name',
|
||||||
defaultMessage: 'The oracle',
|
defaultMessage: 'The Oracle',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Archetype: React.FC<{
|
export const archetypeSelfDescriptions = defineMessages<ArchetypeData>({
|
||||||
data: ArchetypeData;
|
booster: {
|
||||||
}> = ({ data }) => {
|
id: 'annual_report.summary.archetype.booster.desc_self',
|
||||||
const intl = useIntl();
|
defaultMessage:
|
||||||
let illustration;
|
'You stayed on the hunt for posts to boost, amplifying other creators with perfect aim.',
|
||||||
|
},
|
||||||
|
replier: {
|
||||||
|
id: 'annual_report.summary.archetype.replier.desc_self',
|
||||||
|
defaultMessage:
|
||||||
|
'You frequently replied to other people’s posts, pollinating Mastodon with new discussions.',
|
||||||
|
},
|
||||||
|
pollster: {
|
||||||
|
id: 'annual_report.summary.archetype.pollster.desc_self',
|
||||||
|
defaultMessage:
|
||||||
|
'You created more polls than other post types, cultivating curiosity on Mastodon.',
|
||||||
|
},
|
||||||
|
lurker: {
|
||||||
|
id: 'annual_report.summary.archetype.lurker.desc_self',
|
||||||
|
defaultMessage:
|
||||||
|
'We know you were out there, somewhere, enjoying Mastodon in your own quiet way.',
|
||||||
|
},
|
||||||
|
oracle: {
|
||||||
|
id: 'annual_report.summary.archetype.oracle.desc_self',
|
||||||
|
defaultMessage:
|
||||||
|
'You created new posts more than replies, keeping Mastodon fresh and future-facing.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
switch (data) {
|
export const archetypePublicDescriptions = defineMessages<ArchetypeData>({
|
||||||
case 'booster':
|
booster: {
|
||||||
illustration = booster;
|
id: 'annual_report.summary.archetype.booster.desc_public',
|
||||||
break;
|
defaultMessage:
|
||||||
case 'replier':
|
'{name} stayed on the hunt for posts to boost, amplifying other creators with perfect aim.',
|
||||||
illustration = replier;
|
},
|
||||||
break;
|
replier: {
|
||||||
case 'pollster':
|
id: 'annual_report.summary.archetype.replier.desc_public',
|
||||||
illustration = pollster;
|
defaultMessage:
|
||||||
break;
|
'{name} frequently replied to other people’s posts, pollinating Mastodon with new discussions.',
|
||||||
case 'lurker':
|
},
|
||||||
illustration = lurker;
|
pollster: {
|
||||||
break;
|
id: 'annual_report.summary.archetype.pollster.desc_public',
|
||||||
case 'oracle':
|
defaultMessage:
|
||||||
illustration = oracle;
|
'{name} created more polls than other post types, cultivating curiosity on Mastodon.',
|
||||||
break;
|
},
|
||||||
}
|
lurker: {
|
||||||
|
id: 'annual_report.summary.archetype.lurker.desc_public',
|
||||||
|
defaultMessage:
|
||||||
|
'We know {name} was out there, somewhere, enjoying Mastodon in their own quiet way.',
|
||||||
|
},
|
||||||
|
oracle: {
|
||||||
|
id: 'annual_report.summary.archetype.oracle.desc_public',
|
||||||
|
defaultMessage:
|
||||||
|
'{name} created new posts more than replies, keeping Mastodon fresh and future-facing.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const illustrations = {
|
||||||
|
booster,
|
||||||
|
replier,
|
||||||
|
pollster,
|
||||||
|
lurker,
|
||||||
|
oracle,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const Archetype: React.FC<{
|
||||||
|
report: AnnualReport;
|
||||||
|
account?: Account;
|
||||||
|
canShare: boolean;
|
||||||
|
}> = ({ report, account, canShare }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
const isSelfView = account?.id === me;
|
||||||
|
|
||||||
|
const [isRevealed, setIsRevealed] = useState(!isSelfView);
|
||||||
|
const reveal = useCallback(() => {
|
||||||
|
setIsRevealed(true);
|
||||||
|
wrapperRef.current?.focus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const archetype = report.data.archetype;
|
||||||
|
const descriptions = isSelfView
|
||||||
|
? archetypeSelfDescriptions
|
||||||
|
: archetypePublicDescriptions;
|
||||||
|
|
||||||
|
const name = account?.display_name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='annual-report__bento__box annual-report__summary__archetype'>
|
<div
|
||||||
<div className='annual-report__summary__archetype__label'>
|
className={classNames(styles.box, styles.archetype)}
|
||||||
{intl.formatMessage(archetypeNames[data])}
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
||||||
|
tabIndex={0}
|
||||||
|
ref={wrapperRef}
|
||||||
|
>
|
||||||
|
<div className={styles.archetypeArtboard}>
|
||||||
|
{account && (
|
||||||
|
<Avatar
|
||||||
|
account={account}
|
||||||
|
size={50}
|
||||||
|
className={styles.archetypeAvatar}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={styles.archetypeIllustrationWrapper}>
|
||||||
|
<img
|
||||||
|
src={illustrations[archetype]}
|
||||||
|
alt=''
|
||||||
|
className={classNames(
|
||||||
|
styles.archetypeIllustration,
|
||||||
|
isRevealed ? '' : styles.blurredImage,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
src={space_elements}
|
||||||
|
alt=''
|
||||||
|
className={styles.archetypePlanetRing}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<img src={illustration} alt='' />
|
<div className={classNames(styles.content, styles.comfortable)}>
|
||||||
|
<h2 className={styles.title}>
|
||||||
|
{isSelfView ? (
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.archetype.title_self'
|
||||||
|
defaultMessage='Your archetype'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.archetype.title_public'
|
||||||
|
defaultMessage="{name}'s archetype"
|
||||||
|
values={{ name }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</h2>
|
||||||
|
<p className={styles.statLarge}>
|
||||||
|
{isRevealed ? (
|
||||||
|
intl.formatMessage(archetypeNames[archetype])
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.archetype.die_drei_fragezeichen'
|
||||||
|
defaultMessage='???'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{isRevealed ? (
|
||||||
|
intl.formatMessage(descriptions[archetype], {
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.archetype.reveal_description'
|
||||||
|
defaultMessage='Thanks for being part of Mastodon! Time to find out which archetype you embodied in {year}.'
|
||||||
|
values={{ year: report.year }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{!isRevealed && (
|
||||||
|
<Button onClick={reveal}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.archetype.reveal'
|
||||||
|
defaultMessage='Reveal my archetype'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isRevealed && canShare && <ShareButton report={report} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,68 +1,24 @@
|
|||||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||||
|
|
||||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
import styles from './index.module.scss';
|
||||||
import type { TimeSeriesMonth } from 'flavours/glitch/models/annual_report';
|
|
||||||
|
|
||||||
export const Followers: React.FC<{
|
export const Followers: React.FC<{
|
||||||
data: TimeSeriesMonth[];
|
count: number;
|
||||||
total?: number;
|
}> = ({ count }) => {
|
||||||
}> = ({ data, total }) => {
|
|
||||||
const change = data.reduce((sum, item) => sum + item.followers, 0);
|
|
||||||
|
|
||||||
const cumulativeGraph = data.reduce(
|
|
||||||
(newData, item) => [
|
|
||||||
...newData,
|
|
||||||
item.followers + (newData[newData.length - 1] ?? 0),
|
|
||||||
],
|
|
||||||
[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='annual-report__bento__box annual-report__summary__followers'>
|
<div className={classNames(styles.box, styles.followers, styles.content)}>
|
||||||
<Sparklines data={cumulativeGraph} margin={0}>
|
<div className={styles.statLarge}>
|
||||||
<svg>
|
<FormattedNumber value={count} />
|
||||||
<defs>
|
</div>
|
||||||
<linearGradient id='gradient' x1='0%' y1='0%' x2='0%' y2='100%'>
|
|
||||||
<stop
|
|
||||||
offset='0%'
|
|
||||||
stopColor='var(--sparkline-gradient-top)'
|
|
||||||
stopOpacity='1'
|
|
||||||
/>
|
|
||||||
<stop
|
|
||||||
offset='100%'
|
|
||||||
stopColor='var(--sparkline-gradient-bottom)'
|
|
||||||
stopOpacity='0'
|
|
||||||
/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<SparklinesCurve style={{ fill: 'none' }} />
|
<div className={styles.title}>
|
||||||
</Sparklines>
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.followers.new_followers'
|
||||||
<div className='annual-report__summary__followers__foreground'>
|
defaultMessage='{count, plural, one {new follower} other {new followers}}'
|
||||||
<div className='annual-report__summary__followers__number'>
|
values={{ count }}
|
||||||
{change > -1 ? '+' : '-'}
|
/>
|
||||||
<FormattedNumber value={change} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='annual-report__summary__followers__label'>
|
|
||||||
<span>
|
|
||||||
<FormattedMessage
|
|
||||||
id='annual_report.summary.followers.followers'
|
|
||||||
defaultMessage='followers'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<div className='annual-report__summary__followers__footnote'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='annual_report.summary.followers.total'
|
|
||||||
defaultMessage='{count} total'
|
|
||||||
values={{ count: <ShortNumber value={total ?? 0} /> }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,106 +1,77 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-return,
|
/* eslint-disable @typescript-eslint/no-unsafe-return,
|
||||||
@typescript-eslint/no-explicit-any,
|
@typescript-eslint/no-explicit-any,
|
||||||
@typescript-eslint/no-unsafe-assignment */
|
@typescript-eslint/no-unsafe-assignment,
|
||||||
|
@typescript-eslint/no-unsafe-member-access,
|
||||||
import { useCallback } from 'react';
|
@typescript-eslint/no-unsafe-call */
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { DisplayName } from '@/flavours/glitch/components/display_name';
|
import classNames from 'classnames';
|
||||||
import { toggleStatusSpoilers } from 'flavours/glitch/actions/statuses';
|
|
||||||
import { DetailedStatus } from 'flavours/glitch/features/status/components/detailed_status';
|
import { StatusQuoteManager } from 'flavours/glitch/components/status_quoted';
|
||||||
import { me } from 'flavours/glitch/initial_state';
|
|
||||||
import type { TopStatuses } from 'flavours/glitch/models/annual_report';
|
import type { TopStatuses } from 'flavours/glitch/models/annual_report';
|
||||||
import {
|
import { makeGetStatus } from 'flavours/glitch/selectors';
|
||||||
makeGetStatus,
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
makeGetPictureInPicture,
|
|
||||||
} from 'flavours/glitch/selectors';
|
import styles from './index.module.scss';
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
|
||||||
|
|
||||||
const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any;
|
const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any;
|
||||||
const getPictureInPicture = makeGetPictureInPicture() as unknown as (
|
|
||||||
arg0: any,
|
|
||||||
arg1: any,
|
|
||||||
) => any;
|
|
||||||
|
|
||||||
export const HighlightedPost: React.FC<{
|
export const HighlightedPost: React.FC<{
|
||||||
data: TopStatuses;
|
data: TopStatuses;
|
||||||
}> = ({ data }) => {
|
}> = ({ data }) => {
|
||||||
let statusId, label;
|
const { by_reblogs, by_favourites, by_replies } = data;
|
||||||
|
|
||||||
if (data.by_reblogs) {
|
const statusId = by_reblogs || by_favourites || by_replies;
|
||||||
statusId = data.by_reblogs;
|
|
||||||
label = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='annual_report.summary.highlighted_post.by_reblogs'
|
|
||||||
defaultMessage='most boosted post'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (data.by_favourites) {
|
|
||||||
statusId = data.by_favourites;
|
|
||||||
label = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='annual_report.summary.highlighted_post.by_favourites'
|
|
||||||
defaultMessage='most favourited post'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
statusId = data.by_replies;
|
|
||||||
label = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='annual_report.summary.highlighted_post.by_replies'
|
|
||||||
defaultMessage='post with the most replies'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const domain = useAppSelector((state) => state.meta.get('domain'));
|
|
||||||
const status = useAppSelector((state) =>
|
const status = useAppSelector((state) =>
|
||||||
statusId ? getStatus(state, { id: statusId }) : undefined,
|
statusId ? getStatus(state, { id: statusId }) : undefined,
|
||||||
);
|
);
|
||||||
const pictureInPicture = useAppSelector((state) =>
|
|
||||||
statusId ? getPictureInPicture(state, { id: statusId }) : undefined,
|
|
||||||
);
|
|
||||||
const account = useAppSelector((state) =>
|
|
||||||
me ? state.accounts.get(me) : undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleToggleHidden = useCallback(() => {
|
|
||||||
dispatch(toggleStatusSpoilers(statusId));
|
|
||||||
}, [dispatch, statusId]);
|
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
return (
|
return <div className={classNames(styles.box, styles.mostBoostedPost)} />;
|
||||||
<div className='annual-report__bento__box annual-report__summary__most-boosted-post' />
|
}
|
||||||
|
|
||||||
|
let label;
|
||||||
|
if (by_reblogs) {
|
||||||
|
label = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.highlighted_post.boost_count'
|
||||||
|
defaultMessage='This post was boosted {count, plural, one {once} other {# times}}.'
|
||||||
|
values={{ count: status.get('reblogs_count') }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (by_favourites) {
|
||||||
|
label = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.highlighted_post.favourite_count'
|
||||||
|
defaultMessage='This post was favorited {count, plural, one {once} other {# times}}.'
|
||||||
|
values={{ count: status.get('favourites_count') }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
label = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.highlighted_post.reply_count'
|
||||||
|
defaultMessage='This post got {count, plural, one {one reply} other {# replies}}.'
|
||||||
|
values={{ count: status.get('replies_count') }}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayName = (
|
|
||||||
<span className='display-name'>
|
|
||||||
<strong className='display-name__html'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='annual_report.summary.highlighted_post.possessive'
|
|
||||||
defaultMessage="{name}'s"
|
|
||||||
values={{
|
|
||||||
name: <DisplayName account={account} variant='simple' />,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</strong>
|
|
||||||
<span className='display-name__account'>{label}</span>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='annual-report__bento__box annual-report__summary__most-boosted-post'>
|
<div className={classNames(styles.box, styles.mostBoostedPost)}>
|
||||||
<DetailedStatus
|
<div className={styles.content}>
|
||||||
status={status}
|
<h2 className={styles.title}>
|
||||||
pictureInPicture={pictureInPicture}
|
<FormattedMessage
|
||||||
domain={domain}
|
id='annual_report.summary.highlighted_post.title'
|
||||||
onToggleHidden={handleToggleHidden}
|
defaultMessage='Most popular post'
|
||||||
overrideDisplayName={displayName}
|
/>
|
||||||
expanded={false}
|
</h2>
|
||||||
/>
|
<p>{label}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StatusQuoteManager showActions={false} id={`${statusId}`} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,283 @@
|
|||||||
|
.modalWrapper {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px;
|
||||||
|
overflow-y: auto;
|
||||||
|
pointer-events: none;
|
||||||
|
scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary);
|
||||||
|
|
||||||
|
.loading-indicator .circular-progress {
|
||||||
|
color: var(--lime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 100%;
|
||||||
|
|
||||||
|
--default-icon-color: var(--color-bg-primary);
|
||||||
|
--default-bg-color: var(--color-text-primary);
|
||||||
|
--hover-icon-color: var(--color-bg-primary);
|
||||||
|
--hover-bg-color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
position: relative;
|
||||||
|
max-width: 600px;
|
||||||
|
padding: 24px;
|
||||||
|
contain: layout;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
pointer-events: auto;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
background: var(--color-bg-primary);
|
||||||
|
background:
|
||||||
|
radial-gradient(at 40% 87%, #240c9a99 0, transparent 50%),
|
||||||
|
radial-gradient(at 19% 10%, #6b0c9a99 0, transparent 50%),
|
||||||
|
radial-gradient(at 90% 27%, #9a0c8299 0, transparent 50%),
|
||||||
|
radial-gradient(at 16% 95%, #1e948299 0, transparent 50%),
|
||||||
|
radial-gradient(at 80% 91%, #16dae499 0, transparent 50%)
|
||||||
|
var(--color-bg-primary);
|
||||||
|
border-radius: 40px;
|
||||||
|
|
||||||
|
@media (width < 600px) {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: -1;
|
||||||
|
background: inherit;
|
||||||
|
border-radius: inherit;
|
||||||
|
filter: blur(20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: monospace;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stack {
|
||||||
|
--grid-spacing: 12px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
gap: var(--grid-spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
position: relative;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgb(from var(--color-bg-primary) r g b / 60%);
|
||||||
|
box-shadow: inset 0 0 0 1px rgb(from var(--color-text-primary) r g b / 40%);
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset-inline: 0;
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
transparent,
|
||||||
|
white,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
text-wrap: balance;
|
||||||
|
|
||||||
|
&.comfortable {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #c2c8ff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statLarge {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statExtraLarge {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mostBoostedPost {
|
||||||
|
padding: 0;
|
||||||
|
padding-top: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statsGrid {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--grid-spacing);
|
||||||
|
grid-template-columns: 1fr 2fr;
|
||||||
|
grid-template-areas:
|
||||||
|
'followers hashtag'
|
||||||
|
'new-posts hashtag';
|
||||||
|
|
||||||
|
@media (width < 680px) {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-areas:
|
||||||
|
'followers new-posts'
|
||||||
|
'hashtag hashtag';
|
||||||
|
}
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.onlyHashtag {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-areas: 'hashtag';
|
||||||
|
}
|
||||||
|
|
||||||
|
&.noHashtag {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-areas: 'followers new-posts';
|
||||||
|
}
|
||||||
|
|
||||||
|
&.singleNumber {
|
||||||
|
grid-template-columns: 1fr 2fr;
|
||||||
|
grid-template-areas: 'number hashtag';
|
||||||
|
|
||||||
|
@media (width < 680px) {
|
||||||
|
grid-template-areas:
|
||||||
|
'number number'
|
||||||
|
'hashtag hashtag';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.singleNumber.noHashtag {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-areas: 'number';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.followers {
|
||||||
|
grid-area: followers;
|
||||||
|
|
||||||
|
.singleNumber & {
|
||||||
|
grid-area: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.newPosts {
|
||||||
|
grid-area: new-posts;
|
||||||
|
|
||||||
|
.singleNumber & {
|
||||||
|
grid-area: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mostUsedHashtag {
|
||||||
|
grid-area: hashtag;
|
||||||
|
padding-block: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archetype {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archetypeArtboard {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
align-self: center;
|
||||||
|
width: 180px;
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archetypeAvatar {
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
left: 4px;
|
||||||
|
border-radius: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archetypeIllustrationWrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 92px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 100%;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
box-shadow: inset -10px -4px 15px #00000080;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.archetypeIllustration {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blurredImage {
|
||||||
|
filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.archetypePlanetRing {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
}
|
||||||
@@ -1,95 +1,123 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { defineMessage, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessage, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { focusCompose, resetCompose } from '@/flavours/glitch/actions/compose';
|
import { useLocation } from 'react-router';
|
||||||
|
|
||||||
|
import classNames from 'classnames/bind';
|
||||||
|
|
||||||
import { closeModal } from '@/flavours/glitch/actions/modal';
|
import { closeModal } from '@/flavours/glitch/actions/modal';
|
||||||
import { Button } from '@/flavours/glitch/components/button';
|
import { IconButton } from '@/flavours/glitch/components/icon_button';
|
||||||
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
|
||||||
import { me } from '@/flavours/glitch/initial_state';
|
import { me } from '@/flavours/glitch/initial_state';
|
||||||
import type { AnnualReport as AnnualReportData } from '@/flavours/glitch/models/annual_report';
|
|
||||||
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||||
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
|
||||||
import { Archetype, archetypeNames } from './archetype';
|
import { Archetype } from './archetype';
|
||||||
import { Followers } from './followers';
|
import { Followers } from './followers';
|
||||||
import { HighlightedPost } from './highlighted_post';
|
import { HighlightedPost } from './highlighted_post';
|
||||||
|
import styles from './index.module.scss';
|
||||||
import { MostUsedHashtag } from './most_used_hashtag';
|
import { MostUsedHashtag } from './most_used_hashtag';
|
||||||
import { NewPosts } from './new_posts';
|
import { NewPosts } from './new_posts';
|
||||||
|
|
||||||
const shareMessage = defineMessage({
|
const moduleClassNames = classNames.bind(styles);
|
||||||
|
|
||||||
|
export const shareMessage = defineMessage({
|
||||||
id: 'annual_report.summary.share_message',
|
id: 'annual_report.summary.share_message',
|
||||||
defaultMessage: 'I got the {archetype} archetype!',
|
defaultMessage: 'I got the {archetype} archetype!',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Share = false when using the embedded version of the report.
|
// Share = false when using the embedded version of the report.
|
||||||
export const AnnualReport: FC<{ share?: boolean }> = ({ share = true }) => {
|
export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({
|
||||||
const currentAccount = useAppSelector((state) =>
|
context = 'standalone',
|
||||||
me ? state.accounts.get(me) : undefined,
|
}) => {
|
||||||
);
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const report = useAppSelector((state) => state.annualReport.report);
|
const report = useAppSelector((state) => state.annualReport.report);
|
||||||
|
const account = useAppSelector((state) => {
|
||||||
|
if (me) {
|
||||||
|
return state.accounts.get(me);
|
||||||
|
}
|
||||||
|
if (report?.schema_version === 2) {
|
||||||
|
return state.accounts.get(report.account_id);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const close = useCallback(() => {
|
||||||
|
dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
// Close modal when navigating away from within
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const [initialPathname] = useState(pathname);
|
||||||
|
useEffect(() => {
|
||||||
|
if (pathname !== initialPathname) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}, [pathname, initialPathname, close]);
|
||||||
|
|
||||||
if (!report) {
|
if (!report) {
|
||||||
return <LoadingIndicator />;
|
return <LoadingIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newPostCount = report.data.time_series.reduce(
|
||||||
|
(sum, item) => sum + item.statuses,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newFollowerCount = report.data.time_series.reduce(
|
||||||
|
(sum, item) => sum + item.followers,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const topHashtag = report.data.top_hashtags[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='annual-report'>
|
<div className={moduleClassNames(styles.wrapper, 'theme-dark')}>
|
||||||
<div className='annual-report__header'>
|
<div className={styles.header}>
|
||||||
<h1>
|
<h1>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='annual_report.summary.thanks'
|
id='annual_report.summary.title'
|
||||||
defaultMessage='Thanks for being part of Mastodon!'
|
defaultMessage='Wrapstodon {year}'
|
||||||
/>
|
|
||||||
</h1>
|
|
||||||
<p>
|
|
||||||
<FormattedMessage
|
|
||||||
id='annual_report.summary.here_it_is'
|
|
||||||
defaultMessage='Here is your {year} in review:'
|
|
||||||
values={{ year: report.year }}
|
values={{ year: report.year }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</h1>
|
||||||
|
{account && <p>@{account.acct}</p>}
|
||||||
|
{context === 'modal' && (
|
||||||
|
<IconButton
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'annual_report.summary.close',
|
||||||
|
defaultMessage: 'Close',
|
||||||
|
})}
|
||||||
|
className={styles.closeButton}
|
||||||
|
icon='close'
|
||||||
|
iconComponent={CloseIcon}
|
||||||
|
onClick={close}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='annual-report__bento annual-report__summary'>
|
<div className={styles.stack}>
|
||||||
<Archetype data={report.data.archetype} />
|
|
||||||
<HighlightedPost data={report.data.top_statuses} />
|
<HighlightedPost data={report.data.top_statuses} />
|
||||||
<Followers
|
<div
|
||||||
data={report.data.time_series}
|
className={moduleClassNames(styles.statsGrid, {
|
||||||
total={currentAccount?.followers_count}
|
noHashtag: !topHashtag,
|
||||||
|
onlyHashtag: !(newFollowerCount && newPostCount),
|
||||||
|
singleNumber: !!newFollowerCount !== !!newPostCount,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{!!newFollowerCount && <Followers count={newFollowerCount} />}
|
||||||
|
{!!newPostCount && <NewPosts count={newPostCount} />}
|
||||||
|
{topHashtag && <MostUsedHashtag hashtag={topHashtag} />}
|
||||||
|
</div>
|
||||||
|
<Archetype
|
||||||
|
report={report}
|
||||||
|
account={account}
|
||||||
|
canShare={context === 'modal'}
|
||||||
/>
|
/>
|
||||||
<MostUsedHashtag data={report.data.top_hashtags} />
|
|
||||||
<NewPosts data={report.data.time_series} />
|
|
||||||
{share && <ShareButton report={report} />}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const handleShareClick = useCallback(() => {
|
|
||||||
// Generate the share message.
|
|
||||||
const archetypeName = intl.formatMessage(
|
|
||||||
archetypeNames[report.data.archetype],
|
|
||||||
);
|
|
||||||
const shareLines = [
|
|
||||||
intl.formatMessage(shareMessage, {
|
|
||||||
archetype: archetypeName,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
// Share URL is only available for schema version 2.
|
|
||||||
if (report.schema_version === 2 && report.share_url) {
|
|
||||||
shareLines.push(report.share_url);
|
|
||||||
}
|
|
||||||
shareLines.push(`#Wrapstodon${report.year}`);
|
|
||||||
|
|
||||||
// Reset the composer and focus it with the share message, then close the modal.
|
|
||||||
dispatch(resetCompose());
|
|
||||||
dispatch(focusCompose(shareLines.join('\n\n')));
|
|
||||||
dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }));
|
|
||||||
}, [report, intl, dispatch]);
|
|
||||||
|
|
||||||
return <Button text='Share here' onClick={handleShareClick} />;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { AnnualReport } from '.';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
const AnnualReportModal: React.FC<{
|
||||||
|
onChangeBackgroundColor: (color: string) => void;
|
||||||
|
}> = ({ onChangeBackgroundColor }) => {
|
||||||
|
useEffect(() => {
|
||||||
|
onChangeBackgroundColor('var(--color-bg-media-base)');
|
||||||
|
}, [onChangeBackgroundColor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'modal-root__modal',
|
||||||
|
styles.modalWrapper,
|
||||||
|
'theme-dark',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AnnualReport context='modal' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default AnnualReportModal;
|
||||||
@@ -1,30 +1,34 @@
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import type { NameAndCount } from 'flavours/glitch/models/annual_report';
|
import type { NameAndCount } from 'flavours/glitch/models/annual_report';
|
||||||
|
|
||||||
export const MostUsedHashtag: React.FC<{
|
import styles from './index.module.scss';
|
||||||
data: NameAndCount[];
|
|
||||||
}> = ({ data }) => {
|
|
||||||
const hashtag = data[0];
|
|
||||||
|
|
||||||
|
export const MostUsedHashtag: React.FC<{
|
||||||
|
hashtag: NameAndCount;
|
||||||
|
}> = ({ hashtag }) => {
|
||||||
return (
|
return (
|
||||||
<div className='annual-report__bento__box annual-report__summary__most-used-hashtag'>
|
<div
|
||||||
<div className='annual-report__summary__most-used-hashtag__hashtag'>
|
className={classNames(styles.box, styles.mostUsedHashtag, styles.content)}
|
||||||
{hashtag ? (
|
>
|
||||||
<>#{hashtag.name}</>
|
<div className={styles.title}>
|
||||||
) : (
|
|
||||||
<FormattedMessage
|
|
||||||
id='annual_report.summary.most_used_hashtag.none'
|
|
||||||
defaultMessage='None'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className='annual-report__summary__most-used-hashtag__label'>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='annual_report.summary.most_used_hashtag.most_used_hashtag'
|
id='annual_report.summary.most_used_hashtag.most_used_hashtag'
|
||||||
defaultMessage='most used hashtag'
|
defaultMessage='Most used hashtag'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.statExtraLarge}>#{hashtag.name}</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id='annual_report.summary.most_used_hashtag.used_count'
|
||||||
|
defaultMessage='You included this hashtag in {count, plural, one {one post} other {# posts}}.'
|
||||||
|
values={{ count: hashtag.count }}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,51 +1,23 @@
|
|||||||
import { FormattedNumber, FormattedMessage } from 'react-intl';
|
import { FormattedNumber, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import ChatBubbleIcon from '@/material-icons/400-24px/chat_bubble.svg?react';
|
import classNames from 'classnames';
|
||||||
import type { TimeSeriesMonth } from 'flavours/glitch/models/annual_report';
|
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
export const NewPosts: React.FC<{
|
export const NewPosts: React.FC<{
|
||||||
data: TimeSeriesMonth[];
|
count: number;
|
||||||
}> = ({ data }) => {
|
}> = ({ count }) => {
|
||||||
const posts = data.reduce((sum, item) => sum + item.statuses, 0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='annual-report__bento__box annual-report__summary__new-posts'>
|
<div className={classNames(styles.box, styles.newPosts, styles.content)}>
|
||||||
<svg width={500} height={500}>
|
<div className={styles.statLarge}>
|
||||||
<defs>
|
<FormattedNumber value={count} />
|
||||||
<pattern
|
|
||||||
id='posts'
|
|
||||||
x='0'
|
|
||||||
y='0'
|
|
||||||
width='32'
|
|
||||||
height='35'
|
|
||||||
patternUnits='userSpaceOnUse'
|
|
||||||
>
|
|
||||||
<circle cx='12' cy='12' r='12' fill='var(--lime)' />
|
|
||||||
<ChatBubbleIcon
|
|
||||||
fill='var(--indigo-1)'
|
|
||||||
x='4'
|
|
||||||
y='4'
|
|
||||||
width='16'
|
|
||||||
height='16'
|
|
||||||
/>
|
|
||||||
</pattern>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<rect
|
|
||||||
width={500}
|
|
||||||
height={500}
|
|
||||||
fill='url(#posts)'
|
|
||||||
style={{ opacity: 0.2 }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<div className='annual-report__summary__new-posts__number'>
|
|
||||||
<FormattedNumber value={posts} />
|
|
||||||
</div>
|
</div>
|
||||||
<div className='annual-report__summary__new-posts__label'>
|
|
||||||
|
<div className={styles.title}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='annual_report.summary.new_posts.new_posts'
|
id='annual_report.summary.new_posts.new_posts'
|
||||||
defaultMessage='new posts'
|
defaultMessage='{count, plural, one {new post} other {new posts}}'
|
||||||
|
values={{ count }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { resetCompose, focusCompose } from '@/flavours/glitch/actions/compose';
|
||||||
|
import { closeModal } from '@/flavours/glitch/actions/modal';
|
||||||
|
import { Button } from '@/flavours/glitch/components/button';
|
||||||
|
import type { AnnualReport as AnnualReportData } from '@/flavours/glitch/models/annual_report';
|
||||||
|
import { useAppDispatch } from '@/flavours/glitch/store';
|
||||||
|
|
||||||
|
import { shareMessage } from '.';
|
||||||
|
import { archetypeNames } from './archetype';
|
||||||
|
|
||||||
|
export const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const handleShareClick = useCallback(() => {
|
||||||
|
// Generate the share message.
|
||||||
|
const archetypeName = intl.formatMessage(
|
||||||
|
archetypeNames[report.data.archetype],
|
||||||
|
);
|
||||||
|
const shareLines = [
|
||||||
|
intl.formatMessage(shareMessage, {
|
||||||
|
archetype: archetypeName,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
// Share URL is only available for schema version 2.
|
||||||
|
if (report.schema_version === 2 && report.share_url) {
|
||||||
|
shareLines.push(report.share_url);
|
||||||
|
}
|
||||||
|
shareLines.push(`#Wrapstodon${report.year}`);
|
||||||
|
|
||||||
|
// Reset the composer and focus it with the share message, then close the modal.
|
||||||
|
dispatch(resetCompose());
|
||||||
|
dispatch(focusCompose(shareLines.join('\n\n')));
|
||||||
|
dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }));
|
||||||
|
}, [report, intl, dispatch]);
|
||||||
|
|
||||||
|
return <Button text='Share here' onClick={handleShareClick} />;
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
.wrapper {
|
.wrapper {
|
||||||
max-width: 40rem;
|
max-width: max-content;
|
||||||
margin: 0 auto;
|
margin: 40px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
@@ -3,12 +3,12 @@ import type { FC } from 'react';
|
|||||||
import { IconLogo } from '@/flavours/glitch/components/logo';
|
import { IconLogo } from '@/flavours/glitch/components/logo';
|
||||||
|
|
||||||
import { AnnualReport } from './index';
|
import { AnnualReport } from './index';
|
||||||
import classes from './share.module.css';
|
import classes from './shared_page.module.css';
|
||||||
|
|
||||||
export const WrapstodonShare: FC = () => {
|
export const WrapstodonSharedPage: FC = () => {
|
||||||
return (
|
return (
|
||||||
<main className={classes.wrapper}>
|
<main className={classes.wrapper}>
|
||||||
<AnnualReport share={false} />
|
<AnnualReport />
|
||||||
<footer className={classes.footer}>
|
<footer className={classes.footer}>
|
||||||
<IconLogo className={classes.logo} />
|
<IconLogo className={classes.logo} />
|
||||||
Generated with ♥ by the Mastodon team
|
Generated with ♥ by the Mastodon team
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { AnnualReport } from 'flavours/glitch/features/annual_report';
|
|
||||||
|
|
||||||
const AnnualReportModal: React.FC<{
|
|
||||||
onChangeBackgroundColor: (color: string) => void;
|
|
||||||
}> = ({ onChangeBackgroundColor }) => {
|
|
||||||
useEffect(() => {
|
|
||||||
onChangeBackgroundColor('var(--indigo-1)');
|
|
||||||
}, [onChangeBackgroundColor]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal annual-report-modal'>
|
|
||||||
<AnnualReport />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
|
||||||
export default AnnualReportModal;
|
|
||||||
@@ -231,7 +231,7 @@ export function LinkTimeline () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function AnnualReportModal () {
|
export function AnnualReportModal () {
|
||||||
return import('../components/annual_report_modal');
|
return import('../../annual_report/modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListEdit () {
|
export function ListEdit () {
|
||||||
|
|||||||
@@ -55,5 +55,6 @@ export type AnnualReport = {
|
|||||||
schema_version: 2;
|
schema_version: 2;
|
||||||
data: AnnualReportV2;
|
data: AnnualReportV2;
|
||||||
share_url: string | null;
|
share_url: string | null;
|
||||||
|
account_id: string;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
@use 'mastodon/polls';
|
@use 'mastodon/polls';
|
||||||
@use 'mastodon/modal';
|
@use 'mastodon/modal';
|
||||||
@use 'mastodon/emoji_picker';
|
@use 'mastodon/emoji_picker';
|
||||||
@use 'mastodon/annual_reports';
|
|
||||||
@use 'mastodon/about';
|
@use 'mastodon/about';
|
||||||
@use 'mastodon/tables';
|
@use 'mastodon/tables';
|
||||||
@use 'mastodon/admin';
|
@use 'mastodon/admin';
|
||||||
|
|||||||
@@ -1,342 +0,0 @@
|
|||||||
@use 'variables' as *;
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--indigo-1: #17063b;
|
|
||||||
--indigo-2: #2f0c7a;
|
|
||||||
--indigo-3: #562cfc;
|
|
||||||
--indigo-5: #858afa;
|
|
||||||
--indigo-6: #cccfff;
|
|
||||||
--lime: #baff3b;
|
|
||||||
--goldenrod-2: #ffc954;
|
|
||||||
}
|
|
||||||
|
|
||||||
.annual-report {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
background: var(--indigo-1);
|
|
||||||
padding: 24px;
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 25px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 30px;
|
|
||||||
color: var(--lime);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 20px;
|
|
||||||
color: var(--indigo-6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__bento {
|
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
|
|
||||||
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto) minmax(
|
|
||||||
0,
|
|
||||||
auto
|
|
||||||
);
|
|
||||||
|
|
||||||
&__box {
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--indigo-2);
|
|
||||||
color: var(--indigo-5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__summary {
|
|
||||||
&__most-boosted-post {
|
|
||||||
grid-column: span 2;
|
|
||||||
grid-row: span 2;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.status__content,
|
|
||||||
.content-warning {
|
|
||||||
color: var(--indigo-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.detailed-status {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-warning {
|
|
||||||
border: 0;
|
|
||||||
background: var(--indigo-1);
|
|
||||||
|
|
||||||
.link-button {
|
|
||||||
color: var(--indigo-5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.detailed-status__meta__line {
|
|
||||||
border-bottom-color: var(--indigo-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.detailed-status__meta {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detailed-status__meta,
|
|
||||||
.poll__footer,
|
|
||||||
.poll__link,
|
|
||||||
.detailed-status .logo,
|
|
||||||
.detailed-status__display-name {
|
|
||||||
color: var(--indigo-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.detailed-status__meta .animated-number,
|
|
||||||
.detailed-status__display-name strong {
|
|
||||||
color: var(--indigo-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.poll__chart {
|
|
||||||
background-color: var(--indigo-3);
|
|
||||||
|
|
||||||
&.leading {
|
|
||||||
background-color: var(--goldenrod-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-card,
|
|
||||||
.hashtag-bar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__followers {
|
|
||||||
grid-column: span 1;
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
padding-block-start: 24px;
|
|
||||||
padding-block-end: 24px;
|
|
||||||
|
|
||||||
--sparkline-gradient-top: rgba(86, 44, 252, 50%);
|
|
||||||
--sparkline-gradient-bottom: rgba(86, 44, 252, 0%);
|
|
||||||
|
|
||||||
&__foreground {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__number {
|
|
||||||
font-size: 31px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 37px;
|
|
||||||
color: var(--lime);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 17px;
|
|
||||||
color: var(--indigo-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footnote {
|
|
||||||
display: block;
|
|
||||||
font-weight: 400;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
inset-inline-end: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 0;
|
|
||||||
height: 70%;
|
|
||||||
width: auto;
|
|
||||||
|
|
||||||
path:first-child {
|
|
||||||
fill: url('#gradient') !important;
|
|
||||||
fill-opacity: 1 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
path:last-child {
|
|
||||||
stroke: var(--color-graph-primary-stroke) !important;
|
|
||||||
fill: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__archetype {
|
|
||||||
grid-column: span 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
padding: 16px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 17px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--lime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__most-used-app {
|
|
||||||
grid-column: span 1;
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 17px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--indigo-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 17px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--goldenrod-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__percentile {
|
|
||||||
grid-row: span 2;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
text-align: center;
|
|
||||||
text-wrap: balance;
|
|
||||||
padding: 16px 8px;
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__number {
|
|
||||||
font-size: 54px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 73px;
|
|
||||||
color: var(--goldenrod-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footnote {
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 14px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__new-posts {
|
|
||||||
grid-column: span 2;
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 24px;
|
|
||||||
color: var(--indigo-6);
|
|
||||||
z-index: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__number {
|
|
||||||
font-size: 76px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 91px;
|
|
||||||
color: var(--goldenrod-2);
|
|
||||||
z-index: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
position: absolute;
|
|
||||||
inset-inline-start: -7px;
|
|
||||||
top: -4px;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__most-used-hashtag {
|
|
||||||
grid-column: span 2;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&__hashtag {
|
|
||||||
font-size: 42px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 58px;
|
|
||||||
color: var(--indigo-6);
|
|
||||||
margin-inline-start: -100%;
|
|
||||||
margin-inline-end: -100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 17px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.annual-report-modal {
|
|
||||||
max-width: 600px;
|
|
||||||
background: var(--indigo-1);
|
|
||||||
border-radius: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
.loading-indicator .circular-progress {
|
|
||||||
color: var(--lime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $no-columns-breakpoint) {
|
|
||||||
border-bottom: 0;
|
|
||||||
border-radius: 16px 16px 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-group--annual-report {
|
|
||||||
.notification-group__icon {
|
|
||||||
color: var(--lime);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-group__main .link-button {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--lime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user