import React, { useState, useMemo, useReducer, useEffect, } from 'react'; import PropTypes from 'prop-types'; import './ImagePack.scss'; import initMatrix from '../../../client/initMatrix'; import { openReusableDialog } from '../../../client/action/navigation'; import { suffixRename } from '../../../util/common'; import Button from '../../atoms/button/Button'; import Text from '../../atoms/text/Text'; import Input from '../../atoms/input/Input'; import Checkbox from '../../atoms/button/Checkbox'; import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; import { ImagePack as ImagePackBuilder } from '../../organisms/emoji-board/custom-emoji'; import { confirmDialog } from '../confirm-dialog/ConfirmDialog'; import ImagePackProfile from './ImagePackProfile'; import ImagePackItem from './ImagePackItem'; import ImagePackUpload from './ImagePackUpload'; const renameImagePackItem = (shortcode) => new Promise((resolve) => { let isCompleted = false; openReusableDialog( Rename, (requestClose) => (
{ e.preventDefault(); const sc = e.target.shortcode.value; if (sc.trim() === '') return; isCompleted = true; resolve(sc.trim()); requestClose(); }} >
), () => { if (!isCompleted) resolve(null); }, ); }); function getUsage(usage) { if (usage.includes('emoticon') && usage.includes('sticker')) return 'both'; if (usage.includes('emoticon')) return 'emoticon'; if (usage.includes('sticker')) return 'sticker'; return 'both'; } function isGlobalPack(roomId, stateKey) { const mx = initMatrix.matrixClient; const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent(); if (typeof globalContent !== 'object') return false; const { rooms } = globalContent; if (typeof rooms !== 'object') return false; return rooms[roomId]?.[stateKey] !== undefined; } function useRoomImagePack(roomId, stateKey) { const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey); const pack = useMemo(() => ( ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent()) ), [room, stateKey]); const sendPackContent = (content) => { mx.sendStateEvent(roomId, 'im.ponies.room_emotes', content, stateKey); }; return { pack, sendPackContent, }; } function useUserImagePack() { const mx = initMatrix.matrixClient; const packEvent = mx.getAccountData('im.ponies.user_emotes'); const pack = useMemo(() => ( ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? { pack: { display_name: 'Personal' }, images: {}, }) ), []); const sendPackContent = (content) => { mx.setAccountData('im.ponies.user_emotes', content); }; return { pack, sendPackContent, }; } function useImagePackHandles(pack, sendPackContent) { const [, forceUpdate] = useReducer((count) => count + 1, 0); const getNewKey = (key) => { if (typeof key !== 'string') return undefined; let newKey = key?.replace(/\s/g, '_'); if (pack.getImages().get(newKey)) { newKey = suffixRename( newKey, (suffixedKey) => pack.getImages().get(suffixedKey), ); } return newKey; }; const handleAvatarChange = (url) => { pack.setAvatarUrl(url); sendPackContent(pack.getContent()); forceUpdate(); }; const handleEditProfile = (name, attribution) => { pack.setDisplayName(name); pack.setAttribution(attribution); sendPackContent(pack.getContent()); forceUpdate(); }; const handleUsageChange = (newUsage) => { const usage = []; if (newUsage === 'emoticon' || newUsage === 'both') usage.push('emoticon'); if (newUsage === 'sticker' || newUsage === 'both') usage.push('sticker'); pack.setUsage(usage); pack.getImages().forEach((img) => pack.setImageUsage(img.shortcode, undefined)); sendPackContent(pack.getContent()); forceUpdate(); }; const handleRenameItem = async (key) => { const newKey = getNewKey(await renameImagePackItem(key)); if (!newKey || newKey === key) return; pack.updateImageKey(key, newKey); sendPackContent(pack.getContent()); forceUpdate(); }; const handleDeleteItem = async (key) => { const isConfirmed = await confirmDialog( 'Delete', `Are you sure that you want to delete "${key}"?`, 'Delete', 'danger', ); if (!isConfirmed) return; pack.removeImage(key); sendPackContent(pack.getContent()); forceUpdate(); }; const handleUsageItem = (key, newUsage) => { const usage = []; if (newUsage === 'emoticon' || newUsage === 'both') usage.push('emoticon'); if (newUsage === 'sticker' || newUsage === 'both') usage.push('sticker'); pack.setImageUsage(key, usage); sendPackContent(pack.getContent()); forceUpdate(); }; const handleAddItem = (key, url) => { const newKey = getNewKey(key); if (!newKey || !url) return; pack.addImage(newKey, { url, }); sendPackContent(pack.getContent()); forceUpdate(); }; return { handleAvatarChange, handleEditProfile, handleUsageChange, handleRenameItem, handleDeleteItem, handleUsageItem, handleAddItem, }; } function addGlobalImagePack(mx, roomId, stateKey) { const content = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? {}; if (!content.rooms) content.rooms = {}; if (!content.rooms[roomId]) content.rooms[roomId] = {}; content.rooms[roomId][stateKey] = {}; return mx.setAccountData('im.ponies.emote_rooms', content); } function removeGlobalImagePack(mx, roomId, stateKey) { const content = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? {}; if (!content.rooms) return Promise.resolve(); if (!content.rooms[roomId]) return Promise.resolve(); delete content.rooms[roomId][stateKey]; if (Object.keys(content.rooms[roomId]).length === 0) { delete content.rooms[roomId]; } return mx.setAccountData('im.ponies.emote_rooms', content); } function ImagePack({ roomId, stateKey, handlePackDelete }) { const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); const [viewMore, setViewMore] = useState(false); const [isGlobal, setIsGlobal] = useState(isGlobalPack(roomId, stateKey)); const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey); const { handleAvatarChange, handleEditProfile, handleUsageChange, handleRenameItem, handleDeleteItem, handleUsageItem, handleAddItem, } = useImagePackHandles(pack, sendPackContent); const handleGlobalChange = (isG) => { setIsGlobal(isG); if (isG) addGlobalImagePack(mx, roomId, stateKey); else removeGlobalImagePack(mx, roomId, stateKey); }; const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0; const canChange = room.currentState.hasSufficientPowerLevelFor('state_default', myPowerlevel); const handleDeletePack = async () => { const isConfirmed = await confirmDialog( 'Delete Pack', `Are you sure that you want to delete "${pack.displayName}"?`, 'Delete', 'danger', ); if (!isConfirmed) return; handlePackDelete(stateKey); }; const images = [...pack.images].slice(0, viewMore ? pack.images.size : 2); return (
{ canChange && ( )} { images.length === 0 ? null : (
Image Shortcode Usage
{images.map(([shortcode, image]) => ( ))}
)} {(pack.images.size > 2 || handlePackDelete) && (
{pack.images.size > 2 && ( )} { handlePackDelete && }
)}
Use globally Add this pack to your account to use in all rooms.
); } ImagePack.defaultProps = { handlePackDelete: null, }; ImagePack.propTypes = { roomId: PropTypes.string.isRequired, stateKey: PropTypes.string.isRequired, handlePackDelete: PropTypes.func, }; function ImagePackUser() { const mx = initMatrix.matrixClient; const [viewMore, setViewMore] = useState(false); const { pack, sendPackContent } = useUserImagePack(); const { handleAvatarChange, handleEditProfile, handleUsageChange, handleRenameItem, handleDeleteItem, handleUsageItem, handleAddItem, } = useImagePackHandles(pack, sendPackContent); const images = [...pack.images].slice(0, viewMore ? pack.images.size : 2); return (
{ images.length === 0 ? null : (
Image Shortcode Usage
{images.map(([shortcode, image]) => ( ))}
)} {(pack.images.size > 2) && (
)}
); } function useGlobalImagePack() { const [, forceUpdate] = useReducer((count) => count + 1, 0); const mx = initMatrix.matrixClient; const roomIdToStateKeys = new Map(); const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? { rooms: {} }; const { rooms } = globalContent; Object.keys(rooms).forEach((roomId) => { if (typeof rooms[roomId] !== 'object') return; const room = mx.getRoom(roomId); const stateKeys = Object.keys(rooms[roomId]); if (!room || stateKeys.length === 0) return; roomIdToStateKeys.set(roomId, stateKeys); }); useEffect(() => { const handleEvent = (event) => { if (event.getType() === 'im.ponies.emote_rooms') forceUpdate(); }; mx.addListener('accountData', handleEvent); return () => { mx.removeListener('accountData', handleEvent); }; }, []); return roomIdToStateKeys; } function ImagePackGlobal() { const mx = initMatrix.matrixClient; const roomIdToStateKeys = useGlobalImagePack(); const handleChange = (roomId, stateKey) => { removeGlobalImagePack(mx, roomId, stateKey); }; return (
Global packs
{ roomIdToStateKeys.size > 0 ? [...roomIdToStateKeys].map(([roomId, stateKeys]) => { const room = mx.getRoom(roomId); return ( stateKeys.map((stateKey) => { const data = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey); const pack = ImagePackBuilder.parsePack(data?.getId(), data?.getContent()); if (!pack) return null; return (
handleChange(roomId, stateKey)} isActive />
{pack.displayName ?? 'Unknown'} {room.name}
); }) ); }) :
No global packs
}
); } export default ImagePack; export { ImagePackUser, ImagePackGlobal };