Display emoji and sticker in room settings

This commit is contained in:
Ajay Bura 2022-07-26 16:10:53 +05:30 committed by Ajay Bura
parent d678b05259
commit 4fd2d3adc2
12 changed files with 316 additions and 16 deletions

View file

@ -26,11 +26,11 @@
&--icon {
@include dir.side(padding, var(--sp-tight), var(--sp-loose));
}
.ic-raw {
@include dir.side(margin, 0, var(--sp-extra-tight));
flex-shrink: 0;
}
}
}
@mixin color($textColor, $iconColor) {

View file

@ -0,0 +1,85 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './ImagePack.scss';
import initMatrix from '../../../client/initMatrix';
import Button from '../../atoms/button/Button';
import Text from '../../atoms/text/Text';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import ImagePackProfile from './ImagePackProfile';
import ImagePackItem from './ImagePackItem';
import { ImagePack as ImagePackBuilder, getUserImagePack } from '../../organisms/emoji-board/custom-emoji';
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 ImagePack({ roomId, stateKey }) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const [viewMore, setViewMore] = useState(false);
const packEvent = roomId
? room.currentState.getStateEvents('im.ponies.room_emotes', stateKey)
: mx.getAccountData('im.ponies.user_emotes');
const pack = roomId
? ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent(), room)
: getUserImagePack(mx);
return (
<div className="image-pack">
<MenuHeader>{pack.displayName}</MenuHeader>
<ImagePackProfile
avatarUrl={mx.mxcUrlToHttp(pack.avatarUrl ?? pack.getEmojis()[0].mxc)}
displayName={pack.displayName}
attribution={pack.attribution}
usage={getUsage(pack.usage)}
onUsage={() => false}
onEdit={() => false}
/>
<div className="image-pack__header">
<Text variant="b3">Image</Text>
<Text variant="b3">Shortcode</Text>
<Text variant="b3">Usage</Text>
</div>
<div>
{([...pack.images].slice(0, viewMore ? pack.images.size : 2)).map(([shortcode, image]) => (
<ImagePackItem
key={shortcode}
url={mx.mxcUrlToHttp(image.mxc)}
shortcode={shortcode}
usage={getUsage(image.usage)}
/>
))}
</div>
<div className="image-pack__footer">
{pack.images.size > 2 && (
<Button onClick={() => setViewMore(!viewMore)}>
{
viewMore
? 'View less'
: `View ${pack.images.size - 2} more`
}
</Button>
)}
</div>
</div>
);
}
ImagePack.defaultProps = {
roomId: null,
stateKey: null,
};
ImagePack.propTypes = {
roomId: PropTypes.string,
stateKey: PropTypes.string,
};
export default ImagePack;

View file

@ -0,0 +1,23 @@
@use '../../partials/flex';
.image-pack {
&-item {
border-top: 1px solid var(--bg-surface-border);
}
&__header {
margin-top: var(--sp-normal);
padding: var(--sp-extra-tight) var(--sp-normal);
display: flex;
align-items: center;
gap: var(--sp-normal);
& > *:nth-child(2) {
@extend .cp-fx__item-one;
}
}
&__footer {
padding: var(--sp-normal);
}
}

View file

@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import './ImagePackItem.scss';
import Avatar from '../../atoms/avatar/Avatar';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
import RawIcon from '../../atoms/system-icons/RawIcon';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
function ImagePackItem({ url, shortcode, usage }) {
return (
<div className="image-pack-item">
<Avatar imageSrc={url} size="extra-small" text={shortcode} bgColor="black" />
<div className="image-pack-item__content">
<Text>{shortcode}</Text>
</div>
<div className="image-pack-item__usage">
<Button>
<RawIcon src={ChevronBottomIC} size="extra-small" />
<Text variant="b2">
{usage === 'emoticon' && 'Emoji'}
{usage === 'sticker' && 'Sticker'}
{usage === 'both' && 'Both'}
</Text>
</Button>
</div>
</div>
);
}
ImagePackItem.propTypes = {
url: PropTypes.string.isRequired,
shortcode: PropTypes.string.isRequired,
usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired,
};
export default ImagePackItem;

View file

@ -0,0 +1,17 @@
@use '../../partials/flex';
.image-pack-item {
margin: 0 var(--sp-normal);
padding: var(--sp-tight) 0;
display: flex;
align-items: center;
gap: var(--sp-normal);
&__content {
@extend .cp-fx__item-one;
}
&__usage > button {
padding: 6px var(--sp-extra-tight);
}
}

View file

@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';
import './ImagePackProfile.scss';
import Text from '../../atoms/text/Text';
import Avatar from '../../atoms/avatar/Avatar';
import Button from '../../atoms/button/Button';
import IconButton from '../../atoms/button/IconButton';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
function ImagePackProfile({
avatarUrl, displayName, attribution, usage, onUsage, onEdit,
}) {
return (
<div className="image-pack-profile">
<Avatar text={displayName} bgColor="blue" imageSrc={avatarUrl} size="normal" />
<div className="image-pack-profile__content">
<div>
<Text>{displayName}</Text>
{onEdit && <IconButton size="extra-small" onClick={onEdit} src={PencilIC} />}
</div>
{attribution && <Text variant="b3">{attribution}</Text>}
</div>
<div className="image-pack-profile__usage">
<Text variant="b3">Pack usage</Text>
<Button iconSrc={onUsage ? ChevronBottomIC : null} onClick={onUsage}>
{usage === 'emoticon' && 'Emoji'}
{usage === 'sticker' && 'Sticker'}
{usage === 'both' && 'Emoji & Sticker'}
</Button>
</div>
</div>
);
}
ImagePackProfile.defaultProps = {
avatarUrl: null,
attribution: null,
onUsage: null,
onEdit: null,
};
ImagePackProfile.propTypes = {
avatarUrl: PropTypes.string,
displayName: PropTypes.string.isRequired,
attribution: PropTypes.string,
usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired,
onUsage: PropTypes.func,
onEdit: PropTypes.func,
};
export default ImagePackProfile;

View file

@ -0,0 +1,22 @@
@use '../../partials/flex';
.image-pack-profile {
padding: var(--sp-normal);
display: flex;
&__content {
padding: 0 var(--sp-tight);
@extend .cp-fx__item-one;
& div:first-child {
display: flex;
align-items: center;
gap: var(--sp-extra-tight);
}
}
&__usage {
& > *:first-child {
margin-bottom: var(--sp-ultra-tight);
}
}
}

View file

@ -0,0 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
function ImagePackUsageSelector({ usage, onSelect }) {
return (
<div>
<MenuHeader>Usage</MenuHeader>
<MenuItem variant={usage === 'emoticon' ? 'positive' : 'surface'} onClick={() => onSelect('emoticon')}>Emoji</MenuItem>
<MenuItem variant={usage === 'sticker' ? 'positive' : 'surface'} onClick={() => onSelect('sticker')}>Sticker</MenuItem>
<MenuItem variant={usage === 'both' ? 'positive' : 'surface'} onClick={() => onSelect('both')}>Both</MenuItem>
</div>
);
}
ImagePackUsageSelector.propTypes = {
usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired,
onSelect: PropTypes.func.isRequired,
};
export default ImagePackUsageSelector;

View file

@ -213,8 +213,6 @@ function EmojiBoard({ onSelect, searchRef }) {
for (let i = 0; i < packs.length; i += 1) {
packs[i].packIndex = i;
}
console.log(packs)
setAvailableEmojis(packs);
}
};
@ -294,7 +292,7 @@ function EmojiBoard({ onSelect, searchRef }) {
{
availableEmojis.map((pack) => {
const src = initMatrix.matrixClient
.mxcUrlToHttp(pack.avatar ?? pack.getEmojis()[0].mxc);
.mxcUrlToHttp(pack.avatarUrl ?? pack.getEmojis()[0].mxc);
return (
<IconButton
onClick={() => openGroup(recentOffset + pack.packIndex)}

View file

@ -11,9 +11,10 @@ class ImagePack {
const pack = packContent.pack ?? {};
const displayName = pack.display_name ?? room?.name ?? undefined;
const avatar = pack.avatar_url ?? room?.getMxcAvatarUrl() ?? undefined;
const avatarUrl = pack.avatar_url ?? room?.getMxcAvatarUrl() ?? undefined;
const packUsage = pack.usage ?? ['emoticon', 'sticker'];
const { attribution } = pack;
const images = new Map();
const emoticons = [];
const stickers = [];
@ -28,6 +29,7 @@ class ImagePack {
shortcode, mxc, body, usage, info,
};
images.set(shortcode, image);
if (usage.includes('emoticon')) {
emoticons.push(image);
}
@ -38,9 +40,10 @@ class ImagePack {
return new ImagePack(eventId, {
displayName,
avatar,
avatarUrl,
usage: packUsage,
attribution,
images,
emoticons,
stickers,
});
@ -48,22 +51,28 @@ class ImagePack {
constructor(id, {
displayName,
avatar,
avatarUrl,
usage,
attribution,
images,
emoticons,
stickers,
}) {
this.id = id;
this.displayName = displayName;
this.avatar = avatar;
this.avatarUrl = avatarUrl;
this.usage = usage;
this.attribution = attribution;
this.images = images;
this.emoticons = emoticons;
this.stickers = stickers;
}
getImages() {
return this.images;
}
getEmojis() {
return this.emoticons;
}
@ -175,6 +184,8 @@ function getEmojiForCompletion(room) {
}
export {
ImagePack,
getUserImagePack, getGlobalImagePacks, getRoomImagePacks,
getShortcodeToEmoji, getShortcodeToCustomEmoji,
getRelevantPacks, getEmojiForCompletion,
};

View file

@ -25,9 +25,11 @@ import RoomHistoryVisibility from '../../molecules/room-history-visibility/RoomH
import RoomEncryption from '../../molecules/room-encryption/RoomEncryption';
import RoomPermissions from '../../molecules/room-permissions/RoomPermissions';
import RoomMembers from '../../molecules/room-members/RoomMembers';
import ImagePack from '../../molecules/image-pack/ImagePack';
import UserIC from '../../../../public/res/ic/outlined/user.svg';
import SettingsIC from '../../../../public/res/ic/outlined/settings.svg';
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
import SearchIC from '../../../../public/res/ic/outlined/search.svg';
import ShieldUserIC from '../../../../public/res/ic/outlined/shield-user.svg';
import LockIC from '../../../../public/res/ic/outlined/lock.svg';
@ -42,6 +44,7 @@ const tabText = {
GENERAL: 'General',
SEARCH: 'Search',
MEMBERS: 'Members',
EMOJIS: 'Emojis',
PERMISSIONS: 'Permissions',
SECURITY: 'Security',
};
@ -58,6 +61,10 @@ const tabItems = [{
iconSrc: UserIC,
text: tabText.MEMBERS,
disabled: false,
}, {
iconSrc: EmojiIC,
text: tabText.EMOJIS,
disabled: false,
}, {
iconSrc: ShieldUserIC,
text: tabText.PERMISSIONS,
@ -121,6 +128,29 @@ GeneralSettings.propTypes = {
roomId: PropTypes.string.isRequired,
};
function RoomEmojis({ roomId }) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const packEvents = room.currentState.getStateEvents('im.ponies.room_emotes');
const unUsablePacks = [];
const usablePacks = packEvents.filter((mEvent) => {
if (typeof mEvent.getContent()?.images !== 'object') {
unUsablePacks.push(mEvent);
return false;
}
return true;
});
return usablePacks.map((mEvent) => (
<ImagePack
key={mEvent.getId()}
roomId={roomId}
stateKey={mEvent.getStateKey()}
/>
));
}
function SecuritySettings({ roomId }) {
return (
<>
@ -197,6 +227,7 @@ function RoomSettings({ roomId }) {
{selectedTab.text === tabText.GENERAL && <GeneralSettings roomId={roomId} />}
{selectedTab.text === tabText.SEARCH && <RoomSearch roomId={roomId} />}
{selectedTab.text === tabText.MEMBERS && <RoomMembers roomId={roomId} />}
{selectedTab.text === tabText.EMOJIS && <RoomEmojis roomId={roomId} />}
{selectedTab.text === tabText.PERMISSIONS && <RoomPermissions roomId={roomId} />}
{selectedTab.text === tabText.SECURITY && <SecuritySettings roomId={roomId} />}
</div>
@ -210,7 +241,5 @@ RoomSettings.propTypes = {
roomId: PropTypes.string.isRequired,
};
export {
RoomSettings as default,
tabText,
};
export default RoomSettings;
export { tabText };

View file

@ -76,6 +76,7 @@
.room-settings .room-permissions__card,
.room-settings .room-search__form,
.room-settings .room-search__result-item ,
.room-settings .room-members {
.room-settings .room-members,
.room-settings .image-pack {
@extend .room-settings__card;
}