Skip to content

Commit

Permalink
Implement the download_file widget action
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Weimann <[email protected]>
  • Loading branch information
weeman1337 committed Aug 26, 2024
1 parent 8518d2f commit 354910f
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-jeans-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@matrix-widget-toolkit/api': minor
---

Add support for the download_file widget action
66 changes: 66 additions & 0 deletions example-widget-mui/src/UploadImagePage/Image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2024 Nordeck IT + Consulting GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { useWidgetApi } from '@matrix-widget-toolkit/react';
import React, { useCallback, useEffect, useState } from 'react';

type ImageProps = {
alt?: string;
/**
* MXC URI of the image that should be shown
*/
contentUrl: string;
};

/**
* Component that loads the image from the content repository and displays it.
*/
export const Image: React.FC<ImageProps> = function ({
contentUrl,
...imageProps
}) {
const [dataUrl, setDataUrl] = useState<string>();
const widgetApi = useWidgetApi();

const handleLoad = useCallback(() => {
if (dataUrl) {
URL.revokeObjectURL(dataUrl);
}
}, [dataUrl]);

useEffect(() => {
(async () => {
try {
const result = await widgetApi.downloadFile(contentUrl);

if (!(result.file instanceof Blob)) {
throw new Error('Got non Blob file response');
}

const downloadedFileDataUrl = URL.createObjectURL(result.file);
setDataUrl(downloadedFileDataUrl);
} catch (error) {
console.log('Error downloading file', error);
}
})();
}, [contentUrl]);

if (dataUrl === undefined) {
return null;
}

return <img {...imageProps} src={dataUrl} onLoad={handleLoad} />;
};
9 changes: 3 additions & 6 deletions example-widget-mui/src/UploadImagePage/ImageListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
UploadedImageEvent,
isValidUploadedImage,
} from '../events';
import { Image } from './Image';

export const ImageListView = (): ReactElement => {
const widgetApi = useWidgetApi();
Expand Down Expand Up @@ -75,13 +76,9 @@ export const ImageListView = (): ReactElement => {
{imageNames.length > 0 &&
imageNames.map((image) => (
<ImageListItem key={image.event_id}>
<img
src={`${getHttpUriForMxc(
image.content.url,
widgetApi.widgetParameters.baseUrl,
)}?w=164&h=164&fit=crop&auto=format`}
<Image
alt={image.content.name}
loading="lazy"
contentUrl={image.content.url}
/>
<ImageListItemBar
title={image.content.name}
Expand Down
3 changes: 2 additions & 1 deletion example-widget-mui/src/UploadImagePage/UploadImagePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,11 @@ export const UploadImagePage = (): ReactElement => {
ROOM_EVENT_UPLOADED_IMAGE,
),
WidgetApiFromWidgetAction.MSC4039UploadFileAction,
WidgetApiFromWidgetAction.MSC4039DownloadFileAction,
WidgetApiFromWidgetAction.MSC4039GetMediaConfigAction,
]}
>
{/*
{/*
The StoreProvider is located here to keep the example small. Normal
applications would locate it outside of the router to establish a
single, global store.
Expand Down
3 changes: 3 additions & 0 deletions packages/api/api-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
```ts

import { Capability } from 'matrix-widget-api';
import { IDownloadFileActionFromWidgetResponseData } from 'matrix-widget-api';
import { IGetMediaConfigActionFromWidgetResponseData } from 'matrix-widget-api';
import { IModalWidgetCreateData } from 'matrix-widget-api';
import { IModalWidgetOpenRequestDataButton } from 'matrix-widget-api';
Expand Down Expand Up @@ -271,6 +272,7 @@ export type WidgetApi = {
}>;
getMediaConfig(): Promise<IGetMediaConfigActionFromWidgetResponseData>;
uploadFile(file: XMLHttpRequestBodyInit): Promise<IUploadFileActionFromWidgetResponseData>;
downloadFile(contentUrl: string): Promise<IDownloadFileActionFromWidgetResponseData>;
};

// @public
Expand All @@ -281,6 +283,7 @@ export class WidgetApiImpl implements WidgetApi {
widgetParameters: WidgetParameters, { capabilities, supportStandalone }?: WidgetApiOptions);
closeModal<T extends IModalWidgetReturnData>(data?: T): Promise<void>;
static create({ capabilities, supportStandalone, }?: WidgetApiOptions): Promise<WidgetApi>;
downloadFile(contentUrl: string): Promise<IDownloadFileActionFromWidgetResponseData>;
getMediaConfig(): Promise<IGetMediaConfigActionFromWidgetResponseData>;
getWidgetConfig<T extends IWidgetApiRequestData>(): Readonly<WidgetConfig<T> | undefined>;
hasCapabilities(capabilities: Array<WidgetEventCapability | Capability>): boolean;
Expand Down
8 changes: 8 additions & 0 deletions packages/api/src/api/WidgetApiImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import {
Capability,
IDownloadFileActionFromWidgetResponseData,
IGetMediaConfigActionFromWidgetResponseData,
IModalWidgetCreateData,
IModalWidgetOpenRequestDataButton,
Expand Down Expand Up @@ -799,4 +800,11 @@ export class WidgetApiImpl implements WidgetApi {
): Promise<IUploadFileActionFromWidgetResponseData> {
return await this.matrixWidgetApi.uploadFile(file);
}

/** {@inheritdoc WidgetApi.downloadFile} */
async downloadFile(
contentUrl: string,
): Promise<IDownloadFileActionFromWidgetResponseData> {
return await this.matrixWidgetApi.downloadFile(contentUrl);
}
}
10 changes: 10 additions & 0 deletions packages/api/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import {
Capability,
IDownloadFileActionFromWidgetResponseData,
IGetMediaConfigActionFromWidgetResponseData,
IModalWidgetCreateData,
IModalWidgetOpenRequestDataButton,
Expand Down Expand Up @@ -561,5 +562,14 @@ export type WidgetApi = {
file: XMLHttpRequestBodyInit,
): Promise<IUploadFileActionFromWidgetResponseData>;

/**
* Download a file to the media repository on the homeserver.
* @param contentUrl - MXC URI of the file to download
* @returns resolves to an object with: file - the file contents
*/
downloadFile(
contentUrl: string,
): Promise<IDownloadFileActionFromWidgetResponseData>;

// TODO: sendSticker, setAlwaysOnScreen
};
1 change: 1 addition & 0 deletions packages/testing/src/api/mockWidgetApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export function mockWidgetApi(opts?: {
uploadFile: jest.fn().mockResolvedValue({
content_uri: 'mxc://...',
}),
downloadFile: jest.fn(),
};

widgetApi.receiveRoomEvents.mockImplementation(async (type, options) => {
Expand Down

0 comments on commit 354910f

Please sign in to comment.