forked from mirror/cinny
Merge remote-tracking branch 'upstream/dev' into custom
This commit is contained in:
commit
f7d0c10cb0
19 changed files with 296 additions and 212 deletions
|
@ -10,7 +10,7 @@
|
||||||
cinny = pkgs.buildNpmPackage {
|
cinny = pkgs.buildNpmPackage {
|
||||||
name = "cinny";
|
name = "cinny";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
npmDepsHash = "sha256-tKCQbvi8jW5lxthDiqARRxisWdb4inxNqLM0QhpV220=";
|
npmDepsHash = "sha256-43nCceq1hR89m/xvC+89qxHGdtOUKjZU/MAz4pYu8K0=";
|
||||||
nativeBuildInputs = with pkgs; [ python3 pkg-config ];
|
nativeBuildInputs = with pkgs; [ python3 pkg-config ];
|
||||||
buildInputs = with pkgs; [ pixman cairo pango ];
|
buildInputs = with pkgs; [ pixman cairo pango ];
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
|
|
12
package-lock.json
generated
12
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.1.0",
|
"version": "4.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.1.0",
|
"version": "4.2.0",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
"jotai": "2.6.0",
|
"jotai": "2.6.0",
|
||||||
"linkify-react": "4.1.3",
|
"linkify-react": "4.1.3",
|
||||||
"linkifyjs": "4.1.3",
|
"linkifyjs": "4.1.3",
|
||||||
"matrix-js-sdk": "34.4.0",
|
"matrix-js-sdk": "34.5.0",
|
||||||
"millify": "6.1.0",
|
"millify": "6.1.0",
|
||||||
"pdfjs-dist": "4.2.67",
|
"pdfjs-dist": "4.2.67",
|
||||||
"prismjs": "1.29.0",
|
"prismjs": "1.29.0",
|
||||||
|
@ -9117,9 +9117,9 @@
|
||||||
"integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="
|
"integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA=="
|
||||||
},
|
},
|
||||||
"node_modules/matrix-js-sdk": {
|
"node_modules/matrix-js-sdk": {
|
||||||
"version": "34.4.0",
|
"version": "34.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-34.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-34.5.0.tgz",
|
||||||
"integrity": "sha512-bI5xJZS3/qhjPQqQL5HhOQ1iBvnHxiqhS2zgzk9SarEuXiH08wbVl9gAAuDqOYE3miNGs4WQQJ19MoaUEOnNwg==",
|
"integrity": "sha512-pbp+IxAkSwGmefrlUGCrtrs3UWyqN2iWh4lKnJW+jFIlsksXq7A8vL4cS1z8LXmpcHQAg3mKNuj8n8uhm51t1A==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.1.0",
|
"version": "4.2.0",
|
||||||
"description": "Yet another matrix client",
|
"description": "Yet another matrix client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
"jotai": "2.6.0",
|
"jotai": "2.6.0",
|
||||||
"linkify-react": "4.1.3",
|
"linkify-react": "4.1.3",
|
||||||
"linkifyjs": "4.1.3",
|
"linkifyjs": "4.1.3",
|
||||||
"matrix-js-sdk": "34.4.0",
|
"matrix-js-sdk": "34.5.0",
|
||||||
"millify": "6.1.0",
|
"millify": "6.1.0",
|
||||||
"pdfjs-dist": "4.2.67",
|
"pdfjs-dist": "4.2.67",
|
||||||
"prismjs": "1.29.0",
|
"prismjs": "1.29.0",
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds';
|
||||||
import * as css from './ImageViewer.css';
|
import * as css from './ImageViewer.css';
|
||||||
import { useZoom } from '../../hooks/useZoom';
|
import { useZoom } from '../../hooks/useZoom';
|
||||||
import { usePan } from '../../hooks/usePan';
|
import { usePan } from '../../hooks/usePan';
|
||||||
|
import { downloadMedia } from '../../utils/matrix';
|
||||||
|
|
||||||
export type ImageViewerProps = {
|
export type ImageViewerProps = {
|
||||||
alt: string;
|
alt: string;
|
||||||
|
@ -18,8 +19,9 @@ export const ImageViewer = as<'div', ImageViewerProps>(
|
||||||
const { zoom, zoomIn, zoomOut, setZoom } = useZoom(0.2);
|
const { zoom, zoomIn, zoomOut, setZoom } = useZoom(0.2);
|
||||||
const { pan, cursor, onMouseDown } = usePan(zoom !== 1);
|
const { pan, cursor, onMouseDown } = usePan(zoom !== 1);
|
||||||
|
|
||||||
const handleDownload = () => {
|
const handleDownload = async () => {
|
||||||
FileSaver.saveAs(src, alt);
|
const fileContent = await downloadMedia(src);
|
||||||
|
FileSaver.saveAs(fileContent, alt);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
|
||||||
import { Range } from 'react-range';
|
import { Range } from 'react-range';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { getFileSrcUrl } from './util';
|
|
||||||
import { IAudioInfo } from '../../../../types/matrix/common';
|
import { IAudioInfo } from '../../../../types/matrix/common';
|
||||||
import {
|
import {
|
||||||
PlayTimeCallback,
|
PlayTimeCallback,
|
||||||
|
@ -17,7 +16,12 @@ import {
|
||||||
} from '../../../hooks/media';
|
} from '../../../hooks/media';
|
||||||
import { useThrottle } from '../../../hooks/useThrottle';
|
import { useThrottle } from '../../../hooks/useThrottle';
|
||||||
import { secondsToMinutesAndSeconds } from '../../../utils/common';
|
import { secondsToMinutesAndSeconds } from '../../../utils/common';
|
||||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
import {
|
||||||
|
decryptFile,
|
||||||
|
downloadEncryptedMedia,
|
||||||
|
downloadMedia,
|
||||||
|
mxcUrlToHttp,
|
||||||
|
} from '../../../utils/matrix';
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const PLAY_TIME_THROTTLE_OPS = {
|
const PLAY_TIME_THROTTLE_OPS = {
|
||||||
|
@ -49,10 +53,13 @@ export function AudioContent({
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const [srcState, loadSrc] = useAsyncCallback(
|
const [srcState, loadSrc] = useAsyncCallback(
|
||||||
useCallback(
|
useCallback(async () => {
|
||||||
() => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo),
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
[mx, url, useAuthentication, mimeType, encInfo]
|
const fileContent = encInfo
|
||||||
)
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
|
||||||
|
: await downloadMedia(mediaUrl);
|
||||||
|
return URL.createObjectURL(fileContent);
|
||||||
|
}, [mx, url, useAuthentication, mimeType, encInfo])
|
||||||
);
|
);
|
||||||
|
|
||||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||||
|
|
|
@ -20,7 +20,6 @@ import FocusTrap from 'focus-trap-react';
|
||||||
import { IFileInfo } from '../../../../types/matrix/common';
|
import { IFileInfo } from '../../../../types/matrix/common';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { getFileSrcUrl, getSrcFile } from './util';
|
|
||||||
import { bytesToSize } from '../../../utils/common';
|
import { bytesToSize } from '../../../utils/common';
|
||||||
import {
|
import {
|
||||||
READABLE_EXT_TO_MIME_TYPE,
|
READABLE_EXT_TO_MIME_TYPE,
|
||||||
|
@ -30,7 +29,12 @@ import {
|
||||||
} from '../../../utils/mimeTypes';
|
} from '../../../utils/mimeTypes';
|
||||||
import * as css from './style.css';
|
import * as css from './style.css';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
import {
|
||||||
|
decryptFile,
|
||||||
|
downloadEncryptedMedia,
|
||||||
|
downloadMedia,
|
||||||
|
mxcUrlToHttp,
|
||||||
|
} from '../../../utils/matrix';
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const renderErrorButton = (retry: () => void, text: string) => (
|
const renderErrorButton = (retry: () => void, text: string) => (
|
||||||
|
@ -80,19 +84,17 @@ export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: Rea
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [textViewer, setTextViewer] = useState(false);
|
const [textViewer, setTextViewer] = useState(false);
|
||||||
|
|
||||||
const loadSrc = useCallback(
|
|
||||||
() => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo),
|
|
||||||
[mx, url, useAuthentication, mimeType, encInfo]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [textState, loadText] = useAsyncCallback(
|
const [textState, loadText] = useAsyncCallback(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const src = await loadSrc();
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
const blob = await getSrcFile(src);
|
const fileContent = encInfo
|
||||||
const text = blob.text();
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
|
||||||
|
: await downloadMedia(mediaUrl);
|
||||||
|
|
||||||
|
const text = fileContent.text();
|
||||||
setTextViewer(true);
|
setTextViewer(true);
|
||||||
return text;
|
return text;
|
||||||
}, [loadSrc])
|
}, [mx, useAuthentication, mimeType, encInfo, url])
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -174,9 +176,12 @@ export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: Read
|
||||||
|
|
||||||
const [pdfState, loadPdf] = useAsyncCallback(
|
const [pdfState, loadPdf] = useAsyncCallback(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const httpUrl = await getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo);
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
|
const fileContent = encInfo
|
||||||
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
|
||||||
|
: await downloadMedia(mediaUrl);
|
||||||
setPdfViewer(true);
|
setPdfViewer(true);
|
||||||
return httpUrl;
|
return URL.createObjectURL(fileContent);
|
||||||
}, [mx, url, useAuthentication, mimeType, encInfo])
|
}, [mx, url, useAuthentication, mimeType, encInfo])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -248,9 +253,14 @@ export function DownloadFile({ body, mimeType, url, info, encInfo }: DownloadFil
|
||||||
|
|
||||||
const [downloadState, download] = useAsyncCallback(
|
const [downloadState, download] = useAsyncCallback(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const httpUrl = await getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo);
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
FileSaver.saveAs(httpUrl, body);
|
const fileContent = encInfo
|
||||||
return httpUrl;
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
|
||||||
|
: await downloadMedia(mediaUrl);
|
||||||
|
|
||||||
|
const fileURL = URL.createObjectURL(fileContent);
|
||||||
|
FileSaver.saveAs(fileURL, body);
|
||||||
|
return fileURL;
|
||||||
}, [mx, url, useAuthentication, mimeType, encInfo, body])
|
}, [mx, url, useAuthentication, mimeType, encInfo, body])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,11 @@ import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
|
||||||
import { IImageInfo, MATRIX_BLUR_HASH_PROPERTY_NAME } from '../../../../types/matrix/common';
|
import { IImageInfo, MATRIX_BLUR_HASH_PROPERTY_NAME } from '../../../../types/matrix/common';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { getFileSrcUrl } from './util';
|
|
||||||
import * as css from './style.css';
|
import * as css from './style.css';
|
||||||
import { bytesToSize } from '../../../utils/common';
|
import { bytesToSize } from '../../../utils/common';
|
||||||
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
|
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../utils/matrix';
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type RenderViewerProps = {
|
type RenderViewerProps = {
|
||||||
|
@ -79,10 +78,16 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||||
const [viewer, setViewer] = useState(false);
|
const [viewer, setViewer] = useState(false);
|
||||||
|
|
||||||
const [srcState, loadSrc] = useAsyncCallback(
|
const [srcState, loadSrc] = useAsyncCallback(
|
||||||
useCallback(
|
useCallback(async () => {
|
||||||
() => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType || FALLBACK_MIMETYPE, encInfo),
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
[mx, url, useAuthentication, mimeType, encInfo]
|
if (encInfo) {
|
||||||
)
|
const fileContent = await downloadEncryptedMedia(mediaUrl, (encBuf) =>
|
||||||
|
decryptFile(encBuf, mimeType ?? FALLBACK_MIMETYPE, encInfo)
|
||||||
|
);
|
||||||
|
return URL.createObjectURL(fileContent);
|
||||||
|
}
|
||||||
|
return mediaUrl;
|
||||||
|
}, [mx, url, useAuthentication, mimeType, encInfo])
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLoad = () => {
|
const handleLoad = () => {
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { ReactNode, useCallback, useEffect } from 'react';
|
||||||
import { IThumbnailContent } from '../../../../types/matrix/common';
|
import { IThumbnailContent } from '../../../../types/matrix/common';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { getFileSrcUrl } from './util';
|
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../utils/matrix';
|
||||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
|
||||||
|
|
||||||
export type ThumbnailContentProps = {
|
export type ThumbnailContentProps = {
|
||||||
info: IThumbnailContent;
|
info: IThumbnailContent;
|
||||||
|
@ -15,17 +15,23 @@ export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const [thumbSrcState, loadThumbSrc] = useAsyncCallback(
|
const [thumbSrcState, loadThumbSrc] = useAsyncCallback(
|
||||||
useCallback(() => {
|
useCallback(async () => {
|
||||||
const thumbInfo = info.thumbnail_info;
|
const thumbInfo = info.thumbnail_info;
|
||||||
const thumbMxcUrl = info.thumbnail_file?.url ?? info.thumbnail_url;
|
const thumbMxcUrl = info.thumbnail_file?.url ?? info.thumbnail_url;
|
||||||
|
const encInfo = info.thumbnail_file;
|
||||||
if (typeof thumbMxcUrl !== 'string' || typeof thumbInfo?.mimetype !== 'string') {
|
if (typeof thumbMxcUrl !== 'string' || typeof thumbInfo?.mimetype !== 'string') {
|
||||||
throw new Error('Failed to load thumbnail');
|
throw new Error('Failed to load thumbnail');
|
||||||
}
|
}
|
||||||
return getFileSrcUrl(
|
|
||||||
mxcUrlToHttp(mx, thumbMxcUrl, useAuthentication) ?? '',
|
const mediaUrl = mxcUrlToHttp(mx, thumbMxcUrl, useAuthentication) ?? thumbMxcUrl;
|
||||||
thumbInfo.mimetype,
|
if (encInfo) {
|
||||||
info.thumbnail_file
|
const fileContent = await downloadEncryptedMedia(mediaUrl, (encBuf) =>
|
||||||
);
|
decryptFile(encBuf, thumbInfo.mimetype ?? FALLBACK_MIMETYPE, encInfo)
|
||||||
|
);
|
||||||
|
return URL.createObjectURL(fileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaUrl;
|
||||||
}, [mx, info, useAuthentication])
|
}, [mx, info, useAuthentication])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,14 @@ import {
|
||||||
import * as css from './style.css';
|
import * as css from './style.css';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { getFileSrcUrl } from './util';
|
|
||||||
import { bytesToSize } from '../../../../util/common';
|
import { bytesToSize } from '../../../../util/common';
|
||||||
import { millisecondsToMinutesAndSeconds } from '../../../utils/common';
|
import { millisecondsToMinutesAndSeconds } from '../../../utils/common';
|
||||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
import {
|
||||||
|
decryptFile,
|
||||||
|
downloadEncryptedMedia,
|
||||||
|
downloadMedia,
|
||||||
|
mxcUrlToHttp,
|
||||||
|
} from '../../../utils/matrix';
|
||||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type RenderVideoProps = {
|
type RenderVideoProps = {
|
||||||
|
@ -70,10 +74,15 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
const [srcState, loadSrc] = useAsyncCallback(
|
const [srcState, loadSrc] = useAsyncCallback(
|
||||||
useCallback(
|
useCallback(async () => {
|
||||||
() => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo),
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
[mx, url, useAuthentication, mimeType, encInfo]
|
const fileContent = encInfo
|
||||||
)
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) =>
|
||||||
|
decryptFile(encBuf, mimeType, encInfo)
|
||||||
|
)
|
||||||
|
: await downloadMedia(mediaUrl);
|
||||||
|
return URL.createObjectURL(fileContent);
|
||||||
|
}, [mx, url, useAuthentication, mimeType, encInfo])
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLoad = () => {
|
const handleLoad = () => {
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
|
|
||||||
import { decryptFile } from '../../../utils/matrix';
|
|
||||||
|
|
||||||
export const getFileSrcUrl = async (
|
|
||||||
httpUrl: string,
|
|
||||||
mimeType: string,
|
|
||||||
encInfo?: EncryptedAttachmentInfo
|
|
||||||
): Promise<string> => {
|
|
||||||
if (encInfo) {
|
|
||||||
if (typeof httpUrl !== 'string') throw new Error('Malformed event');
|
|
||||||
const encRes = await fetch(httpUrl, { method: 'GET' });
|
|
||||||
const encData = await encRes.arrayBuffer();
|
|
||||||
const decryptedBlob = await decryptFile(encData, mimeType, encInfo);
|
|
||||||
return URL.createObjectURL(decryptedBlob);
|
|
||||||
}
|
|
||||||
return httpUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSrcFile = async (src: string): Promise<Blob> => {
|
|
||||||
const res = await fetch(src, { method: 'GET' });
|
|
||||||
const blob = await res.blob();
|
|
||||||
return blob;
|
|
||||||
};
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
EventTimelineSet,
|
EventTimelineSet,
|
||||||
EventTimelineSetHandlerMap,
|
EventTimelineSetHandlerMap,
|
||||||
IContent,
|
IContent,
|
||||||
IEncryptedFile,
|
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
Room,
|
Room,
|
||||||
|
@ -48,12 +47,7 @@ import {
|
||||||
import { isKeyHotkey } from 'is-hotkey';
|
import { isKeyHotkey } from 'is-hotkey';
|
||||||
import { Opts as LinkifyOpts } from 'linkifyjs';
|
import { Opts as LinkifyOpts } from 'linkifyjs';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { eventWithShortcode, factoryEventSentBy, getMxIdLocalPart } from '../../utils/matrix';
|
||||||
decryptFile,
|
|
||||||
eventWithShortcode,
|
|
||||||
factoryEventSentBy,
|
|
||||||
getMxIdLocalPart,
|
|
||||||
} from '../../utils/matrix';
|
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { useVirtualPaginator, ItemRange } from '../../hooks/useVirtualPaginator';
|
import { useVirtualPaginator, ItemRange } from '../../hooks/useVirtualPaginator';
|
||||||
import { useAlive } from '../../hooks/useAlive';
|
import { useAlive } from '../../hooks/useAlive';
|
||||||
|
@ -220,18 +214,6 @@ export const getEventIdAbsoluteIndex = (
|
||||||
return baseIndex + eventIndex;
|
return baseIndex + eventIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const factoryGetFileSrcUrl =
|
|
||||||
(httpUrl: string, mimeType: string, encFile?: IEncryptedFile) => async (): Promise<string> => {
|
|
||||||
if (encFile) {
|
|
||||||
if (typeof httpUrl !== 'string') throw new Error('Malformed event');
|
|
||||||
const encRes = await fetch(httpUrl, { method: 'GET' });
|
|
||||||
const encData = await encRes.arrayBuffer();
|
|
||||||
const decryptedBlob = await decryptFile(encData, mimeType, encFile);
|
|
||||||
return URL.createObjectURL(decryptedBlob);
|
|
||||||
}
|
|
||||||
return httpUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RoomTimelineProps = {
|
type RoomTimelineProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
eventId?: string;
|
eventId?: string;
|
||||||
|
@ -311,9 +293,9 @@ const useTimelinePagination = (
|
||||||
range:
|
range:
|
||||||
offsetRange > 0
|
offsetRange > 0
|
||||||
? {
|
? {
|
||||||
start: currentTimeline.range.start + offsetRange,
|
start: currentTimeline.range.start + offsetRange,
|
||||||
end: currentTimeline.range.end + offsetRange,
|
end: currentTimeline.range.end + offsetRange,
|
||||||
}
|
}
|
||||||
: { ...currentTimeline.range },
|
: { ...currentTimeline.range },
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
@ -332,7 +314,7 @@ const useTimelinePagination = (
|
||||||
if (
|
if (
|
||||||
!paginationToken &&
|
!paginationToken &&
|
||||||
getTimelinesEventsCount(lTimelines) !==
|
getTimelinesEventsCount(lTimelines) !==
|
||||||
getTimelinesEventsCount(getLinkedTimelines(timelineToPaginate))
|
getTimelinesEventsCount(getLinkedTimelines(timelineToPaginate))
|
||||||
) {
|
) {
|
||||||
recalibratePagination(lTimelines, timelinesEventsCount, backwards);
|
recalibratePagination(lTimelines, timelinesEventsCount, backwards);
|
||||||
return;
|
return;
|
||||||
|
@ -492,10 +474,10 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
|
|
||||||
const [focusItem, setFocusItem] = useState<
|
const [focusItem, setFocusItem] = useState<
|
||||||
| {
|
| {
|
||||||
index: number;
|
index: number;
|
||||||
scrollTo: boolean;
|
scrollTo: boolean;
|
||||||
highlight: boolean;
|
highlight: boolean;
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
>();
|
>();
|
||||||
const alive = useAlive();
|
const alive = useAlive();
|
||||||
|
@ -729,7 +711,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
const editableEvtId = editableEvt?.getId();
|
const editableEvtId = editableEvt?.getId();
|
||||||
if (!editableEvtId) return;
|
if (!editableEvtId) return;
|
||||||
setEditId(editableEvtId);
|
setEditId(editableEvtId);
|
||||||
evt.preventDefault()
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[mx, room, editor]
|
[mx, room, editor]
|
||||||
|
@ -1469,14 +1451,14 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
const eventJSX = reactionOrEditEvent(mEvent)
|
const eventJSX = reactionOrEditEvent(mEvent)
|
||||||
? null
|
? null
|
||||||
: renderMatrixEvent(
|
: renderMatrixEvent(
|
||||||
mEvent.getType(),
|
mEvent.getType(),
|
||||||
typeof mEvent.getStateKey() === 'string',
|
typeof mEvent.getStateKey() === 'string',
|
||||||
mEventId,
|
mEventId,
|
||||||
mEvent,
|
mEvent,
|
||||||
item,
|
item,
|
||||||
timelineSet,
|
timelineSet,
|
||||||
collapsed
|
collapsed
|
||||||
);
|
);
|
||||||
prevEvent = mEvent;
|
prevEvent = mEvent;
|
||||||
isPrevRendered = !!eventJSX;
|
isPrevRendered = !!eventJSX;
|
||||||
|
|
||||||
|
@ -1558,8 +1540,9 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
{!canPaginateBack && rangeAtStart && getItems().length > 0 && (
|
{!canPaginateBack && rangeAtStart && getItems().length > 0 && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: `${config.space.S700} ${config.space.S400} ${config.space.S600} ${messageLayout === 1 ? config.space.S400 : toRem(64)
|
padding: `${config.space.S700} ${config.space.S400} ${config.space.S600} ${
|
||||||
}`,
|
messageLayout === 1 ? config.space.S400 : toRem(64)
|
||||||
|
}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RoomIntro room={room} />
|
<RoomIntro room={room} />
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import React, {
|
import React, { useState, useMemo, useReducer, useEffect } from 'react';
|
||||||
useState, useMemo, useReducer, useEffect,
|
|
||||||
} from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import './ImagePack.scss';
|
import './ImagePack.scss';
|
||||||
|
|
||||||
|
@ -19,41 +17,41 @@ import ImagePackProfile from './ImagePackProfile';
|
||||||
import ImagePackItem from './ImagePackItem';
|
import ImagePackItem from './ImagePackItem';
|
||||||
import ImagePackUpload from './ImagePackUpload';
|
import ImagePackUpload from './ImagePackUpload';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const renameImagePackItem = (shortcode) => new Promise((resolve) => {
|
const renameImagePackItem = (shortcode) =>
|
||||||
let isCompleted = false;
|
new Promise((resolve) => {
|
||||||
|
let isCompleted = false;
|
||||||
|
|
||||||
openReusableDialog(
|
openReusableDialog(
|
||||||
<Text variant="s1" weight="medium">Rename</Text>,
|
<Text variant="s1" weight="medium">
|
||||||
(requestClose) => (
|
Rename
|
||||||
<div style={{ padding: 'var(--sp-normal)' }}>
|
</Text>,
|
||||||
<form
|
(requestClose) => (
|
||||||
onSubmit={(e) => {
|
<div style={{ padding: 'var(--sp-normal)' }}>
|
||||||
e.preventDefault();
|
<form
|
||||||
const sc = e.target.shortcode.value;
|
onSubmit={(e) => {
|
||||||
if (sc.trim() === '') return;
|
e.preventDefault();
|
||||||
isCompleted = true;
|
const sc = e.target.shortcode.value;
|
||||||
resolve(sc.trim());
|
if (sc.trim() === '') return;
|
||||||
requestClose();
|
isCompleted = true;
|
||||||
}}
|
resolve(sc.trim());
|
||||||
>
|
requestClose();
|
||||||
<Input
|
}}
|
||||||
value={shortcode}
|
>
|
||||||
name="shortcode"
|
<Input value={shortcode} name="shortcode" label="Shortcode" autoFocus required />
|
||||||
label="Shortcode"
|
<div style={{ height: 'var(--sp-normal)' }} />
|
||||||
autoFocus
|
<Button variant="primary" type="submit">
|
||||||
required
|
Rename
|
||||||
/>
|
</Button>
|
||||||
<div style={{ height: 'var(--sp-normal)' }} />
|
</form>
|
||||||
<Button variant="primary" type="submit">Rename</Button>
|
</div>
|
||||||
</form>
|
),
|
||||||
</div>
|
() => {
|
||||||
),
|
if (!isCompleted) resolve(null);
|
||||||
() => {
|
}
|
||||||
if (!isCompleted) resolve(null);
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function getUsage(usage) {
|
function getUsage(usage) {
|
||||||
if (usage.includes('emoticon') && usage.includes('sticker')) return 'both';
|
if (usage.includes('emoticon') && usage.includes('sticker')) return 'both';
|
||||||
|
@ -79,7 +77,7 @@ function useRoomImagePack(roomId, stateKey) {
|
||||||
|
|
||||||
const pack = useMemo(() => {
|
const pack = useMemo(() => {
|
||||||
const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
|
const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
|
||||||
return ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent())
|
return ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent());
|
||||||
}, [room, stateKey]);
|
}, [room, stateKey]);
|
||||||
|
|
||||||
const sendPackContent = (content) => {
|
const sendPackContent = (content) => {
|
||||||
|
@ -96,10 +94,13 @@ function useUserImagePack() {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const pack = useMemo(() => {
|
const pack = useMemo(() => {
|
||||||
const packEvent = mx.getAccountData('im.ponies.user_emotes');
|
const packEvent = mx.getAccountData('im.ponies.user_emotes');
|
||||||
return ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? {
|
return ImagePackBuilder.parsePack(
|
||||||
pack: { display_name: 'Personal' },
|
mx.getUserId(),
|
||||||
images: {},
|
packEvent?.getContent() ?? {
|
||||||
})
|
pack: { display_name: 'Personal' },
|
||||||
|
images: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
}, [mx]);
|
}, [mx]);
|
||||||
|
|
||||||
const sendPackContent = (content) => {
|
const sendPackContent = (content) => {
|
||||||
|
@ -119,10 +120,7 @@ function useImagePackHandles(pack, sendPackContent) {
|
||||||
if (typeof key !== 'string') return undefined;
|
if (typeof key !== 'string') return undefined;
|
||||||
let newKey = key?.replace(/\s/g, '_');
|
let newKey = key?.replace(/\s/g, '_');
|
||||||
if (pack.getImages().get(newKey)) {
|
if (pack.getImages().get(newKey)) {
|
||||||
newKey = suffixRename(
|
newKey = suffixRename(newKey, (suffixedKey) => pack.getImages().get(suffixedKey));
|
||||||
newKey,
|
|
||||||
(suffixedKey) => pack.getImages().get(suffixedKey),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return newKey;
|
return newKey;
|
||||||
};
|
};
|
||||||
|
@ -163,7 +161,7 @@ function useImagePackHandles(pack, sendPackContent) {
|
||||||
'Delete',
|
'Delete',
|
||||||
`Are you sure that you want to delete "${key}"?`,
|
`Are you sure that you want to delete "${key}"?`,
|
||||||
'Delete',
|
'Delete',
|
||||||
'danger',
|
'danger'
|
||||||
);
|
);
|
||||||
if (!isConfirmed) return;
|
if (!isConfirmed) return;
|
||||||
pack.removeImage(key);
|
pack.removeImage(key);
|
||||||
|
@ -226,6 +224,7 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
const room = mx.getRoom(roomId);
|
const room = mx.getRoom(roomId);
|
||||||
const [viewMore, setViewMore] = useState(false);
|
const [viewMore, setViewMore] = useState(false);
|
||||||
const [isGlobal, setIsGlobal] = useState(isGlobalPack(mx, roomId, stateKey));
|
const [isGlobal, setIsGlobal] = useState(isGlobalPack(mx, roomId, stateKey));
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey);
|
const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey);
|
||||||
|
|
||||||
|
@ -253,7 +252,7 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
'Delete Pack',
|
'Delete Pack',
|
||||||
`Are you sure that you want to delete "${pack.displayName}"?`,
|
`Are you sure that you want to delete "${pack.displayName}"?`,
|
||||||
'Delete',
|
'Delete',
|
||||||
'danger',
|
'danger'
|
||||||
);
|
);
|
||||||
if (!isConfirmed) return;
|
if (!isConfirmed) return;
|
||||||
handlePackDelete(stateKey);
|
handlePackDelete(stateKey);
|
||||||
|
@ -264,7 +263,19 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
return (
|
return (
|
||||||
<div className="image-pack">
|
<div className="image-pack">
|
||||||
<ImagePackProfile
|
<ImagePackProfile
|
||||||
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
|
avatarUrl={
|
||||||
|
pack.avatarUrl
|
||||||
|
? mx.mxcUrlToHttp(
|
||||||
|
pack.avatarUrl,
|
||||||
|
42,
|
||||||
|
42,
|
||||||
|
'crop',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
displayName={pack.displayName ?? 'Unknown'}
|
displayName={pack.displayName ?? 'Unknown'}
|
||||||
attribution={pack.attribution}
|
attribution={pack.attribution}
|
||||||
usage={getUsage(pack.usage)}
|
usage={getUsage(pack.usage)}
|
||||||
|
@ -272,10 +283,8 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
onAvatarChange={canChange ? handleAvatarChange : null}
|
onAvatarChange={canChange ? handleAvatarChange : null}
|
||||||
onEditProfile={canChange ? handleEditProfile : null}
|
onEditProfile={canChange ? handleEditProfile : null}
|
||||||
/>
|
/>
|
||||||
{ canChange && (
|
{canChange && <ImagePackUpload onUpload={handleAddItem} />}
|
||||||
<ImagePackUpload onUpload={handleAddItem} />
|
{images.length === 0 ? null : (
|
||||||
)}
|
|
||||||
{ images.length === 0 ? null : (
|
|
||||||
<div>
|
<div>
|
||||||
<div className="image-pack__header">
|
<div className="image-pack__header">
|
||||||
<Text variant="b3">Image</Text>
|
<Text variant="b3">Image</Text>
|
||||||
|
@ -285,7 +294,15 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
{images.map(([shortcode, image]) => (
|
{images.map(([shortcode, image]) => (
|
||||||
<ImagePackItem
|
<ImagePackItem
|
||||||
key={shortcode}
|
key={shortcode}
|
||||||
url={mx.mxcUrlToHttp(image.mxc)}
|
url={mx.mxcUrlToHttp(
|
||||||
|
image.mxc,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)}
|
||||||
shortcode={shortcode}
|
shortcode={shortcode}
|
||||||
usage={getUsage(image.usage)}
|
usage={getUsage(image.usage)}
|
||||||
onUsageChange={canChange ? handleUsageItem : undefined}
|
onUsageChange={canChange ? handleUsageItem : undefined}
|
||||||
|
@ -299,14 +316,14 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
<div className="image-pack__footer">
|
<div className="image-pack__footer">
|
||||||
{pack.images.size > 2 && (
|
{pack.images.size > 2 && (
|
||||||
<Button onClick={() => setViewMore(!viewMore)}>
|
<Button onClick={() => setViewMore(!viewMore)}>
|
||||||
{
|
{viewMore ? 'View less' : `View ${pack.images.size - 2} more`}
|
||||||
viewMore
|
</Button>
|
||||||
? 'View less'
|
)}
|
||||||
: `View ${pack.images.size - 2} more`
|
{handlePackDelete && (
|
||||||
}
|
<Button variant="danger" onClick={handleDeletePack}>
|
||||||
|
Delete Pack
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{ handlePackDelete && <Button variant="danger" onClick={handleDeletePack}>Delete Pack</Button>}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="image-pack__global">
|
<div className="image-pack__global">
|
||||||
|
@ -332,6 +349,7 @@ ImagePack.propTypes = {
|
||||||
function ImagePackUser() {
|
function ImagePackUser() {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const [viewMore, setViewMore] = useState(false);
|
const [viewMore, setViewMore] = useState(false);
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const { pack, sendPackContent } = useUserImagePack();
|
const { pack, sendPackContent } = useUserImagePack();
|
||||||
|
|
||||||
|
@ -350,7 +368,19 @@ function ImagePackUser() {
|
||||||
return (
|
return (
|
||||||
<div className="image-pack">
|
<div className="image-pack">
|
||||||
<ImagePackProfile
|
<ImagePackProfile
|
||||||
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
|
avatarUrl={
|
||||||
|
pack.avatarUrl
|
||||||
|
? mx.mxcUrlToHttp(
|
||||||
|
pack.avatarUrl,
|
||||||
|
42,
|
||||||
|
42,
|
||||||
|
'crop',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
displayName={pack.displayName ?? 'Personal'}
|
displayName={pack.displayName ?? 'Personal'}
|
||||||
attribution={pack.attribution}
|
attribution={pack.attribution}
|
||||||
usage={getUsage(pack.usage)}
|
usage={getUsage(pack.usage)}
|
||||||
|
@ -359,7 +389,7 @@ function ImagePackUser() {
|
||||||
onEditProfile={handleEditProfile}
|
onEditProfile={handleEditProfile}
|
||||||
/>
|
/>
|
||||||
<ImagePackUpload onUpload={handleAddItem} />
|
<ImagePackUpload onUpload={handleAddItem} />
|
||||||
{ images.length === 0 ? null : (
|
{images.length === 0 ? null : (
|
||||||
<div>
|
<div>
|
||||||
<div className="image-pack__header">
|
<div className="image-pack__header">
|
||||||
<Text variant="b3">Image</Text>
|
<Text variant="b3">Image</Text>
|
||||||
|
@ -369,7 +399,15 @@ function ImagePackUser() {
|
||||||
{images.map(([shortcode, image]) => (
|
{images.map(([shortcode, image]) => (
|
||||||
<ImagePackItem
|
<ImagePackItem
|
||||||
key={shortcode}
|
key={shortcode}
|
||||||
url={mx.mxcUrlToHttp(image.mxc)}
|
url={mx.mxcUrlToHttp(
|
||||||
|
image.mxc,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)}
|
||||||
shortcode={shortcode}
|
shortcode={shortcode}
|
||||||
usage={getUsage(image.usage)}
|
usage={getUsage(image.usage)}
|
||||||
onUsageChange={handleUsageItem}
|
onUsageChange={handleUsageItem}
|
||||||
|
@ -379,14 +417,10 @@ function ImagePackUser() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(pack.images.size > 2) && (
|
{pack.images.size > 2 && (
|
||||||
<div className="image-pack__footer">
|
<div className="image-pack__footer">
|
||||||
<Button onClick={() => setViewMore(!viewMore)}>
|
<Button onClick={() => setViewMore(!viewMore)}>
|
||||||
{
|
{viewMore ? 'View less' : `View ${pack.images.size - 2} more`}
|
||||||
viewMore
|
|
||||||
? 'View less'
|
|
||||||
: `View ${pack.images.size - 2} more`
|
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -435,29 +469,33 @@ function ImagePackGlobal() {
|
||||||
<div className="image-pack-global">
|
<div className="image-pack-global">
|
||||||
<MenuHeader>Global packs</MenuHeader>
|
<MenuHeader>Global packs</MenuHeader>
|
||||||
<div>
|
<div>
|
||||||
{
|
{roomIdToStateKeys.size > 0 ? (
|
||||||
roomIdToStateKeys.size > 0
|
[...roomIdToStateKeys].map(([roomId, stateKeys]) => {
|
||||||
? [...roomIdToStateKeys].map(([roomId, stateKeys]) => {
|
const room = mx.getRoom(roomId);
|
||||||
const room = mx.getRoom(roomId);
|
return stateKeys.map((stateKey) => {
|
||||||
|
const data = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
|
||||||
|
const pack = ImagePackBuilder.parsePack(data?.getId(), data?.getContent());
|
||||||
|
if (!pack) return null;
|
||||||
return (
|
return (
|
||||||
stateKeys.map((stateKey) => {
|
<div className="image-pack__global" key={pack.id}>
|
||||||
const data = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
|
<Checkbox
|
||||||
const pack = ImagePackBuilder.parsePack(data?.getId(), data?.getContent());
|
variant="positive"
|
||||||
if (!pack) return null;
|
onToggle={() => handleChange(roomId, stateKey)}
|
||||||
return (
|
isActive
|
||||||
<div className="image-pack__global" key={pack.id}>
|
/>
|
||||||
<Checkbox variant="positive" onToggle={() => handleChange(roomId, stateKey)} isActive />
|
<div>
|
||||||
<div>
|
<Text variant="b2">{pack.displayName ?? 'Unknown'}</Text>
|
||||||
<Text variant="b2">{pack.displayName ?? 'Unknown'}</Text>
|
<Text variant="b3">{room.name}</Text>
|
||||||
<Text variant="b3">{room.name}</Text>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
: <div className="image-pack-global__empty"><Text>No global packs</Text></div>
|
})
|
||||||
}
|
) : (
|
||||||
|
<div className="image-pack-global__empty">
|
||||||
|
<Text>No global packs</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,11 +18,13 @@ import UserIC from '../../../../public/res/ic/outlined/user.svg';
|
||||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||||
import { getDMRoomFor } from '../../utils/matrix';
|
import { getDMRoomFor } from '../../utils/matrix';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
|
function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
|
||||||
const [isSearching, updateIsSearching] = useState(false);
|
const [isSearching, updateIsSearching] = useState(false);
|
||||||
const [searchQuery, updateSearchQuery] = useState({});
|
const [searchQuery, updateSearchQuery] = useState({});
|
||||||
const [users, updateUsers] = useState([]);
|
const [users, updateUsers] = useState([]);
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const [procUsers, updateProcUsers] = useState(new Set()); // proc stands for processing.
|
const [procUsers, updateProcUsers] = useState(new Set()); // proc stands for processing.
|
||||||
const [procUserError, updateUserProcError] = useState(new Map());
|
const [procUserError, updateUserProcError] = useState(new Map());
|
||||||
|
@ -222,7 +224,15 @@ function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
|
||||||
key={userId}
|
key={userId}
|
||||||
avatarSrc={
|
avatarSrc={
|
||||||
typeof user.avatar_url === 'string'
|
typeof user.avatar_url === 'string'
|
||||||
? mx.mxcUrlToHttp(user.avatar_url, 42, 42, 'crop')
|
? mx.mxcUrlToHttp(
|
||||||
|
user.avatar_url,
|
||||||
|
42,
|
||||||
|
42,
|
||||||
|
'crop',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
name={name}
|
name={name}
|
||||||
|
|
|
@ -14,15 +14,19 @@ import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||||
|
|
||||||
import './ProfileEditor.scss';
|
import './ProfileEditor.scss';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
function ProfileEditor({ userId }) {
|
function ProfileEditor({ userId }) {
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const user = mx.getUser(mx.getUserId());
|
const user = mx.getUser(mx.getUserId());
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const displayNameRef = useRef(null);
|
const displayNameRef = useRef(null);
|
||||||
const [avatarSrc, setAvatarSrc] = useState(
|
const [avatarSrc, setAvatarSrc] = useState(
|
||||||
user.avatarUrl ? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop') : null
|
user.avatarUrl
|
||||||
|
? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop', undefined, undefined, useAuthentication)
|
||||||
|
: null
|
||||||
);
|
);
|
||||||
const [username, setUsername] = useState(user.displayName);
|
const [username, setUsername] = useState(user.displayName);
|
||||||
const [disabled, setDisabled] = useState(true);
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
@ -31,13 +35,25 @@ function ProfileEditor({ userId }) {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
mx.getProfileInfo(mx.getUserId()).then((info) => {
|
mx.getProfileInfo(mx.getUserId()).then((info) => {
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
setAvatarSrc(info.avatar_url ? mx.mxcUrlToHttp(info.avatar_url, 80, 80, 'crop') : null);
|
setAvatarSrc(
|
||||||
|
info.avatar_url
|
||||||
|
? mx.mxcUrlToHttp(
|
||||||
|
info.avatar_url,
|
||||||
|
80,
|
||||||
|
80,
|
||||||
|
'crop',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
);
|
||||||
setUsername(info.displayname);
|
setUsername(info.displayname);
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
};
|
};
|
||||||
}, [mx, userId]);
|
}, [mx, userId, useAuthentication]);
|
||||||
|
|
||||||
const handleAvatarUpload = async (url) => {
|
const handleAvatarUpload = async (url) => {
|
||||||
if (url === null) {
|
if (url === null) {
|
||||||
|
@ -54,7 +70,7 @@ function ProfileEditor({ userId }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mx.setAvatarUrl(url);
|
mx.setAvatarUrl(url);
|
||||||
setAvatarSrc(mx.mxcUrlToHttp(url, 80, 80, 'crop'));
|
setAvatarSrc(mx.mxcUrlToHttp(url, 80, 80, 'crop', undefined, undefined, useAuthentication));
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveDisplayName = () => {
|
const saveDisplayName = () => {
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||||
import { getDMRoomFor } from '../../utils/matrix';
|
import { getDMRoomFor } from '../../utils/matrix';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
function ModerationTools({ roomId, userId }) {
|
function ModerationTools({ roomId, userId }) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
@ -329,6 +330,7 @@ function useRerenderOnProfileChange(roomId, userId) {
|
||||||
function ProfileViewer() {
|
function ProfileViewer() {
|
||||||
const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog();
|
const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog();
|
||||||
useRerenderOnProfileChange(roomId, userId);
|
useRerenderOnProfileChange(roomId, userId);
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const room = mx.getRoom(roomId);
|
const room = mx.getRoom(roomId);
|
||||||
|
@ -338,7 +340,9 @@ function ProfileViewer() {
|
||||||
const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(mx, userId);
|
const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(mx, userId);
|
||||||
const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl;
|
const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl;
|
||||||
const avatarUrl =
|
const avatarUrl =
|
||||||
avatarMxc && avatarMxc !== 'null' ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null;
|
avatarMxc && avatarMxc !== 'null'
|
||||||
|
? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop', undefined, undefined, useAuthentication)
|
||||||
|
: null;
|
||||||
|
|
||||||
const powerLevel = roomMember?.powerLevel || 0;
|
const powerLevel = roomMember?.powerLevel || 0;
|
||||||
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
|
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
|
||||||
|
|
|
@ -15,7 +15,7 @@ export function AuthFooter() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
v4.1.0
|
v4.2.0
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
|
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
|
||||||
Twitter
|
Twitter
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function WelcomePage() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
v4.1.0
|
v4.2.0
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,3 +273,20 @@ export const mxcUrlToHttp = (
|
||||||
allowRedirects,
|
allowRedirects,
|
||||||
useAuthentication
|
useAuthentication
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const downloadMedia = async (src: string): Promise<Blob> => {
|
||||||
|
// this request is authenticated by service worker
|
||||||
|
const res = await fetch(src, { method: 'GET' });
|
||||||
|
const blob = await res.blob();
|
||||||
|
return blob;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const downloadEncryptedMedia = async (
|
||||||
|
src: string,
|
||||||
|
decryptContent: (buf: ArrayBuffer) => Promise<Blob>
|
||||||
|
): Promise<Blob> => {
|
||||||
|
const encryptedContent = await downloadMedia(src);
|
||||||
|
const decryptedContent = await decryptContent(await encryptedContent.arrayBuffer());
|
||||||
|
|
||||||
|
return decryptedContent;
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const cons = {
|
const cons = {
|
||||||
version: '4.1.0',
|
version: '4.2.0',
|
||||||
secretKey: {
|
secretKey: {
|
||||||
ACCESS_TOKEN: 'cinny_access_token',
|
ACCESS_TOKEN: 'cinny_access_token',
|
||||||
DEVICE_ID: 'cinny_device_id',
|
DEVICE_ID: 'cinny_device_id',
|
||||||
|
|
Loading…
Reference in a new issue