mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(catalog): add filters modal for text-only posts, nsfw boards in p/all
This commit is contained in:
@@ -2,9 +2,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
|
||||
import { isAllView, isCatalogView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
|
||||
import useSortingStore from '../../stores/use-sorting-store';
|
||||
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
||||
import useTimeFilter from '../../hooks/use-time-filter';
|
||||
import CatalogFilters from '../../views/catalog/catalog-filters/';
|
||||
import styles from './board-buttons.module.css';
|
||||
|
||||
interface BoardButtonsProps {
|
||||
@@ -82,7 +83,7 @@ const RefreshButton = () => {
|
||||
|
||||
const SortOptions = () => {
|
||||
const { t } = useTranslation();
|
||||
const { sortType, setSortType } = useCatalogFiltersStore();
|
||||
const { sortType, setSortType } = useSortingStore();
|
||||
|
||||
const handleSortChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const type = event.target.value as 'active' | 'new';
|
||||
@@ -183,20 +184,6 @@ export const MobileBoardButtons = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const TextOnlyPostsFilter = () => {
|
||||
const { showTextOnlyThreads, setShowTextOnlyThreads } = useCatalogFiltersStore();
|
||||
|
||||
return (
|
||||
<div>
|
||||
Text-Only Posts:{' '}
|
||||
<select value={showTextOnlyThreads ? 'Show' : 'Hide'} onChange={(e) => setShowTextOnlyThreads(e.target.value === 'Show')}>
|
||||
<option value='Hide'>Hide</option>
|
||||
<option value='Show'>Show</option>
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DesktopBoardButtons = () => {
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
@@ -244,7 +231,7 @@ export const DesktopBoardButtons = () => {
|
||||
{(isInAllView || isInSubscriptionsView) && (
|
||||
<TimeFilter isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} />
|
||||
)}
|
||||
{isInCatalogView && <TextOnlyPostsFilter />}
|
||||
{(isInCatalogView || isInAllView || isInSubscriptionsView) && <CatalogFilters />}{' '}
|
||||
{!(isInAllView || isInSubscriptionsView) && (
|
||||
<>
|
||||
[
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
|
||||
.settingsModal {
|
||||
top: 25px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 394px;
|
||||
height: auto;
|
||||
max-height: 80%;
|
||||
left: calc(50% - 22px);
|
||||
overflow-y: auto;
|
||||
margin-left: -178px;
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
padding: 2px;
|
||||
|
||||
@@ -35,7 +35,7 @@ const SettingsModal = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.overlay} onClick={closeModal}></div>
|
||||
<div className={styles.overlay} onClick={closeModal} />
|
||||
<div className={styles.settingsModal}>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.title}>{t('settings')}</span>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface CatalogFiltersStore {
|
||||
showAdultBoards: boolean;
|
||||
setShowAdultBoards: (value: boolean) => void;
|
||||
showGoreBoards: boolean;
|
||||
setShowGoreBoards: (value: boolean) => void;
|
||||
showTextOnlyThreads: boolean;
|
||||
setShowTextOnlyThreads: (value: boolean) => void;
|
||||
}
|
||||
@@ -11,6 +15,16 @@ const useCatalogFiltersStore = create<CatalogFiltersStore>((set) => ({
|
||||
set({ showTextOnlyThreads: value });
|
||||
localStorage.setItem('showTextOnlyThreads', value.toString());
|
||||
},
|
||||
showAdultBoards: localStorage.getItem('showAdultBoards') === 'true' ? true : false,
|
||||
setShowAdultBoards: (value: boolean) => {
|
||||
set({ showAdultBoards: value });
|
||||
localStorage.setItem('showAdultBoards', value.toString());
|
||||
},
|
||||
showGoreBoards: localStorage.getItem('showGoreBoards') === 'true' ? true : false,
|
||||
setShowGoreBoards: (value: boolean) => {
|
||||
set({ showGoreBoards: value });
|
||||
localStorage.setItem('showGoreBoards', value.toString());
|
||||
},
|
||||
}));
|
||||
|
||||
export default useCatalogFiltersStore;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.button {
|
||||
text-transform: capitalize;
|
||||
color: var(--button-desktop-text-color);
|
||||
text-decoration: var(--button-text-decoration);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
color: var(--button-desktop-text-color-hover);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal label, .modal button, .modal select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.modal {
|
||||
top: 60px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 340px;
|
||||
height: auto;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
padding: 2px;
|
||||
font-size: var(--settings-modal-font-size);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.25);
|
||||
background-color: var(--settings-modal-background-color);
|
||||
border-top: var(--settings-modal-border-top);
|
||||
border-right: var(--settings-modal-border-right);
|
||||
border-bottom: var(--settings-modal-border-bottom);
|
||||
border-left: var(--settings-modal-border-left);
|
||||
}
|
||||
|
||||
|
||||
.header {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
border-bottom: var(--settings-modal-header-border-bottom);
|
||||
}
|
||||
|
||||
.title {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
right: 5px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: block;
|
||||
background-size: 100%;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
image-rendering: pixelated;
|
||||
background-image: var(--settings-modal-close-button-background-image);
|
||||
}
|
||||
|
||||
.filters {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.filters label {
|
||||
display: block;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.filters label input[type='checkbox'] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.categoryTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.paddingBottom {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useState } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { isAllView, isCatalogView } from '../../../lib/utils/view-utils';
|
||||
import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store';
|
||||
import styles from './catalog-filters.module.css';
|
||||
|
||||
const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { showAdultBoards, setShowAdultBoards, showGoreBoards, setShowGoreBoards, showTextOnlyThreads, setShowTextOnlyThreads } = useCatalogFiltersStore();
|
||||
const location = useLocation();
|
||||
const isInCatalogView = isCatalogView(location.pathname, useParams());
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.overlay} onClick={closeModal} />
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.title}>{t('filters')}</span>
|
||||
<span className={styles.closeButton} title='close' onClick={closeModal} />
|
||||
</div>
|
||||
<div className={styles.filters}>
|
||||
{isInCatalogView && (
|
||||
<label className={styles.paddingBottom}>
|
||||
<input type='checkbox' checked={!showTextOnlyThreads} onChange={(e) => setShowTextOnlyThreads(!e.target.checked)} />
|
||||
Hide Text-Only Posts
|
||||
</label>
|
||||
)}
|
||||
{isInAllView && (
|
||||
<div className={styles.nsfwLabels}>
|
||||
<div className={styles.categoryTitle}>NSFW Boards</div>
|
||||
<label>
|
||||
<input type='checkbox' checked={!showGoreBoards} onChange={(e) => setShowGoreBoards(!e.target.checked)} />
|
||||
Hide Gore Boards
|
||||
</label>
|
||||
<label>
|
||||
<input type='checkbox' checked={!showAdultBoards} onChange={(e) => setShowAdultBoards(!e.target.checked)} />
|
||||
Hide Adult Boards
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CatalogFilters = () => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const closeModal = () => {
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
[
|
||||
<span className={styles.button} onClick={() => setShowModal(true)}>
|
||||
Filters
|
||||
</span>
|
||||
]{showModal && <FiltersModal closeModal={closeModal} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CatalogFilters;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './catalog-filters';
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useAccount, Subplebbit, useFeed, useSubplebbit, Comment } from '@plebbit/plebbit-react-hooks';
|
||||
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||
import useDefaultSubplebbits from '../../hooks/use-default-subplebbits';
|
||||
import useFeedStateString from '../../hooks/use-feed-state-string';
|
||||
import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
|
||||
import useTimeFilter from '../../hooks/use-time-filter';
|
||||
@@ -91,21 +91,39 @@ const Catalog = () => {
|
||||
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
|
||||
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
const defaultSubplebbitAddresses = useDefaultSubplebbitAddresses();
|
||||
const defaultSubplebbits = useDefaultSubplebbits();
|
||||
const { showAdultBoards, showGoreBoards } = useCatalogFiltersStore();
|
||||
|
||||
const account = useAccount();
|
||||
const subscriptions = account?.subscriptions;
|
||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname);
|
||||
|
||||
const subplebbitAddresses = useMemo(() => {
|
||||
const filteredDefaultSubplebbits = defaultSubplebbits
|
||||
.filter((subplebbit) => {
|
||||
const { tags } = subplebbit;
|
||||
const hasGoreTag = tags?.includes('gore');
|
||||
const hasAdultTag = tags?.includes('adult');
|
||||
|
||||
if ((hasGoreTag && !showGoreBoards) || (hasAdultTag && !showAdultBoards)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((subplebbit) => subplebbit.address);
|
||||
|
||||
if (isInAllView) {
|
||||
return defaultSubplebbitAddresses;
|
||||
return filteredDefaultSubplebbits;
|
||||
}
|
||||
if (isInSubscriptionsView) {
|
||||
return subscriptions || [];
|
||||
}
|
||||
return [subplebbitAddress];
|
||||
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbitAddresses, subscriptions]);
|
||||
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbits, subscriptions, showAdultBoards, showGoreBoards]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(showAdultBoards, showGoreBoards);
|
||||
}, [showAdultBoards, showGoreBoards]);
|
||||
|
||||
const columnCount = Math.floor(useWindowWidth() / columnWidth);
|
||||
// postPerPage based on columnCount for optimized feed, dont change value after first render
|
||||
|
||||
Reference in New Issue
Block a user