import { forwardRef, useCallback, useId, useMemo, useState } from 'react'; import type { FC } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import classNames from 'classnames'; import type { ApiQuotePolicy } from '@/flavours/glitch/api_types/quotes'; import { isQuotePolicy } from '@/flavours/glitch/api_types/quotes'; import { isStatusVisibility } from '@/flavours/glitch/api_types/statuses'; import type { StatusVisibility } from '@/flavours/glitch/api_types/statuses'; import { Button } from '@/flavours/glitch/components/button'; import { Dropdown } from '@/flavours/glitch/components/dropdown'; import type { SelectItem } from '@/flavours/glitch/components/dropdown_selector'; import { IconButton } from '@/flavours/glitch/components/icon_button'; import { messages as privacyMessages } from '@/flavours/glitch/features/compose/components/privacy_dropdown'; import { createAppSelector, useAppSelector } from '@/flavours/glitch/store'; import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react'; import type { BaseConfirmationModalProps } from './confirmation_modals/confirmation_modal'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, buttonTitle: { id: 'visibility_modal.button_title', defaultMessage: 'Set visibility', }, quotePublic: { id: 'visibility_modal.quote_public', defaultMessage: 'Anyone', }, quoteFollowers: { id: 'visibility_modal.quote_followers', defaultMessage: 'Followers only', }, quoteNobody: { id: 'visibility_modal.quote_nobody', defaultMessage: 'Just me', }, }); export type VisibilityModalCallback = ( visibility: StatusVisibility, quotePolicy: ApiQuotePolicy, ) => void; interface VisibilityModalProps extends BaseConfirmationModalProps { statusId?: string; onChange: VisibilityModalCallback; } const selectStatusPolicy = createAppSelector( [ (state) => state.statuses, (_state, statusId?: string) => statusId, (state) => state.compose.get('quote_policy') as ApiQuotePolicy, ], (statuses, statusId, composeQuotePolicy) => { if (!statusId) { return composeQuotePolicy; } const status = statuses.get(statusId); if (!status) { return 'public'; } const policy = (status.getIn(['quote_approval', 'automatic', 0]) as string) || 'nobody'; const visibility = status.get('visibility') as StatusVisibility; // If the status is private or direct, it cannot be quoted by anyone. if (visibility === 'private' || visibility === 'direct') { return 'nobody'; } // If the status has a specific quote policy, return it. if (isQuotePolicy(policy)) { return policy; } // Otherwise, return the default based on visibility. if (visibility === 'unlisted') { return 'followers'; } return 'public'; }, ); export const VisibilityModal: FC = forwardRef( // eslint-disable-next-line @typescript-eslint/no-unused-vars ({ onClose, onChange, statusId }, _ref) => { const intl = useIntl(); const currentVisibility = useAppSelector((state) => statusId ? ((state.statuses.getIn([statusId, 'visibility'], 'public') as | StatusVisibility | undefined) ?? 'public') : (state.compose.get('privacy') as StatusVisibility), ); const currentQuotePolicy = useAppSelector((state) => selectStatusPolicy(state, statusId), ); const [visibility, setVisibility] = useState(currentVisibility); const [quotePolicy, setQuotePolicy] = useState(currentQuotePolicy); const disableVisibility = !!statusId; const disableQuotePolicy = visibility === 'private' || visibility === 'direct'; const visibilityItems = useMemo[]>( () => [ { value: 'public', text: intl.formatMessage(privacyMessages.public_short), meta: intl.formatMessage(privacyMessages.public_long), icon: 'globe', iconComponent: PublicIcon, }, { value: 'unlisted', text: intl.formatMessage(privacyMessages.unlisted_short), meta: intl.formatMessage(privacyMessages.unlisted_long), extra: intl.formatMessage(privacyMessages.unlisted_extra), icon: 'unlock', iconComponent: QuietTimeIcon, }, { value: 'private', text: intl.formatMessage(privacyMessages.private_short), meta: intl.formatMessage(privacyMessages.private_long), icon: 'lock', iconComponent: LockIcon, }, { value: 'direct', text: intl.formatMessage(privacyMessages.direct_short), meta: intl.formatMessage(privacyMessages.direct_long), icon: 'at', iconComponent: AlternateEmailIcon, }, ], [intl], ); const quoteItems = useMemo[]>( () => [ { value: 'public', text: intl.formatMessage(messages.quotePublic) }, { value: 'followers', text: intl.formatMessage(messages.quoteFollowers), }, { value: 'nobody', text: intl.formatMessage(messages.quoteNobody) }, ], [intl], ); const handleVisibilityChange = useCallback((value: string) => { if (isStatusVisibility(value)) { setVisibility(value); } }, []); const handleQuotePolicyChange = useCallback((value: string) => { if (isQuotePolicy(value)) { setQuotePolicy(value); } }, []); const handleSave = useCallback(() => { onChange(visibility, quotePolicy); onClose(); }, [onChange, onClose, visibility, quotePolicy]); const privacyDropdownId = useId(); const quoteDropdownId = useId(); return (
{(chunks) => ( {chunks} )}
( {chunks} ), }} tagName='p' />
); }, ); VisibilityModal.displayName = 'VisibilityModal'; const QuotePolicyHelper: FC<{ policy: ApiQuotePolicy; visibility: StatusVisibility; }> = ({ policy, visibility }) => { if (visibility === 'unlisted' && policy !== 'nobody') { return (

); } if (visibility === 'private') { return (

); } if (visibility === 'direct') { return (

); } return null; };