|
| 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]); */ |
0 commit comments