diff --git a/src/app/atoms/button/Button.scss b/src/app/atoms/button/Button.scss index 7b12195c..e1a01bb0 100644 --- a/src/app/atoms/button/Button.scss +++ b/src/app/atoms/button/Button.scss @@ -26,10 +26,10 @@ &--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; - } + } + .ic-raw { + @include dir.side(margin, 0, var(--sp-extra-tight)); + flex-shrink: 0; } } diff --git a/src/app/molecules/image-pack/ImagePack.jsx b/src/app/molecules/image-pack/ImagePack.jsx new file mode 100644 index 00000000..cd1494de --- /dev/null +++ b/src/app/molecules/image-pack/ImagePack.jsx @@ -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 ( +
+ {pack.displayName} + false} + onEdit={() => false} + /> +
+ Image + Shortcode + Usage +
+
+ {([...pack.images].slice(0, viewMore ? pack.images.size : 2)).map(([shortcode, image]) => ( + + ))} +
+
+ {pack.images.size > 2 && ( + + )} +
+
+ ); +} + +ImagePack.defaultProps = { + roomId: null, + stateKey: null, +}; + +ImagePack.propTypes = { + roomId: PropTypes.string, + stateKey: PropTypes.string, +}; + +export default ImagePack; diff --git a/src/app/molecules/image-pack/ImagePack.scss b/src/app/molecules/image-pack/ImagePack.scss new file mode 100644 index 00000000..47bcf84f --- /dev/null +++ b/src/app/molecules/image-pack/ImagePack.scss @@ -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); + } +} \ No newline at end of file diff --git a/src/app/molecules/image-pack/ImagePackItem.jsx b/src/app/molecules/image-pack/ImagePackItem.jsx new file mode 100644 index 00000000..b5b2cbfc --- /dev/null +++ b/src/app/molecules/image-pack/ImagePackItem.jsx @@ -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 ( +
+ +
+ {shortcode} +
+
+ +
+
+ ); +} + +ImagePackItem.propTypes = { + url: PropTypes.string.isRequired, + shortcode: PropTypes.string.isRequired, + usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired, +}; + +export default ImagePackItem; diff --git a/src/app/molecules/image-pack/ImagePackItem.scss b/src/app/molecules/image-pack/ImagePackItem.scss new file mode 100644 index 00000000..702ca4e0 --- /dev/null +++ b/src/app/molecules/image-pack/ImagePackItem.scss @@ -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); + } +} \ No newline at end of file diff --git a/src/app/molecules/image-pack/ImagePackProfile.jsx b/src/app/molecules/image-pack/ImagePackProfile.jsx new file mode 100644 index 00000000..245398bc --- /dev/null +++ b/src/app/molecules/image-pack/ImagePackProfile.jsx @@ -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 ( +
+ +
+
+ {displayName} + {onEdit && } +
+ {attribution && {attribution}} +
+
+ Pack usage + +
+
+ ); +} + +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; diff --git a/src/app/molecules/image-pack/ImagePackProfile.scss b/src/app/molecules/image-pack/ImagePackProfile.scss new file mode 100644 index 00000000..819a46a7 --- /dev/null +++ b/src/app/molecules/image-pack/ImagePackProfile.scss @@ -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); + } + } +} \ No newline at end of file diff --git a/src/app/molecules/image-pack/ImagePackUsageSelector.jsx b/src/app/molecules/image-pack/ImagePackUsageSelector.jsx new file mode 100644 index 00000000..71efc521 --- /dev/null +++ b/src/app/molecules/image-pack/ImagePackUsageSelector.jsx @@ -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 ( +
+ Usage + onSelect('emoticon')}>Emoji + onSelect('sticker')}>Sticker + onSelect('both')}>Both +
+ ); +} + +ImagePackUsageSelector.propTypes = { + usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired, + onSelect: PropTypes.func.isRequired, +}; + +export default ImagePackUsageSelector; diff --git a/src/app/organisms/emoji-board/EmojiBoard.jsx b/src/app/organisms/emoji-board/EmojiBoard.jsx index 13e393f5..b03ae6e2 100644 --- a/src/app/organisms/emoji-board/EmojiBoard.jsx +++ b/src/app/organisms/emoji-board/EmojiBoard.jsx @@ -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 ( openGroup(recentOffset + pack.packIndex)} diff --git a/src/app/organisms/emoji-board/custom-emoji.js b/src/app/organisms/emoji-board/custom-emoji.js index 6327c9bb..9ba7c5e7 100644 --- a/src/app/organisms/emoji-board/custom-emoji.js +++ b/src/app/organisms/emoji-board/custom-emoji.js @@ -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, }; diff --git a/src/app/organisms/room/RoomSettings.jsx b/src/app/organisms/room/RoomSettings.jsx index 50c5e512..1d6c478e 100644 --- a/src/app/organisms/room/RoomSettings.jsx +++ b/src/app/organisms/room/RoomSettings.jsx @@ -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) => ( + + )); +} + function SecuritySettings({ roomId }) { return ( <> @@ -197,6 +227,7 @@ function RoomSettings({ roomId }) { {selectedTab.text === tabText.GENERAL && } {selectedTab.text === tabText.SEARCH && } {selectedTab.text === tabText.MEMBERS && } + {selectedTab.text === tabText.EMOJIS && } {selectedTab.text === tabText.PERMISSIONS && } {selectedTab.text === tabText.SECURITY && } @@ -210,7 +241,5 @@ RoomSettings.propTypes = { roomId: PropTypes.string.isRequired, }; -export { - RoomSettings as default, - tabText, -}; +export default RoomSettings; +export { tabText }; diff --git a/src/app/organisms/room/RoomSettings.scss b/src/app/organisms/room/RoomSettings.scss index ab7fca5c..3547e8a8 100644 --- a/src/app/organisms/room/RoomSettings.scss +++ b/src/app/organisms/room/RoomSettings.scss @@ -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; } \ No newline at end of file