Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 6b636f3

Browse files
committed
Alpha-level MSC2545 support
Signed-off-by: ckie <[email protected]>
1 parent 3ccde7a commit 6b636f3

File tree

4 files changed

+178
-2
lines changed

4 files changed

+178
-2
lines changed

res/css/_components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@
236236
@import "./views/rooms/_Autocomplete.scss";
237237
@import "./views/rooms/_AuxPanel.scss";
238238
@import "./views/rooms/_BasicMessageComposer.scss";
239+
@import "./views/rooms/_Cookiestickers.scss";
239240
@import "./views/rooms/_E2EIcon.scss";
240241
@import "./views/rooms/_EditMessageComposer.scss";
241242
@import "./views/rooms/_EntityTile.scss";
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// .mx_Cookiestickers_*
2+
3+
.mx_Cookiestickers_root {
4+
overflow: scroll;
5+
height: 500px;
6+
.mx_RoomView_MessageList {
7+
// grrr
8+
height: unset !important;
9+
}
10+
}
11+
12+
.mx_Cookiestickers_grid {
13+
display: flex;
14+
flex-wrap: wrap;
15+
max-width: 300px;
16+
margin: 0 auto;
17+
}
18+
19+
.mx_Cookiestickers_imageContainer {
20+
flex-shrink: 1;
21+
flex-basis: calc(25% - 10px);
22+
margin: 5px;
23+
user-select: none;
24+
img {
25+
width: 100%;
26+
object-fit: contain;
27+
}
28+
}
29+
30+
.mx_Cookiestickers_label {
31+
padding-left: 1em;
32+
padding-right: 1em;
33+
margin-top: 0px;
34+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
Copyright 2015, 2016 OpenMarket Ltd
3+
Copyright 2017, 2018 Vector Creations Ltd
4+
Copyright 2019 Michael Telatynski <[email protected]>
5+
Copyright 2019, 2020, 2022 The Matrix.org Foundation C.I.C.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
import { IImageInfo, Room } from 'matrix-js-sdk/src/matrix';
21+
import React, { useContext } from 'react';
22+
23+
import MatrixClientContext from '../../../contexts/MatrixClientContext';
24+
import { mediaFromMxc } from '../../../customisations/Media';
25+
import ContextMenu, { ChevronFace } from '../../structures/ContextMenu';
26+
import ScrollPanel from '../../structures/ScrollPanel';
27+
import GenericElementContextMenu from '../context_menus/GenericElementContextMenu';
28+
29+
const EMOTE_ROOMS_EVENT_TYPE = "im.ponies.emote_rooms";
30+
const ROOM_EMOTES_EVENT_TYPE = "im.ponies.room_emotes";
31+
32+
// Css Class; it's a short name for easy usage.
33+
const cc = (thing: string) => "mx_Cookiestickers_" + thing;
34+
35+
interface I2545Image {
36+
body: string;
37+
info: IImageInfo;
38+
url: string; // mxc
39+
usage: ("sticker" | "emoticon")[];
40+
}
41+
42+
interface I2545Pack {
43+
pack: {
44+
attribution: string;
45+
avatar_url: string;
46+
display_name: string;
47+
};
48+
images: {
49+
[id: string]: I2545Image;
50+
};
51+
}
52+
53+
export const Cookiestickerpicker: React.FC<{
54+
room: Room;
55+
threadId: string;
56+
menuPosition?: any;
57+
isStickerPickerOpen: boolean;
58+
setStickerPickerOpen: (isStickerPickerOpen: boolean) => void;
59+
}> = ({ room, threadId, menuPosition, isStickerPickerOpen, setStickerPickerOpen }) => {
60+
const cli = useContext(MatrixClientContext);
61+
if (!isStickerPickerOpen) return null;
62+
63+
const evt = cli.getAccountData(EMOTE_ROOMS_EVENT_TYPE);
64+
const evtContent = evt.event.content as { rooms: { [roomId: string]: { [packName: string]: {} } } };
65+
66+
const packs = Object.keys(evtContent.rooms)
67+
.map(roomId => {
68+
const room = cli.getRoom(roomId);
69+
return Object.keys(evtContent.rooms[roomId])
70+
.map(name => {
71+
const pack = room.currentState.getStateEvents(ROOM_EMOTES_EVENT_TYPE, name)
72+
.event.content as I2545Pack;
73+
return { room, pack, packName: name };
74+
});
75+
}).flat(1);
76+
77+
const packImage = (image: I2545Image) => {
78+
const media = mediaFromMxc(image.url, cli);
79+
// noinspection JSIgnoredPromiseFromCall
80+
media.downloadSource();
81+
const onImageClick = (imgEvt: React.MouseEvent) => {
82+
imgEvt.preventDefault();
83+
cli.sendStickerMessage(room.roomId, threadId, media.srcMxc, image.info, image.body);
84+
setStickerPickerOpen(false);
85+
};
86+
87+
// eslint-disable-next-line new-cap
88+
return <div key={image.url} className={cc("imageContainer")}>
89+
<img src={media.srcHttp} onClick={onImageClick} />
90+
</div>;
91+
};
92+
93+
const renderedPacks = packs.map(({ room, pack, packName }) => {
94+
const images = Object.values(pack.images).map(packImage);
95+
return <div key={packName}>
96+
<h3 className={cc("label")}>{ pack.pack.display_name }</h3>
97+
<div className={cc("grid")}>
98+
{ images }
99+
</div>
100+
</div>;
101+
});
102+
103+
const finished = () => {
104+
if (isStickerPickerOpen) {
105+
setStickerPickerOpen(false);
106+
}
107+
};
108+
109+
return <ContextMenu
110+
chevronFace={ChevronFace.Bottom}
111+
menuWidth={300}
112+
menuHeight={500}
113+
onFinished={finished}
114+
menuPaddingTop={0}
115+
menuPaddingLeft={0}
116+
menuPaddingRight={0}
117+
zIndex={3500}
118+
{...menuPosition}
119+
>
120+
<GenericElementContextMenu element={
121+
<ScrollPanel startAtBottom={false} className={cc("root")}>
122+
{ renderedPacks }
123+
</ScrollPanel>}
124+
onResize={finished} />
125+
</ContextMenu>;
126+
};
127+
128+
/* const accountDataHandler = useCallback((ev) => {
129+
* console.log("ckalm", ev);
130+
* // TODO
131+
* }, [cli]);
132+
* useTypedEventEmitter(cli, ClientEvent.AccountData, accountDataHandler);
133+
*/
134+
// Count of how many operations are currently in progress, if > 0 then show a Spinner
135+
/* const [pendingUpdateCount, setPendingUpdateCount] = useState(0); */
136+
/* const startUpdating = useCallback(() => {
137+
* setPendingUpdateCount(pendingUpdateCount + 1);
138+
* }, [pendingUpdateCount]);
139+
* const stopUpdating = useCallback(() => {
140+
* setPendingUpdateCount(pendingUpdateCount - 1);
141+
* }, [pendingUpdateCount]); */

src/components/views/rooms/MessageComposer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { _t } from '../../../languageHandler';
2828
import { MatrixClientPeg } from '../../../MatrixClientPeg';
2929
import dis from '../../../dispatcher/dispatcher';
3030
import { ActionPayload } from "../../../dispatcher/payloads";
31-
import Stickerpicker from './Stickerpicker';
3231
import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
3332
import E2EIcon from './E2EIcon';
3433
import SettingsStore from "../../../settings/SettingsStore";
@@ -53,6 +52,7 @@ import MessageComposerButtons from './MessageComposerButtons';
5352
import { ButtonEvent } from '../elements/AccessibleButton';
5453
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
5554
import { Icon as InfoIcon } from "../../../../res/img/element-icons/room/room-summary.svg";
55+
import { Cookiestickerpicker } from './Cookiestickerpicker';
5656

5757
let instanceCount = 0;
5858

@@ -420,7 +420,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
420420
: null;
421421

422422
controls.push(
423-
<Stickerpicker
423+
<Cookiestickerpicker
424424
room={this.props.room}
425425
threadId={threadId}
426426
isStickerPickerOpen={this.state.isStickerPickerOpen}

0 commit comments

Comments
 (0)