// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { KeyboardEvent, MouseEvent, useRef, useState } from 'react'; import classNames from 'classnames'; import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu'; import { ConfirmationDialog } from './ConfirmationDialog'; import { CustomColorEditor } from './CustomColorEditor'; import { Modal } from './Modal'; import { ConversationColors, ConversationColorType, CustomColorType, } from '../types/Colors'; import { ConversationType } from '../state/ducks/conversations'; import { LocalizerType } from '../types/Util'; import { SampleMessageBubbles } from './SampleMessageBubbles'; import { PanelRow } from './conversation/conversation-details/PanelRow'; import { getCustomColorStyle } from '../util/getCustomColorStyle'; type CustomColorDataType = { id?: string; value?: CustomColorType; }; export type PropsDataType = { customColors?: Record; getConversationsWithCustomColor: (colorId: string) => Array; i18n: LocalizerType; isInModal?: boolean; onChatColorReset?: () => unknown; onSelectColor: ( color: ConversationColorType, customColorData?: { id: string; value: CustomColorType; } ) => unknown; selectedColor?: ConversationColorType; selectedCustomColor: CustomColorDataType; }; type PropsActionType = { addCustomColor: (color: CustomColorType) => unknown; editCustomColor: (colorId: string, color: CustomColorType) => unknown; removeCustomColor: (colorId: string) => unknown; removeCustomColorOnConversations: (colorId: string) => unknown; resetAllChatColors: () => unknown; }; export type PropsType = PropsDataType & PropsActionType; export const ChatColorPicker = ({ addCustomColor, customColors = {}, editCustomColor, getConversationsWithCustomColor, i18n, isInModal = false, onChatColorReset, onSelectColor, removeCustomColor, removeCustomColorOnConversations, resetAllChatColors, selectedColor = ConversationColors[0], selectedCustomColor, }: PropsType): JSX.Element => { const [confirmResetAll, setConfirmResetAll] = useState(false); const [customColorToEdit, setCustomColorToEdit] = useState< CustomColorDataType | undefined >(undefined); const renderCustomColorEditorWrapper = () => ( setCustomColorToEdit(undefined)} onSave={(color: CustomColorType) => { if (customColorToEdit?.id) { editCustomColor(customColorToEdit.id, color); onSelectColor('custom', { id: customColorToEdit.id, value: color, }); } else { addCustomColor(color); } }} /> ); if (isInModal && customColorToEdit) { return renderCustomColorEditorWrapper(); } return (
{customColorToEdit ? renderCustomColorEditorWrapper() : null} {confirmResetAll ? ( { setConfirmResetAll(false); }} title={i18n('ChatColorPicker__resetAll')} > {i18n('ChatColorPicker__confirm-reset-message')} ) : null}
{ConversationColors.map(color => (
onSelectColor(color)} onKeyDown={(ev: KeyboardEvent) => { if (ev.key === 'Enter') { onSelectColor(color); } }} role="button" tabIndex={0} /> ))} {Object.keys(customColors).map(colorId => { const colorValues = customColors[colorId]; return ( { onSelectColor('custom', { id: colorId, value: colorValues, }); }} onDelete={() => { removeCustomColor(colorId); removeCustomColorOnConversations(colorId); }} onDupe={() => { addCustomColor(colorValues); }} onEdit={() => { setCustomColorToEdit({ id: colorId, value: colorValues }); }} /> ); })}
setCustomColorToEdit({ id: undefined, value: undefined }) } onKeyDown={(ev: KeyboardEvent) => { if (ev.key === 'Enter') { setCustomColorToEdit({ id: undefined, value: undefined }); } }} role="button" tabIndex={0} >

{onChatColorReset ? ( ) : null} { setConfirmResetAll(true); }} />
); }; type CustomColorBubblePropsType = { color: CustomColorType; colorId: string; getConversationsWithCustomColor: (colorId: string) => Array; i18n: LocalizerType; isSelected: boolean; onDelete: () => unknown; onDupe: () => unknown; onEdit: () => unknown; onChoose: () => unknown; }; const CustomColorBubble = ({ color, colorId, getConversationsWithCustomColor, i18n, isSelected, onDelete, onDupe, onEdit, onChoose, }: CustomColorBubblePropsType): JSX.Element => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const menuRef = useRef(null); const [confirmDeleteCount, setConfirmDeleteCount] = useState< number | undefined >(undefined); const handleClick = (ev: KeyboardEvent | MouseEvent) => { if (!isSelected) { onChoose(); return; } if (menuRef && menuRef.current) { menuRef.current.handleContextClick(ev); } }; const bubble = (
{ if (ev.key === 'Enter') { handleClick(ev); } }} role="button" tabIndex={0} style={{ ...getCustomColorStyle(color), }} /> ); return ( <> {confirmDeleteCount ? ( { setConfirmDeleteCount(undefined); }} title={i18n('ChatColorPicker__delete--title')} > {i18n('ChatColorPicker__delete--message', [ String(confirmDeleteCount), ])} ) : null} {isSelected ? ( {bubble} ) : ( bubble )} { event.stopPropagation(); event.preventDefault(); onEdit(); }} > {i18n('ChatColorPicker__context--edit')} { event.stopPropagation(); event.preventDefault(); onDupe(); }} > {i18n('ChatColorPicker__context--duplicate')} { event.stopPropagation(); event.preventDefault(); const conversations = getConversationsWithCustomColor(colorId); if (!conversations.length) { onDelete(); } else { setConfirmDeleteCount(conversations.length); } }} > {i18n('ChatColorPicker__context--delete')} ); }; type CustomColorEditorWrapperPropsType = { customColorToEdit?: CustomColorDataType; i18n: LocalizerType; isInModal: boolean; onClose: () => unknown; onSave: (color: CustomColorType) => unknown; }; const CustomColorEditorWrapper = ({ customColorToEdit, i18n, isInModal, onClose, onSave, }: CustomColorEditorWrapperPropsType): JSX.Element => { const editor = ( ); if (!isInModal) { return ( {editor} ); } return editor; };