import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Avatar, Box, Button, Icon, Icons, Input, MenuItem, Scroll, Spinner, Text, config, } from 'folds'; import { Room } from 'matrix-js-sdk'; import { useAtomValue, useStore } from 'jotai'; import { useSetting } from '../../../state/hooks/settings'; import { settingsAtom } from '../../../state/settings'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; import { useRoomNavigate } from '../../../hooks/useRoomNavigate'; import { mDirectAtom } from '../../../state/mDirectList'; import { roomIdToMsgDraftAtomFamily, roomIdToUploadItemsAtomFamily, } from '../../../state/room/roomInputDrafts'; import { RoomAvatar, RoomIcon } from '../../../components/room-avatar'; import { mxcUrlToHttp } from '../../../utils/matrix'; import { filesToUploadItems } from '../../../utils/uploadItems'; import { clearSharedPayload, readSharedPayload, shareDraftText, SharePayload, } from '../../../../swShare'; import { BlockType } from '../../../components/editor'; import { bytesToSize } from '../../../utils/common'; import { getHomePath } from '../../pathUtils'; import { useLocalRoomNames } from '../../../hooks/useRoomMeta'; type Shared = { payload: SharePayload; files: File[] }; /** * [Gitea #155] Landing page for the PWA share target: what was shared, pick * a room, and the files land on that room's composer upload board (text/url * as the draft) — the user still presses Send. */ export function Share() { const localNames = useLocalRoomNames(); const mx = useMatrixClient(); const navigate = useNavigate(); const { navigateRoom } = useRoomNavigate(); const directs = useAtomValue(mDirectAtom); const useAuthentication = useMediaAuthentication(); const [shared, setShared] = useState(undefined); const [query, setQuery] = useState(''); const [busy, setBusy] = useState(false); const [stripImageMetadata] = useSetting(settingsAtom, 'stripImageMetadata'); useEffect(() => { readSharedPayload() .then((s) => setShared(s ?? null)) .catch(() => setShared(null)); }, []); const rooms = useMemo(() => { const all = mx .getRooms() .filter((r) => r.getMyMembership() === 'join' && !r.isSpaceRoom()) .sort((a, b) => (b.getLastActiveTimestamp() ?? 0) - (a.getLastActiveTimestamp() ?? 0)); const q = query.trim().toLowerCase(); return q ? all.filter((r) => [r.name, localNames[r.roomId]].some((nm) => nm?.toLowerCase().includes(q)), ) : all; }, [mx, query, localNames]); // Written straight into the room's draft atoms so its composer shows the // upload board + text as soon as it mounts. const store = useStore(); const pick = useCallback( async (room: Room) => { if (!shared || busy) return; setBusy(true); try { if (shared.files.length) { store.set(roomIdToUploadItemsAtomFamily(room.roomId), { type: 'PUT', item: await filesToUploadItems(room, shared.files, stripImageMetadata), }); } const text = shareDraftText(shared.payload); if (text) { store.set(roomIdToMsgDraftAtomFamily(room.roomId), [ { type: BlockType.Paragraph, children: [{ text }] }, ]); } await clearSharedPayload(); navigateRoom(room.roomId); } finally { setBusy(false); } }, [shared, busy, navigateRoom, store, stripImageMetadata], ); return ( Share to Lotus Chat {shared === undefined && ( )} {shared === null && ( Nothing was shared, or it has already been placed in a room. )} {shared && ( Shared {shared.files.map((f) => ( {f.name} {bytesToSize(f.size)} ))} {shareDraftText(shared.payload) && ( {shareDraftText(shared.payload)} )} Send to setQuery(evt.currentTarget.value)} autoFocus /> {rooms.slice(0, 60).map((room) => ( ))} {rooms.length === 0 && ( No rooms match. )} )} ); } function ShareRoomRow({ room, dm, useAuthentication, disabled, onPick, }: { room: Room; dm: boolean; useAuthentication: boolean; disabled: boolean; onPick: (room: Room) => void; }) { const localNames = useLocalRoomNames(); const mx = useMatrixClient(); const avatarMxc = room.getMxcAvatarUrl(); const avatarUrl = avatarMxc ? (mxcUrlToHttp(mx, avatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined) : undefined; return ( onPick(room)} before={ ( )} /> } > {localNames[room.roomId] ?? room.name} {dm && ( Direct Message )} ); }