From 56e092927dada4d94a13582a20712e2157f52836 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 16 Jun 2025 17:06:33 +0200 Subject: [PATCH] [Glitch] Change order of items in navigation panel in web UI Port 7c4393e719c681184b09b97df718539002f89fab to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/actions/lists.js | 28 +--- .../flavours/glitch/actions/lists_typed.ts | 6 +- app/javascript/flavours/glitch/api/lists.ts | 2 + app/javascript/flavours/glitch/api/tags.ts | 3 +- .../glitch/components/navigation_portal.tsx | 6 - .../getting_started/components/trends.jsx | 54 ------- .../containers/trends_container.js | 15 -- .../glitch/features/getting_started/index.jsx | 4 +- .../glitch/features/list_adder/index.tsx | 2 +- .../flavours/glitch/features/lists/index.tsx | 2 +- .../components/collapsible_panel.tsx | 85 +++++++++++ .../components/disabled_account_banner.tsx | 88 ++++++++++++ .../components/followed_tags_panel.tsx | 69 +++++++++ .../components/list_panel.tsx | 66 +++++++++ .../components/more_link.tsx | 9 -- .../components/more_link.tsx.orig | 0 .../components/sign_in_banner.tsx | 105 ++++++++++++++ .../navigation_panel/components/trends.tsx | 57 ++++++++ .../index.tsx} | 136 +++++++++--------- .../features/ui/components/columns_area.jsx | 2 +- .../ui/components/disabled_account_banner.jsx | 85 ----------- .../features/ui/components/list_panel.tsx | 92 ------------ .../features/ui/components/sign_in_banner.jsx | 56 -------- .../flavours/glitch/reducers/lists.ts | 11 +- .../flavours/glitch/styles/components.scss | 85 +++++++---- 25 files changed, 617 insertions(+), 451 deletions(-) delete mode 100644 app/javascript/flavours/glitch/components/navigation_portal.tsx delete mode 100644 app/javascript/flavours/glitch/features/getting_started/components/trends.jsx delete mode 100644 app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js create mode 100644 app/javascript/flavours/glitch/features/navigation_panel/components/collapsible_panel.tsx create mode 100644 app/javascript/flavours/glitch/features/navigation_panel/components/disabled_account_banner.tsx create mode 100644 app/javascript/flavours/glitch/features/navigation_panel/components/followed_tags_panel.tsx create mode 100644 app/javascript/flavours/glitch/features/navigation_panel/components/list_panel.tsx rename app/javascript/flavours/glitch/features/{ui => navigation_panel}/components/more_link.tsx (94%) create mode 100644 app/javascript/flavours/glitch/features/navigation_panel/components/more_link.tsx.orig create mode 100644 app/javascript/flavours/glitch/features/navigation_panel/components/sign_in_banner.tsx create mode 100644 app/javascript/flavours/glitch/features/navigation_panel/components/trends.tsx rename app/javascript/flavours/glitch/features/{ui/components/navigation_panel.tsx => navigation_panel/index.tsx} (91%) delete mode 100644 app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/list_panel.tsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx diff --git a/app/javascript/flavours/glitch/actions/lists.js b/app/javascript/flavours/glitch/actions/lists.js index f9abc2e769..a4bbfff229 100644 --- a/app/javascript/flavours/glitch/actions/lists.js +++ b/app/javascript/flavours/glitch/actions/lists.js @@ -4,14 +4,12 @@ export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST'; export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS'; export const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL'; -export const LISTS_FETCH_REQUEST = 'LISTS_FETCH_REQUEST'; -export const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS'; -export const LISTS_FETCH_FAIL = 'LISTS_FETCH_FAIL'; - export const LIST_DELETE_REQUEST = 'LIST_DELETE_REQUEST'; export const LIST_DELETE_SUCCESS = 'LIST_DELETE_SUCCESS'; export const LIST_DELETE_FAIL = 'LIST_DELETE_FAIL'; +export * from './lists_typed'; + export const fetchList = id => (dispatch, getState) => { if (getState().getIn(['lists', id])) { return; @@ -40,28 +38,6 @@ export const fetchListFail = (id, error) => ({ error, }); -export const fetchLists = () => (dispatch) => { - dispatch(fetchListsRequest()); - - api().get('/api/v1/lists') - .then(({ data }) => dispatch(fetchListsSuccess(data))) - .catch(err => dispatch(fetchListsFail(err))); -}; - -export const fetchListsRequest = () => ({ - type: LISTS_FETCH_REQUEST, -}); - -export const fetchListsSuccess = lists => ({ - type: LISTS_FETCH_SUCCESS, - lists, -}); - -export const fetchListsFail = error => ({ - type: LISTS_FETCH_FAIL, - error, -}); - export const deleteList = id => (dispatch) => { dispatch(deleteListRequest(id)); diff --git a/app/javascript/flavours/glitch/actions/lists_typed.ts b/app/javascript/flavours/glitch/actions/lists_typed.ts index 8f5dc0ffa3..cc61f34399 100644 --- a/app/javascript/flavours/glitch/actions/lists_typed.ts +++ b/app/javascript/flavours/glitch/actions/lists_typed.ts @@ -1,4 +1,4 @@ -import { apiCreate, apiUpdate } from 'flavours/glitch/api/lists'; +import { apiCreate, apiUpdate, apiGetLists } from 'flavours/glitch/api/lists'; import type { List } from 'flavours/glitch/models/list'; import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions'; @@ -11,3 +11,7 @@ export const updateList = createDataLoadingThunk( 'list/update', (list: Partial) => apiUpdate(list), ); + +export const fetchLists = createDataLoadingThunk('lists/fetch', () => + apiGetLists(), +); diff --git a/app/javascript/flavours/glitch/api/lists.ts b/app/javascript/flavours/glitch/api/lists.ts index 64a0af01f5..1b12c558c9 100644 --- a/app/javascript/flavours/glitch/api/lists.ts +++ b/app/javascript/flavours/glitch/api/lists.ts @@ -13,6 +13,8 @@ export const apiCreate = (list: Partial) => export const apiUpdate = (list: Partial) => apiRequestPut(`v1/lists/${list.id}`, list); +export const apiGetLists = () => apiRequestGet('v1/lists'); + export const apiGetAccounts = (listId: string) => apiRequestGet(`v1/lists/${listId}/accounts`, { limit: 0, diff --git a/app/javascript/flavours/glitch/api/tags.ts b/app/javascript/flavours/glitch/api/tags.ts index 2eb53a98db..b1bdcdcfd9 100644 --- a/app/javascript/flavours/glitch/api/tags.ts +++ b/app/javascript/flavours/glitch/api/tags.ts @@ -20,10 +20,11 @@ export const apiFeatureTag = (tagId: string) => export const apiUnfeatureTag = (tagId: string) => apiRequestPost(`v1/tags/${tagId}/unfeature`); -export const apiGetFollowedTags = async (url?: string) => { +export const apiGetFollowedTags = async (url?: string, limit?: number) => { const response = await api().request({ method: 'GET', url: url ?? '/api/v1/followed_tags', + params: { limit }, }); return { diff --git a/app/javascript/flavours/glitch/components/navigation_portal.tsx b/app/javascript/flavours/glitch/components/navigation_portal.tsx deleted file mode 100644 index aa5d4bf61f..0000000000 --- a/app/javascript/flavours/glitch/components/navigation_portal.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import Trends from 'flavours/glitch/features/getting_started/containers/trends_container'; -import { showTrends } from 'flavours/glitch/initial_state'; - -export const NavigationPortal: React.FC = () => ( -
{showTrends && }
-); diff --git a/app/javascript/flavours/glitch/features/getting_started/components/trends.jsx b/app/javascript/flavours/glitch/features/getting_started/components/trends.jsx deleted file mode 100644 index c5bb92b3be..0000000000 --- a/app/javascript/flavours/glitch/features/getting_started/components/trends.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import PropTypes from 'prop-types'; - -import { FormattedMessage } from 'react-intl'; - -import { Link } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; - -export default class Trends extends ImmutablePureComponent { - - static defaultProps = { - loading: false, - }; - - static propTypes = { - trends: ImmutablePropTypes.list, - fetchTrends: PropTypes.func.isRequired, - }; - - componentDidMount () { - this.props.fetchTrends(); - this.refreshInterval = setInterval(() => this.props.fetchTrends(), 900 * 1000); - } - - componentWillUnmount () { - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - } - - render () { - const { trends } = this.props; - - if (!trends || trends.isEmpty()) { - return null; - } - - return ( -
-

- - - -

- - {trends.take(3).map(hashtag => )} -
- ); - } - -} diff --git a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js b/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js deleted file mode 100644 index cd08b61d40..0000000000 --- a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; - -import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends'; - -import Trends from '../components/trends'; - -const mapStateToProps = state => ({ - trends: state.getIn(['trends', 'tags', 'items']), -}); - -const mapDispatchToProps = dispatch => ({ - fetchTrends: () => dispatch(fetchTrendingHashtags()), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Trends); diff --git a/app/javascript/flavours/glitch/features/getting_started/index.jsx b/app/javascript/flavours/glitch/features/getting_started/index.jsx index 6530318c23..288a1d5e17 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.jsx +++ b/app/javascript/flavours/glitch/features/getting_started/index.jsx @@ -38,7 +38,7 @@ import { NavigationBar } from '../compose/components/navigation_bar'; import { ColumnLink } from '../ui/components/column_link'; import ColumnSubheading from '../ui/components/column_subheading'; -import TrendsContainer from './containers/trends_container'; +import { Trends } from 'flavours/glitch/features/navigation_panel/components/trends'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -209,7 +209,7 @@ class GettingStarted extends ImmutablePureComponent { - {(multiColumn && showTrends) && } + {(multiColumn && showTrends) && } {intl.formatMessage(messages.menu)} diff --git a/app/javascript/flavours/glitch/features/list_adder/index.tsx b/app/javascript/flavours/glitch/features/list_adder/index.tsx index af52349672..68e973464e 100644 --- a/app/javascript/flavours/glitch/features/list_adder/index.tsx +++ b/app/javascript/flavours/glitch/features/list_adder/index.tsx @@ -122,7 +122,7 @@ const ListAdder: React.FC<{ const [listIds, setListIds] = useState([]); useEffect(() => { - dispatch(fetchLists()); + void dispatch(fetchLists()); apiGetAccountLists(accountId) .then((data) => { diff --git a/app/javascript/flavours/glitch/features/lists/index.tsx b/app/javascript/flavours/glitch/features/lists/index.tsx index 34a3b7c8cc..5aca35bffb 100644 --- a/app/javascript/flavours/glitch/features/lists/index.tsx +++ b/app/javascript/flavours/glitch/features/lists/index.tsx @@ -79,7 +79,7 @@ const Lists: React.FC<{ const lists = useAppSelector((state) => getOrderedLists(state)); useEffect(() => { - dispatch(fetchLists()); + void dispatch(fetchLists()); }, [dispatch]); const emptyMessage = ( diff --git a/app/javascript/flavours/glitch/features/navigation_panel/components/collapsible_panel.tsx b/app/javascript/flavours/glitch/features/navigation_panel/components/collapsible_panel.tsx new file mode 100644 index 0000000000..580da6d235 --- /dev/null +++ b/app/javascript/flavours/glitch/features/navigation_panel/components/collapsible_panel.tsx @@ -0,0 +1,85 @@ +import { useState, useCallback, useId } from 'react'; + +import KeyboardArrowDownIcon from '@/material-icons/400-24px/keyboard_arrow_down.svg?react'; +import KeyboardArrowUpIcon from '@/material-icons/400-24px/keyboard_arrow_up.svg?react'; +import type { IconProp } from 'flavours/glitch/components/icon'; +import { IconButton } from 'flavours/glitch/components/icon_button'; +import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; +import { ColumnLink } from 'flavours/glitch/features/ui/components/column_link'; + +export const CollapsiblePanel: React.FC<{ + children: React.ReactNode[]; + to: string; + title: string; + collapseTitle: string; + expandTitle: string; + icon: string; + iconComponent: IconProp; + activeIconComponent?: IconProp; + loading?: boolean; +}> = ({ + children, + to, + icon, + iconComponent, + activeIconComponent, + title, + collapseTitle, + expandTitle, + loading, +}) => { + const [expanded, setExpanded] = useState(false); + const accessibilityId = useId(); + + const handleClick = useCallback(() => { + setExpanded((value) => !value); + }, [setExpanded]); + + return ( +
+
+ + + {(loading || children.length > 0) && ( + <> +
+ + + + )} +
+ + {children.length > 0 && expanded && ( +
+ {children} +
+ )} +
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/navigation_panel/components/disabled_account_banner.tsx b/app/javascript/flavours/glitch/features/navigation_panel/components/disabled_account_banner.tsx new file mode 100644 index 0000000000..fd93aa0bf6 --- /dev/null +++ b/app/javascript/flavours/glitch/features/navigation_panel/components/disabled_account_banner.tsx @@ -0,0 +1,88 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import { openModal } from 'flavours/glitch/actions/modal'; +import { + disabledAccountId, + movedToAccountId, + domain, +} from 'flavours/glitch/initial_state'; +import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; + +export const DisabledAccountBanner: React.FC = () => { + const disabledAccount = useAppSelector((state) => + disabledAccountId ? state.accounts.get(disabledAccountId) : undefined, + ); + const movedToAccount = useAppSelector((state) => + movedToAccountId ? state.accounts.get(movedToAccountId) : undefined, + ); + const dispatch = useAppDispatch(); + + const handleLogOutClick = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} })); + + return false; + }, + [dispatch], + ); + + const disabledAccountLink = ( + + {disabledAccount?.acct}@{domain} + + ); + + return ( +
+

+ {movedToAccount ? ( + + {movedToAccount.acct.includes('@') + ? movedToAccount.acct + : `${movedToAccount.acct}@${domain}`} + + ), + }} + /> + ) : ( + + )} +

+ + + + +
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/navigation_panel/components/followed_tags_panel.tsx b/app/javascript/flavours/glitch/features/navigation_panel/components/followed_tags_panel.tsx new file mode 100644 index 0000000000..b9070ae4e0 --- /dev/null +++ b/app/javascript/flavours/glitch/features/navigation_panel/components/followed_tags_panel.tsx @@ -0,0 +1,69 @@ +import { useEffect, useState } from 'react'; + +import { useIntl, defineMessages } from 'react-intl'; + +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; +import { apiGetFollowedTags } from 'flavours/glitch/api/tags'; +import type { ApiHashtagJSON } from 'flavours/glitch/api_types/tags'; +import { ColumnLink } from 'flavours/glitch/features/ui/components/column_link'; + +import { CollapsiblePanel } from './collapsible_panel'; + +const messages = defineMessages({ + followedTags: { + id: 'navigation_bar.followed_tags', + defaultMessage: 'Followed hashtags', + }, + expand: { + id: 'navigation_panel.expand_followed_tags', + defaultMessage: 'Expand followed hashtags menu', + }, + collapse: { + id: 'navigation_panel.collapse_followed_tags', + defaultMessage: 'Collapse followed hashtags menu', + }, +}); + +export const FollowedTagsPanel: React.FC = () => { + const intl = useIntl(); + const [tags, setTags] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setLoading(true); + + void apiGetFollowedTags(undefined, 4) + .then(({ tags }) => { + setTags(tags); + setLoading(false); + + return ''; + }) + .catch(() => { + setLoading(false); + }); + }, [setLoading, setTags]); + + return ( + + {tags.map((tag) => ( + + ))} + + ); +}; diff --git a/app/javascript/flavours/glitch/features/navigation_panel/components/list_panel.tsx b/app/javascript/flavours/glitch/features/navigation_panel/components/list_panel.tsx new file mode 100644 index 0000000000..ce6ca71b16 --- /dev/null +++ b/app/javascript/flavours/glitch/features/navigation_panel/components/list_panel.tsx @@ -0,0 +1,66 @@ +import { useEffect, useState } from 'react'; + +import { useIntl, defineMessages } from 'react-intl'; + +import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react'; +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; +import { fetchLists } from 'flavours/glitch/actions/lists'; +import { ColumnLink } from 'flavours/glitch/features/ui/components/column_link'; +import { getOrderedLists } from 'flavours/glitch/selectors/lists'; +import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; + +import { CollapsiblePanel } from './collapsible_panel'; + +const messages = defineMessages({ + lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, + expand: { + id: 'navigation_panel.expand_lists', + defaultMessage: 'Expand list menu', + }, + collapse: { + id: 'navigation_panel.collapse_lists', + defaultMessage: 'Collapse list menu', + }, +}); + +export const ListPanel: React.FC = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const lists = useAppSelector((state) => getOrderedLists(state)); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setLoading(true); + + void dispatch(fetchLists()).then(() => { + setLoading(false); + + return ''; + }); + }, [dispatch, setLoading]); + + return ( + + {lists.map((list) => ( + + ))} + + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/more_link.tsx b/app/javascript/flavours/glitch/features/navigation_panel/components/more_link.tsx similarity index 94% rename from app/javascript/flavours/glitch/features/ui/components/more_link.tsx rename to app/javascript/flavours/glitch/features/navigation_panel/components/more_link.tsx index 78d06f87c8..ec4d9e6505 100644 --- a/app/javascript/flavours/glitch/features/ui/components/more_link.tsx +++ b/app/javascript/flavours/glitch/features/navigation_panel/components/more_link.tsx @@ -15,10 +15,6 @@ import { import { useAppDispatch } from 'flavours/glitch/store'; const messages = defineMessages({ - followedTags: { - id: 'navigation_bar.followed_tags', - defaultMessage: 'Followed hashtags', - }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, domainBlocks: { id: 'navigation_bar.domain_blocks', @@ -57,11 +53,6 @@ export const MoreLink: React.FC = () => { const menu = useMemo(() => { const arr: MenuItem[] = [ - { - text: intl.formatMessage(messages.followedTags), - to: '/followed_tags', - }, - null, { text: intl.formatMessage(messages.filters), href: '/filters' }, { text: intl.formatMessage(messages.mutes), to: '/mutes' }, { text: intl.formatMessage(messages.blocks), to: '/blocks' }, diff --git a/app/javascript/flavours/glitch/features/navigation_panel/components/more_link.tsx.orig b/app/javascript/flavours/glitch/features/navigation_panel/components/more_link.tsx.orig new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/javascript/flavours/glitch/features/navigation_panel/components/sign_in_banner.tsx b/app/javascript/flavours/glitch/features/navigation_panel/components/sign_in_banner.tsx new file mode 100644 index 0000000000..ae3d9e4261 --- /dev/null +++ b/app/javascript/flavours/glitch/features/navigation_panel/components/sign_in_banner.tsx @@ -0,0 +1,105 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { openModal } from 'flavours/glitch/actions/modal'; +import { registrationsOpen, sso_redirect } from 'flavours/glitch/initial_state'; +import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; + +export const SignInBanner: React.FC = () => { + const dispatch = useAppDispatch(); + + const openClosedRegistrationsModal = useCallback( + () => + dispatch( + openModal({ modalType: 'CLOSED_REGISTRATIONS', modalProps: {} }), + ), + [dispatch], + ); + + let signupButton: React.ReactNode; + + const signupUrl = useAppSelector( + (state) => + (state.server.getIn(['server', 'registrations', 'url'], null) as + | string + | null) ?? '/auth/sign_up', + ); + + if (sso_redirect) { + return ( +
+

+ + + +

+

+ +

+ + + +
+ ); + } + + if (registrationsOpen) { + signupButton = ( + + + + ); + } else { + signupButton = ( + + ); + } + + return ( +
+

+ + + +

+

+ +

+ {signupButton} + + + +
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/navigation_panel/components/trends.tsx b/app/javascript/flavours/glitch/features/navigation_panel/components/trends.tsx new file mode 100644 index 0000000000..2773fd1c96 --- /dev/null +++ b/app/javascript/flavours/glitch/features/navigation_panel/components/trends.tsx @@ -0,0 +1,57 @@ +import { useEffect } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; + +import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends'; +import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; +import { showTrends } from 'flavours/glitch/initial_state'; +import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; + +export const Trends: React.FC = () => { + const dispatch = useAppDispatch(); + const trends = useAppSelector( + (state) => + state.trends.getIn(['tags', 'items']) as ImmutableList< + ImmutableMap + >, + ); + + useEffect(() => { + dispatch(fetchTrendingHashtags()); + + const refreshInterval = setInterval(() => { + dispatch(fetchTrendingHashtags()); + }, 900 * 1000); + + return () => { + clearInterval(refreshInterval); + }; + }, [dispatch]); + + if (!showTrends || trends.isEmpty()) { + return null; + } + + return ( +
+
+

+ + + +

+ + {trends.take(4).map((hashtag) => ( + + ))} +
+
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.tsx b/app/javascript/flavours/glitch/features/navigation_panel/index.tsx similarity index 91% rename from app/javascript/flavours/glitch/features/ui/components/navigation_panel.tsx rename to app/javascript/flavours/glitch/features/navigation_panel/index.tsx index a114bd85ea..befd0c3a57 100644 --- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.tsx +++ b/app/javascript/flavours/glitch/features/navigation_panel/index.tsx @@ -24,7 +24,6 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react import PersonAddActiveIcon from '@/material-icons/400-24px/person_add-fill.svg?react'; import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react'; -import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import SettingsIcon from '@/material-icons/400-24px/settings.svg?react'; import StarActiveIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarIcon from '@/material-icons/400-24px/star.svg?react'; @@ -37,7 +36,8 @@ import { } from 'flavours/glitch/actions/navigation'; import { Account } from 'flavours/glitch/components/account'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; -import { NavigationPortal } from 'flavours/glitch/components/navigation_portal'; +import { Search } from 'flavours/glitch/features/compose/components/search'; +import { ColumnLink } from 'flavours/glitch/features/ui/components/column_link'; import { useBreakpoint } from 'flavours/glitch/features/ui/hooks/useBreakpoint'; import { useIdentity } from 'flavours/glitch/identity_context'; import { @@ -49,11 +49,12 @@ import { transientSingleColumn } from 'flavours/glitch/is_mobile'; import { selectUnreadNotificationGroupsCount } from 'flavours/glitch/selectors/notifications'; import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; -import { ColumnLink } from './column_link'; -import DisabledAccountBanner from './disabled_account_banner'; -import { ListPanel } from './list_panel'; -import { MoreLink } from './more_link'; -import SignInBanner from './sign_in_banner'; +import { DisabledAccountBanner } from './components/disabled_account_banner'; +import { FollowedTagsPanel } from './components/followed_tags_panel'; +import { ListPanel } from './components/list_panel'; +import { MoreLink } from './components/more_link'; +import { SignInBanner } from './components/sign_in_banner'; +import { Trends } from './components/trends'; const messages = defineMessages({ home: { id: 'tabs_bar.home', defaultMessage: 'Home' }, @@ -76,6 +77,10 @@ const messages = defineMessages({ }, about: { id: 'navigation_bar.about', defaultMessage: 'About' }, search: { id: 'navigation_bar.search', defaultMessage: 'Search' }, + searchTrends: { + id: 'navigation_bar.search_trends', + defaultMessage: 'Search / Trending', + }, advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface', @@ -177,33 +182,6 @@ const FollowRequestsLink: React.FC = () => { ); }; -const SearchLink: React.FC = () => { - const intl = useIntl(); - const showAsSearch = useBreakpoint('full'); - - if (!trendsEnabled || showAsSearch) { - return ( - - ); - } - - return ( - - ); -}; - const ProfileCard: React.FC = () => { if (!me) { return null; @@ -216,6 +194,13 @@ const ProfileCard: React.FC = () => { ); }; +const isFirehoseActive = ( + match: unknown, + { pathname }: { pathname: string }, +) => { + return !!match || pathname.startsWith('/public'); +}; + const MENU_WIDTH = 284; export const NavigationPanel: React.FC = () => { @@ -224,6 +209,7 @@ export const NavigationPanel: React.FC = () => { const open = useAppSelector((state) => state.navigation.open); const dispatch = useAppDispatch(); const openable = useBreakpoint('openable'); + const showSearch = useBreakpoint('full'); const location = useLocation(); const overlayRef = useRef(null); @@ -293,13 +279,6 @@ export const NavigationPanel: React.FC = () => { }, ); - const isFirehoseActive = useCallback( - (match: unknown, location: { pathname: string }): boolean => { - return !!match || location.pathname.startsWith('/public'); - }, - [], - ); - const previouslyFocusedElementRef = useRef(); useEffect(() => { @@ -315,7 +294,7 @@ export const NavigationPanel: React.FC = () => { } }, [open]); - let banner = undefined; + let banner: React.ReactNode; if (transientSingleColumn) { banner = ( @@ -362,6 +341,8 @@ export const NavigationPanel: React.FC = () => { style={openable ? { x } : undefined} >
+ {showSearch && } + {banner &&
{banner}
} @@ -385,43 +366,49 @@ export const NavigationPanel: React.FC = () => { activeIconComponent={HomeActiveIcon} text={intl.formatMessage(messages.home)} /> - - )} - + {trendsEnabled && ( + + )} {(signedIn || timelinePreview) && ( )} - {!signedIn && ( -
-
- {disabledAccountId ? ( - - ) : ( - - )} -
- )} - {signedIn && ( <> + + + + +
+ + + + + { /> - -
{ )}
-
- { text={intl.formatMessage(messages.about)} />
+ + {!signedIn && ( +
+
+ + {disabledAccountId ? ( + + ) : ( + + )} +
+ )}
- +
diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx index 5d596b7bcb..a2e3c91980 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx @@ -25,7 +25,7 @@ import BundleColumnError from './bundle_column_error'; import { ColumnLoading } from './column_loading'; import { ComposePanel } from './compose_panel'; import DrawerLoading from './drawer_loading'; -import { NavigationPanel } from './navigation_panel'; +import { NavigationPanel } from 'flavours/glitch/features/navigation_panel'; const componentMap = { 'COMPOSE': Compose, diff --git a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx deleted file mode 100644 index 7a19983a9c..0000000000 --- a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { FormattedMessage, injectIntl } from 'react-intl'; - -import { Link } from 'react-router-dom'; - -import { connect } from 'react-redux'; - -import { openModal } from 'flavours/glitch/actions/modal'; -import { disabledAccountId, movedToAccountId, domain } from 'flavours/glitch/initial_state'; - -const mapStateToProps = (state) => ({ - disabledAcct: state.getIn(['accounts', disabledAccountId, 'acct']), - movedToAcct: movedToAccountId ? state.getIn(['accounts', movedToAccountId, 'acct']) : undefined, -}); - -const mapDispatchToProps = (dispatch) => ({ - onLogout () { - dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' })); - }, -}); - -class DisabledAccountBanner extends PureComponent { - - static propTypes = { - disabledAcct: PropTypes.string.isRequired, - movedToAcct: PropTypes.string, - onLogout: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleLogOutClick = e => { - e.preventDefault(); - e.stopPropagation(); - - this.props.onLogout(); - - return false; - }; - - render () { - const { disabledAcct, movedToAcct } = this.props; - - const disabledAccountLink = ( - - {disabledAcct}@{domain} - - ); - - return ( -
-

- {movedToAcct ? ( - {movedToAcct.includes('@') ? movedToAcct : `${movedToAcct}@${domain}`}, - }} - /> - ) : ( - - )} -

- - - - -
- ); - } - -} - -export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(DisabledAccountBanner)); diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.tsx b/app/javascript/flavours/glitch/features/ui/components/list_panel.tsx deleted file mode 100644 index bb3f9e2119..0000000000 --- a/app/javascript/flavours/glitch/features/ui/components/list_panel.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useEffect, useState, useCallback, useId } from 'react'; - -import { useIntl, defineMessages } from 'react-intl'; - -import ArrowDropDownIcon from '@/material-icons/400-24px/arrow_drop_down.svg?react'; -import ArrowLeftIcon from '@/material-icons/400-24px/arrow_left.svg?react'; -import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react'; -import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; -import { fetchLists } from 'flavours/glitch/actions/lists'; -import { IconButton } from 'flavours/glitch/components/icon_button'; -import { getOrderedLists } from 'flavours/glitch/selectors/lists'; -import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; - -import { ColumnLink } from './column_link'; - -const messages = defineMessages({ - lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, - expand: { - id: 'navigation_panel.expand_lists', - defaultMessage: 'Expand list menu', - }, - collapse: { - id: 'navigation_panel.collapse_lists', - defaultMessage: 'Collapse list menu', - }, -}); - -export const ListPanel: React.FC = () => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - const lists = useAppSelector((state) => getOrderedLists(state)); - const [expanded, setExpanded] = useState(false); - const accessibilityId = useId(); - - useEffect(() => { - dispatch(fetchLists()); - }, [dispatch]); - - const handleClick = useCallback(() => { - setExpanded((value) => !value); - }, [setExpanded]); - - return ( -
-
- - - {lists.length > 0 && ( - - )} -
- - {lists.length > 0 && expanded && ( -
- {lists.map((list) => ( - - ))} -
- )} -
- ); -}; diff --git a/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx b/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx deleted file mode 100644 index bda9aed025..0000000000 --- a/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useCallback } from 'react'; - -import { FormattedMessage } from 'react-intl'; - - -import { openModal } from 'flavours/glitch/actions/modal'; -import { registrationsOpen, sso_redirect } from 'flavours/glitch/initial_state'; -import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; - -const SignInBanner = () => { - const dispatch = useAppDispatch(); - - const openClosedRegistrationsModal = useCallback( - () => dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' })), - [dispatch], - ); - - let signupButton; - - const signupUrl = useAppSelector((state) => state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up'); - - if (sso_redirect) { - return ( -
-

-

- -
- ); - } - - if (registrationsOpen) { - signupButton = ( - - - - ); - } else { - signupButton = ( - - ); - } - - return ( -
-

-

- {signupButton} - -
- ); -}; - -export default SignInBanner; diff --git a/app/javascript/flavours/glitch/reducers/lists.ts b/app/javascript/flavours/glitch/reducers/lists.ts index a3e2ea7196..8b83b668dc 100644 --- a/app/javascript/flavours/glitch/reducers/lists.ts +++ b/app/javascript/flavours/glitch/reducers/lists.ts @@ -1,7 +1,11 @@ import type { Reducer } from '@reduxjs/toolkit'; import { Map as ImmutableMap } from 'immutable'; -import { createList, updateList } from 'flavours/glitch/actions/lists_typed'; +import { + createList, + updateList, + fetchLists, +} from 'flavours/glitch/actions/lists_typed'; import type { ApiListJSON } from 'flavours/glitch/api_types/lists'; import { createList as createListFromJSON } from 'flavours/glitch/models/list'; import type { List } from 'flavours/glitch/models/list'; @@ -9,7 +13,6 @@ import type { List } from 'flavours/glitch/models/list'; import { LIST_FETCH_SUCCESS, LIST_FETCH_FAIL, - LISTS_FETCH_SUCCESS, LIST_DELETE_SUCCESS, } from '../actions/lists'; @@ -33,12 +36,12 @@ export const listsReducer: Reducer = (state = initialState, action) => { updateList.fulfilled.match(action) ) { return normalizeList(state, action.payload); + } else if (fetchLists.fulfilled.match(action)) { + return normalizeLists(state, action.payload); } else { switch (action.type) { case LIST_FETCH_SUCCESS: return normalizeList(state, action.list as ApiListJSON); - case LISTS_FETCH_SUCCESS: - return normalizeLists(state, action.lists as ApiListJSON[]); case LIST_DELETE_SUCCESS: case LIST_FETCH_FAIL: return state.set(action.id as string, null); diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index 587a04f3b4..ddc5f15f0e 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -3202,10 +3202,9 @@ a.account__display-name { height: 100vh; } - .navigation-panel__sign-in-banner, .navigation-panel__banner, - .getting-started__trends, - .navigation-panel__logo { + .navigation-panel__logo, + .getting-started__trends { display: none; } } @@ -3539,15 +3538,24 @@ a.account__display-name { &__header { display: flex; align-items: center; - padding-inline-end: 12px; + + &__sep { + width: 0; + height: 24px; + border-left: 1px solid var(--background-border-color); + } .column-link { flex: 1 1 auto; } + + .icon-button { + padding: 8px; + } } &__items { - padding-inline-start: 24px + 5px; + padding-inline-start: 24px + 8px; .icon { display: none; @@ -3558,14 +3566,16 @@ a.account__display-name { &__compose-button { display: flex; justify-content: flex-start; - padding-top: 10px; - padding-bottom: 10px; - padding-inline-start: 13px - 7px; - padding-inline-end: 13px; - gap: 5px; + padding-top: 8px; + padding-bottom: 8px; + padding-inline-start: 12px - 7px; + padding-inline-end: 12px; + gap: 8px; margin: 12px; - margin-bottom: 4px; border-radius: 6px; + font-size: 16px; + font-weight: 600; + line-height: 18px; .icon { width: 24px; @@ -3575,7 +3585,11 @@ a.account__display-name { .navigation-bar { padding: 16px; - border-bottom: 1px solid var(--background-border-color); + } + + .search { + margin: 16px; + margin-bottom: 12px; } .logo { @@ -3587,26 +3601,36 @@ a.account__display-name { margin-bottom: 12px; } - @media screen and (height <= 710px) { - &__portal { + .getting-started__trends h4 { + padding: 10px 12px; + padding-inline-start: 16px; + } + + .getting-started__trends .trends__item { + padding: 10px 12px; + padding-inline-start: 16px; + } + + @media screen and (height <= 930px) { + &__portal .trends__item:nth-child(n + 5) { display: none; } } - @media screen and (height <= 765px) { - &__portal .trends__item:nth-child(n + 3) { - display: none; - } - } - - @media screen and (height <= 820px) { + @media screen and (height <= 930px - 56px) { &__portal .trends__item:nth-child(n + 4) { display: none; } } - @media screen and (height <= 920px) { - .column-link.column-link--optional { + @media screen and (height <= 930px - (56px * 2)) { + &__portal .trends__item:nth-child(n + 3) { + display: none; + } + } + + @media screen and (height <= 930px - (56px * 3)) { + &__portal { display: none; } } @@ -3879,9 +3903,10 @@ a.account__display-name { .column-link { display: flex; align-items: center; - gap: 5px; + gap: 8px; font-size: 16px; - padding: 13px; + font-weight: 400; + padding: 12px; text-decoration: none; overflow: hidden; white-space: nowrap; @@ -4730,7 +4755,8 @@ a.status-card { } .load-more .loading-indicator, -.button .loading-indicator { +.button .loading-indicator, +.icon-button .loading-indicator { position: static; transform: none; @@ -4741,6 +4767,13 @@ a.status-card { } } +.icon-button .loading-indicator .circular-progress { + color: lighten($ui-base-color, 26%); + width: 12px; + height: 12px; + margin: 6px; +} + .load-more .loading-indicator .circular-progress { color: lighten($ui-base-color, 26%); }