diff --git a/src/features/explorer/components/explorerFilter.tsx b/src/features/explorer/components/explorerFilter.tsx index 496123b5b..96b37c6d4 100644 --- a/src/features/explorer/components/explorerFilter.tsx +++ b/src/features/explorer/components/explorerFilter.tsx @@ -11,12 +11,14 @@ import Text from 'soapbox/components/ui/text.tsx'; import { CreateFilter, LanguageFilter, + MediaFilter, PlatformFilters, - ToggleFilter, + ToggleRepliesFilter, generateFilter, } from 'soapbox/features/explorer/components/filters.tsx'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts'; import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts'; +import { IFilters } from 'soapbox/reducers/search-filter.ts'; const messages = defineMessages({ filters: { id: 'column.explorer.filters', defaultMessage: 'Filters:' }, @@ -28,6 +30,15 @@ interface IGenerateFilter { value: string; } +export const formatFilters = (filters: IFilters[]): string => { + const language = filters[0].name.toLowerCase() !== 'default' ? filters[0].value : ''; + const protocols = filters.slice(1, 4).filter((protocol) => !protocol.status).map((filter) => filter.value).join(' '); + const defaultFilters = filters.slice(4, 8).filter((x) => x.status).map((filter) => filter.value).join(' '); + const newFilters = filters.slice(8).map((searchFilter) => searchFilter.value).join(' '); + + return [language, protocols, defaultFilters, newFilters].join(' ').trim(); +}; + const ExplorerFilter = () => { const dispatch = useAppDispatch(); const filters = useAppSelector((state) => state.search_filter); @@ -36,14 +47,7 @@ const ExplorerFilter = () => { useEffect( () => { - const language = filters[0].name.toLowerCase() !== 'default' ? filters[0].value : ''; - const protocols = filters.slice(1, 4).filter((protocol) => !protocol.status).map((filter) => filter.value).join(' '); - const defaultFilters = filters.slice(4, 7).filter((x) => x.status).map((filter) => filter.value).join(' '); - const newFilters = filters.slice(7) - .map((searchFilter) => searchFilter.value) - .join(' '); - - const value = [ language, protocols, defaultFilters, newFilters ].join(' '); + const value = formatFilters(filters); dispatch(changeSearch(value)); dispatch(submitSearch(undefined, value)); @@ -60,7 +64,7 @@ const ExplorerFilter = () => { {intl.formatMessage(messages.filters)} - {filters.length > 0 && [...filters.slice(0, 7).filter((value) => value.status).map((value) => generateFilter(dispatch, value)), ...filters.slice(7).map((value) => generateFilter(dispatch, value))]} + {filters.length > 0 && [...filters.slice(0, 8).filter((value) => value.status).map((value) => generateFilter(dispatch, value)), ...filters.slice(8).map((value) => generateFilter(dispatch, value))]} { {/* Show Reply toggle */} - + {/* Media toggle */} - + - {/* Video toggle */} - {/* Language */} diff --git a/src/features/explorer/components/filters.tsx b/src/features/explorer/components/filters.tsx index 4637f3c7a..fb5deda32 100644 --- a/src/features/explorer/components/filters.tsx +++ b/src/features/explorer/components/filters.tsx @@ -2,7 +2,7 @@ import searchIcon from '@tabler/icons/outline/search.svg'; import xIcon from '@tabler/icons/outline/x.svg'; import clsx from 'clsx'; import { useState } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import Button from 'soapbox/components/ui/button.tsx'; import Checkbox from 'soapbox/components/ui/checkbox.tsx'; @@ -17,14 +17,13 @@ import { IGenerateFilter } from 'soapbox/features/explorer/components/explorerFi import { SelectDropdown } from 'soapbox/features/forms/index.tsx'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts'; import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts'; -import { changeLanguage, createFilter, handleToggle, removeFilter, selectProtocol } from 'soapbox/reducers/search-filter.ts'; +import { changeLanguage, changeMedia, createFilter, handleToggleReplies, removeFilter, selectProtocol } from 'soapbox/reducers/search-filter.ts'; import { AppDispatch, RootState } from 'soapbox/store.ts'; import toast from 'soapbox/toast.tsx'; const messages = defineMessages({ - showReplies: { id: 'column.explorer.filters.show_replies', defaultMessage: 'Show replies:' }, - showMedia: { id: 'column.explorer.filters.show_text_posts', defaultMessage: 'Just text posts:' }, - showVideo: { id: 'column.explorer.filters.show_video_posts', defaultMessage: 'Just posts with video:' }, + noReplies: { id: 'column.explorer.filters.no_replies', defaultMessage: 'No Replies:' }, + media: { id: 'column.explorer.filters.media', defaultMessage: 'Media:' }, language: { id: 'column.explorer.filters.language', defaultMessage: 'Language:' }, platforms: { id: 'column.explorer.filters.platforms', defaultMessage: 'Platforms:' }, createYourFilter: { id: 'column.explorer.filters.create_your_filter', defaultMessage: 'Create your filter' }, @@ -36,6 +35,10 @@ const messages = defineMessages({ fediverse: { id: 'column.explorer.filters.fediverse', defaultMessage: 'Fediverse' }, cancel: { id: 'column.explorer.filters.cancel', defaultMessage: 'Cancel' }, addFilter: { id: 'column.explorer.filters.add_filter', defaultMessage: 'Add Filter' }, + all: { id: 'column.explorer.media_filters.all', defaultMessage: 'All' }, + textOnly: { id: 'column.explorer.media_filters.text', defaultMessage: 'Text only' }, + videoOnly: { id: 'column.explorer.media_filters.video', defaultMessage: 'Video only' }, + none: { id: 'column.explorer.media_filters.none', defaultMessage: 'No media' }, }); const languages = { @@ -133,7 +136,7 @@ const PlatformFilters = () => { checked={checked} onChange={handleProtocolFilter} /> - + {intl.formatMessage(message)} @@ -142,7 +145,7 @@ const PlatformFilters = () => { return ( - + {intl.formatMessage(messages.platforms)} @@ -178,12 +181,12 @@ const CreateFilter = () => { return ( - + {intl.formatMessage(messages.createYourFilter)} - - + + {intl.formatMessage(messages.filterByWords)} @@ -191,7 +194,7 @@ const CreateFilter = () => {
- setInputValue(e.target.value)} /> + setInputValue(e.target.value)} />
{ setInclude(true); }} /> - + {intl.formatMessage(messages.include)} @@ -234,7 +237,7 @@ const CreateFilter = () => { setInclude(false); }} /> - + {intl.formatMessage(messages.exclude)} @@ -262,9 +265,49 @@ const CreateFilter = () => { }; +const MediaFilter = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const filters = useAppSelector((state) => state.search_filter).slice(4, 8); + + const mediaFilters = { + all: intl.formatMessage(messages.all), + text: intl.formatMessage(messages.textOnly), + video: intl.formatMessage(messages.videoOnly), + none: intl.formatMessage(messages.none), + }; + + + const defaultValue = (Object.keys(mediaFilters) as Array).find((key) => mediaFilters[key] === filters.find((filter) => filter.status === true)?.name) || mediaFilters.all; + + const handleSelectChange: React.ChangeEventHandler = e => { + const filter = e.target.value; + dispatch(changeMedia(filter)); + }; + + return ( + + + {intl.formatMessage(messages.media)} + + + + + ); + +}; + const LanguageFilter = () => { const intl = useIntl(); const dispatch = useAppDispatch(); + const languageFilter = useAppSelector((state) => state.search_filter)[0]; + + const defaultValue = languageFilter.name.toLowerCase(); const handleSelectChange: React.ChangeEventHandler = e => { const language = e.target.value; @@ -273,14 +316,14 @@ const LanguageFilter = () => { return ( - + {intl.formatMessage(messages.language)} @@ -288,39 +331,27 @@ const LanguageFilter = () => { }; -const ToggleFilter = ({ type }: {type: 'reply' | 'media' | 'video'}) => { +const ToggleRepliesFilter = () => { const intl = useIntl(); const dispatch = useAppDispatch(); - const filterType = type.toLowerCase(); - let label; - - switch (type) { - case 'reply': - label = intl.formatMessage(messages.showReplies); - break; - case 'media': - label = intl.formatMessage(messages.showMedia); - break; - default: - label = intl.formatMessage(messages.showVideo); - } + const label = intl.formatMessage(messages.noReplies); const filters = useAppSelector((state) => state.search_filter); - const repliesFilter = filters.find((filter) => filter.name.toLowerCase() === filterType); + const repliesFilter = filters.find((filter) => filter.value.toLowerCase().includes('reply')); const checked = repliesFilter?.status; - const handleToggleComponent = () => { - dispatch(handleToggle({ type: filterType, checked: !checked })); + const handleToggle = () => { + dispatch(handleToggleReplies({ checked: !checked })); }; return ( - + {label} ); @@ -341,9 +372,10 @@ const generateFilter = (dispatch: AppDispatch, { name, status }: IGenerateFilter textColor = 'text-gray-500'; } else { switch (nameLowCase) { - case 'reply': - case 'media': - case 'video': + case 'no replies': + case 'text only': + case 'video only': + case 'no media': borderColor = 'border-gray-500'; textColor = 'text-gray-500'; break; @@ -371,7 +403,7 @@ const generateFilter = (dispatch: AppDispatch, { name, status }: IGenerateFilter key={name} className={`group m-1 flex items-center gap-0.5 whitespace-normal break-words rounded-full border-2 bg-transparent px-3 text-base font-medium shadow-sm hover:cursor-pointer ${hasButton ? 'hover:pr-1' : '' } ${borderColor} ${textColor} `} > - {name} + {name.toLowerCase() !== 'default' ? name : } {hasButton && ) => { + handleToggleReplies: (state, action: PayloadAction) => { return state.map((currentState) => { const checked = action.payload.checked; - const type = action.payload.type.toLowerCase(); - return currentState.name.toLowerCase() === type + return currentState.value.toLowerCase().includes('reply') ? { ...currentState, status: checked, - value: `${type}:${checked}`, } : currentState; }); }, + /** + * Changes the media filter. + */ + changeMedia: (state, action: PayloadAction) => { + const selected = action.payload.toLowerCase(); + + const resetMediaState = state.map((currentFilter, index) => { + return index > 3 && index <= 7 + ? { + ...currentFilter, + status: false, + } + : currentFilter; + }); + + const applyMediaFilter = (searchFilter: string) => { + return resetMediaState.map((currentState) => + currentState.name.toLowerCase().includes(searchFilter) + ? { + ...currentState, + status: true, + } + : currentState, + ); + }; + + switch (selected) { + case 'text': + case 'video': + case 'image': + return applyMediaFilter(`${selected} only`); + case 'none': + return applyMediaFilter('no media'); + default: + return resetMediaState; + } + }, + /** * Changes the language filter. */ @@ -105,5 +141,6 @@ const search_filter = createSlice({ }, }); -export const { handleToggle, changeLanguage, selectProtocol, createFilter, removeFilter, resetFilters } = search_filter.actions; +export type { IFilters }; +export const { handleToggleReplies, changeMedia, changeLanguage, selectProtocol, createFilter, removeFilter, resetFilters } = search_filter.actions; export default search_filter.reducer; \ No newline at end of file