Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/chatty-camels-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/model-typings': patch
'@rocket.chat/models': patch
'@rocket.chat/meteor': patch
---

Fixes `/sendEmailAttachment` to support sending multiple file attachments in a single email
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Icon, Box } from '@rocket.chat/fuselage';
import type { ComponentProps } from 'react';

import { useOmnichannelRoomIcon } from './context/OmnichannelRoomIconContext';
import { AsyncStatePhase } from '../../../lib/asyncState/AsyncStatePhase';

type OmnichannelAppSourceRoomIconProps = {
source: IOmnichannelSourceFromApp;
Expand All @@ -14,9 +13,9 @@ type OmnichannelAppSourceRoomIconProps = {

export const OmnichannelAppSourceRoomIcon = ({ source, color, size, placement }: OmnichannelAppSourceRoomIconProps) => {
const icon = (placement === 'sidebar' && source.sidebarIcon) || source.defaultIcon;
const { phase, value } = useOmnichannelRoomIcon(source.id, icon || '');
const value = useOmnichannelRoomIcon(source.id, icon || '');

if ([AsyncStatePhase.REJECTED, AsyncStatePhase.LOADING].includes(phase)) {
if (!value) {
return <Icon name='headset' size={size} color={color} />;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import { createContext, useMemo, useContext, useSyncExternalStore } from 'react';

import type { AsyncState } from '../../../../lib/asyncState/AsyncState';
import { AsyncStatePhase } from '../../../../lib/asyncState/AsyncStatePhase';

type IOmnichannelRoomIconContext = {
queryIcon(app: string, icon: string): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => AsyncState<string>];
queryIcon(app: string, icon: string): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined];
};

export const OmnichannelRoomIconContext = createContext<IOmnichannelRoomIconContext>({
queryIcon: () => [
(): (() => void) => (): void => undefined,
(): AsyncState<string> => ({
phase: AsyncStatePhase.LOADING,
value: undefined,
error: undefined,
}),
],
queryIcon: () => [(): (() => void) => (): void => undefined, () => undefined],
});

export const useOmnichannelRoomIcon = (app: string, icon: string): AsyncState<string> => {
export const useOmnichannelRoomIcon = (app: string, icon: string): string | undefined => {
const { queryIcon } = useContext(OmnichannelRoomIconContext);
const [subscribe, getSnapshot] = useMemo(() => queryIcon(app, icon), [app, queryIcon, icon]);
return useSyncExternalStore(subscribe, getSnapshot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import type { ReactNode } from 'react';
import { useCallback, useMemo, useSyncExternalStore } from 'react';
import { createPortal } from 'react-dom';

import type { AsyncState } from '../../../../lib/asyncState/AsyncState';
import { AsyncStatePhase } from '../../../../lib/asyncState/AsyncStatePhase';
import { OmnichannelRoomIconContext } from '../context/OmnichannelRoomIconContext';
import OmnichannelRoomIconManager from '../lib/OmnichannelRoomIconManager';

Expand All @@ -30,32 +28,16 @@ export const OmnichannelRoomIconProvider = ({ children }: OmnichannelRoomIconPro
return (
<OmnichannelRoomIconContext.Provider
value={useMemo(() => {
const extractSnapshot = (app: string, iconName: string): AsyncState<string> => {
const icon = OmnichannelRoomIconManager.get(app, iconName);

if (icon) {
return {
phase: AsyncStatePhase.RESOLVED,
value: icon,
error: undefined,
};
}

return {
phase: AsyncStatePhase.LOADING,
value: undefined,
error: undefined,
};
};
const extractSnapshot = (app: string, iconName: string) => OmnichannelRoomIconManager.get(app, iconName);

// We cache all the icons here, so that we can use them in the OmnichannelRoomIcon component
const snapshots = new Map<string, AsyncState<string>>();
const snapshots = new Map<string, string | undefined>();

return {
queryIcon: (
app: string,
iconName: string,
): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => AsyncState<string>] => [
): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined] => [
(callback): (() => void) =>
OmnichannelRoomIconManager.on(`${app}-${iconName}`, () => {
snapshots.set(`${app}-${iconName}`, extractSnapshot(app, iconName));
Expand All @@ -65,7 +47,7 @@ export const OmnichannelRoomIconProvider = ({ children }: OmnichannelRoomIconPro
}),

// No problem here, because it's return value is a cached in the snapshots map on subsequent calls
(): AsyncState<string> => {
() => {
let snapshot = snapshots.get(`${app}-${iconName}`);

if (!snapshot) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,13 @@ type MapViewProps = {
const MapView = ({ latitude, longitude }: MapViewProps) => {
const googleMapsApiKey = useSetting('MapView_GMapsAPIKey', '');

const linkUrl = `https://maps.google.com/maps?daddr=${latitude},${longitude}`;

const imageUrl = useAsyncImage(
const { data: imageUrl } = useAsyncImage(
googleMapsApiKey
? `https://maps.googleapis.com/maps/api/staticmap?zoom=14&size=250x250&markers=color:gray%7Clabel:%7C${latitude},${longitude}&key=${googleMapsApiKey}`
: undefined,
);

if (!linkUrl) {
return null;
}
const linkUrl = `https://maps.google.com/maps?daddr=${latitude},${longitude}`;

if (!imageUrl) {
return <MapViewFallback linkUrl={linkUrl} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import { useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';

import { useAsyncState } from '../../../../../hooks/useAsyncState';
export const useAsyncImage = (src: string | undefined) => {
return useQuery({
queryKey: ['async-image', src],
queryFn: () =>
new Promise<string>((resolve, reject) => {
if (!src) {
reject(new Error('No src provided'));
return;
}

export const useAsyncImage = (src: string | undefined): string | undefined => {
const { value, resolve, reject, reset } = useAsyncState<string>();

useEffect(() => {
reset();

if (!src) {
return;
}

const image = new Image();
image.addEventListener('load', () => {
resolve(image.src);
});
image.addEventListener('error', (e) => {
reject(e.error);
});
image.src = src;
}, [src, resolve, reject, reset]);

return value;
const image = new Image();
image.addEventListener('load', () => {
resolve(image.src);
});
image.addEventListener('error', () => {
reject(new Error(`Failed to load image: ${src}`));
});
image.src = src;
}),
enabled: Boolean(src),
retry: false,
});
};
32 changes: 2 additions & 30 deletions apps/meteor/client/contexts/AppsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import type { Serialized } from '@rocket.chat/core-typings';
import { createContext } from 'react';

import type { IAppExternalURL, ICategory } from '../apps/@types/IOrchestrator';
import type { AsyncState } from '../lib/asyncState';
import { AsyncStatePhase } from '../lib/asyncState';
import type { App } from '../views/marketplace/types';

export interface IAppsOrchestrator {
Expand All @@ -26,32 +24,6 @@ export interface IAppsOrchestrator {
getCategories(): Promise<Serialized<ICategory[]>>;
}

export type AppsContextValue = {
installedApps: AsyncState<{ apps: App[] }>;
marketplaceApps: AsyncState<{ apps: App[] }>;
privateApps: AsyncState<{ apps: App[] }>;
reload: () => Promise<void>;
orchestrator?: IAppsOrchestrator;
privateAppsEnabled: boolean;
};
type AppsContextValue = IAppsOrchestrator | undefined;

export const AppsContext = createContext<AppsContextValue>({
installedApps: {
phase: AsyncStatePhase.LOADING,
value: undefined,
error: undefined,
},
marketplaceApps: {
phase: AsyncStatePhase.LOADING,
value: undefined,
error: undefined,
},
privateApps: {
phase: AsyncStatePhase.LOADING,
value: undefined,
error: undefined,
},
reload: () => Promise.resolve(),
orchestrator: undefined,
privateAppsEnabled: false,
});
export const AppsContext = createContext<AppsContextValue>(undefined);
8 changes: 0 additions & 8 deletions apps/meteor/client/contexts/hooks/useAppsReload.ts

This file was deleted.

6 changes: 0 additions & 6 deletions apps/meteor/client/contexts/hooks/useAppsResult.ts

This file was deleted.

62 changes: 0 additions & 62 deletions apps/meteor/client/hooks/lists/useRecordList.ts

This file was deleted.

26 changes: 0 additions & 26 deletions apps/meteor/client/hooks/lists/useScrollableMessageList.ts

This file was deleted.

30 changes: 0 additions & 30 deletions apps/meteor/client/hooks/lists/useScrollableRecordList.ts

This file was deleted.

Loading
Loading