Add support for sending stickers
This commit is contained in:
parent
bcd2bffd0c
commit
aa6fb5940b
5 changed files with 207 additions and 1 deletions
4
public/res/ic/outlined/sticker.svg
Normal file
4
public/res/ic/outlined/sticker.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 3L21 8V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V5C3 3.89543 3.89543 3 5 3H16ZM19 9H17C15.8954 9 15 8.10457 15 7V5H5V19H19V9Z" fill="black"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 12C9 13.6569 10.3431 15 12 15C13.6569 15 15 13.6569 15 12H17C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12H9Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 501 B |
|
@ -84,6 +84,7 @@
|
||||||
.emoji {
|
.emoji {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& > p:last-child {
|
& > p:last-child {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import TextareaAutosize from 'react-autosize-textarea';
|
||||||
import initMatrix from '../../../client/initMatrix';
|
import initMatrix from '../../../client/initMatrix';
|
||||||
import cons from '../../../client/state/cons';
|
import cons from '../../../client/state/cons';
|
||||||
import settings from '../../../client/state/settings';
|
import settings from '../../../client/state/settings';
|
||||||
import { openEmojiBoard } from '../../../client/action/navigation';
|
import { openEmojiBoard, openReusableContextMenu } from '../../../client/action/navigation';
|
||||||
import navigation from '../../../client/state/navigation';
|
import navigation from '../../../client/state/navigation';
|
||||||
import { bytesToSize, getEventCords } from '../../../util/common';
|
import { bytesToSize, getEventCords } from '../../../util/common';
|
||||||
import { getUsername } from '../../../util/matrixUtil';
|
import { getUsername } from '../../../util/matrixUtil';
|
||||||
|
@ -20,9 +20,12 @@ import IconButton from '../../atoms/button/IconButton';
|
||||||
import ScrollView from '../../atoms/scroll/ScrollView';
|
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||||
import { MessageReply } from '../../molecules/message/Message';
|
import { MessageReply } from '../../molecules/message/Message';
|
||||||
|
|
||||||
|
import StickerBoard from '../sticker-board/StickerBoard';
|
||||||
|
|
||||||
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
|
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
|
||||||
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
|
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
|
||||||
import SendIC from '../../../../public/res/ic/outlined/send.svg';
|
import SendIC from '../../../../public/res/ic/outlined/send.svg';
|
||||||
|
import StickerIC from '../../../../public/res/ic/outlined/sticker.svg';
|
||||||
import ShieldIC from '../../../../public/res/ic/outlined/shield.svg';
|
import ShieldIC from '../../../../public/res/ic/outlined/shield.svg';
|
||||||
import VLCIC from '../../../../public/res/ic/outlined/vlc.svg';
|
import VLCIC from '../../../../public/res/ic/outlined/vlc.svg';
|
||||||
import VolumeFullIC from '../../../../public/res/ic/outlined/volume-full.svg';
|
import VolumeFullIC from '../../../../public/res/ic/outlined/volume-full.svg';
|
||||||
|
@ -203,6 +206,33 @@ function RoomViewInput({
|
||||||
if (replyTo !== null) setReplyTo(null);
|
if (replyTo !== null) setReplyTo(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSendSticker = async (data) => {
|
||||||
|
const { mxc: url, body, httpUrl } = data;
|
||||||
|
const info = {};
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.src = httpUrl;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(httpUrl);
|
||||||
|
const blob = await res.blob();
|
||||||
|
info.w = img.width;
|
||||||
|
info.h = img.height;
|
||||||
|
info.mimetype = blob.type;
|
||||||
|
info.size = blob.size;
|
||||||
|
info.thumbnail_info = { ...info };
|
||||||
|
info.thumbnail_url = url;
|
||||||
|
} catch {
|
||||||
|
// send sticker without info
|
||||||
|
}
|
||||||
|
|
||||||
|
mx.sendEvent(roomId, 'm.sticker', {
|
||||||
|
body,
|
||||||
|
url,
|
||||||
|
info,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function processTyping(msg) {
|
function processTyping(msg) {
|
||||||
const isEmptyMsg = msg === '';
|
const isEmptyMsg = msg === '';
|
||||||
|
|
||||||
|
@ -342,6 +372,29 @@ function RoomViewInput({
|
||||||
{isMarkdown && <RawIcon size="extra-small" src={MarkdownIC} />}
|
{isMarkdown && <RawIcon size="extra-small" src={MarkdownIC} />}
|
||||||
</div>
|
</div>
|
||||||
<div ref={rightOptionsRef} className="room-input__option-container">
|
<div ref={rightOptionsRef} className="room-input__option-container">
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
openReusableContextMenu(
|
||||||
|
'top',
|
||||||
|
(() => {
|
||||||
|
const cords = getEventCords(e);
|
||||||
|
cords.y -= 20;
|
||||||
|
return cords;
|
||||||
|
})(),
|
||||||
|
(closeMenu) => (
|
||||||
|
<StickerBoard
|
||||||
|
roomId={roomId}
|
||||||
|
onSelect={(data) => {
|
||||||
|
handleSendSticker(data);
|
||||||
|
closeMenu();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
tooltip="Sticker"
|
||||||
|
src={StickerIC}
|
||||||
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
const cords = getEventCords(e);
|
const cords = getEventCords(e);
|
||||||
|
|
88
src/app/organisms/sticker-board/StickerBoard.jsx
Normal file
88
src/app/organisms/sticker-board/StickerBoard.jsx
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import './StickerBoard.scss';
|
||||||
|
|
||||||
|
import initMatrix from '../../../client/initMatrix';
|
||||||
|
import { getRelevantPacks } from '../emoji-board/custom-emoji';
|
||||||
|
|
||||||
|
import Text from '../../atoms/text/Text';
|
||||||
|
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||||
|
|
||||||
|
function StickerBoard({ roomId, onSelect }) {
|
||||||
|
const mx = initMatrix.matrixClient;
|
||||||
|
const room = mx.getRoom(roomId);
|
||||||
|
|
||||||
|
const parentIds = initMatrix.roomList.getAllParentSpaces(room.roomId);
|
||||||
|
const parentRooms = [...parentIds].map((id) => mx.getRoom(id));
|
||||||
|
|
||||||
|
const packs = getRelevantPacks(
|
||||||
|
mx,
|
||||||
|
[room, ...parentRooms],
|
||||||
|
).filter((pack) => pack.getStickers().length !== 0);
|
||||||
|
|
||||||
|
function isTargetNotSticker(target) {
|
||||||
|
return target.classList.contains('sticker-board__sticker') === false;
|
||||||
|
}
|
||||||
|
function getStickerData(target) {
|
||||||
|
const mxc = target.getAttribute('data-mx-sticker');
|
||||||
|
const body = target.getAttribute('title');
|
||||||
|
const httpUrl = target.getAttribute('src');
|
||||||
|
return { mxc, body, httpUrl };
|
||||||
|
}
|
||||||
|
const handleOnSelect = (e) => {
|
||||||
|
if (isTargetNotSticker(e.target)) return;
|
||||||
|
|
||||||
|
const stickerData = getStickerData(e.target);
|
||||||
|
onSelect(stickerData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPack = (pack) => (
|
||||||
|
<div className="sticker-board__pack" key={pack.id}>
|
||||||
|
<Text className="sticker-board__pack-header" variant="b2" weight="bold">{pack.displayName ?? 'Unknown'}</Text>
|
||||||
|
<div className="sticker-board__pack-items">
|
||||||
|
{pack.getStickers().map((sticker) => (
|
||||||
|
<img
|
||||||
|
key={sticker.shortcode}
|
||||||
|
className="sticker-board__sticker"
|
||||||
|
src={mx.mxcUrlToHttp(sticker.mxc)}
|
||||||
|
alt={sticker.shortcode}
|
||||||
|
title={sticker.body ?? sticker.shortcode}
|
||||||
|
data-mx-sticker={sticker.mxc}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sticker-board">
|
||||||
|
<div className="sticker-board__container">
|
||||||
|
<ScrollView autoHide>
|
||||||
|
<div
|
||||||
|
onClick={handleOnSelect}
|
||||||
|
className="sticker-board__content"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
packs.length > 0
|
||||||
|
? packs.map(renderPack)
|
||||||
|
: (
|
||||||
|
<div className="sticker-board__empty">
|
||||||
|
<Text>There is no sticker pack.</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</ScrollView>
|
||||||
|
</div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
StickerBoard.propTypes = {
|
||||||
|
roomId: PropTypes.string.isRequired,
|
||||||
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StickerBoard;
|
60
src/app/organisms/sticker-board/StickerBoard.scss
Normal file
60
src/app/organisms/sticker-board/StickerBoard.scss
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
@use '../../partials/dir';
|
||||||
|
|
||||||
|
.sticker-board {
|
||||||
|
--sticker-board-height: 390px;
|
||||||
|
--sticker-board-width: 286px;
|
||||||
|
display: flex;
|
||||||
|
height: var(--sticker-board-height);
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
width: var(--sticker-board-width);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__pack {
|
||||||
|
margin-bottom: var(--sp-normal);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 99;
|
||||||
|
background-color: var(--bg-surface);
|
||||||
|
|
||||||
|
@include dir.side(margin, var(--sp-extra-tight), 0);
|
||||||
|
padding: var(--sp-extra-tight) var(--sp-ultra-tight);
|
||||||
|
text-transform: uppercase;
|
||||||
|
box-shadow: 0 -4px 0 0 var(--bg-surface);
|
||||||
|
border-bottom: 1px solid var(--bg-surface-border);
|
||||||
|
}
|
||||||
|
&-items {
|
||||||
|
margin: var(--sp-tight);
|
||||||
|
@include dir.side(margin, var(--sp-normal), var(--sp-extra-tight));
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--sp-normal) var(--sp-tight);
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 76px;
|
||||||
|
height: 76px;
|
||||||
|
object-fit: contain;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__empty {
|
||||||
|
width: 100%;
|
||||||
|
height: var(--sticker-board-height);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue