Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
85879ee
refactor: remove `useAsyncState` hook
tassoevan Dec 23, 2025
4b1968a
refactor: add marketplace query keys
tassoevan Dec 23, 2025
d881673
refactor: decompose some `AppsContext` fields
tassoevan Dec 23, 2025
114948f
refactor: remove async state from `AppsContext`
tassoevan Dec 23, 2025
3ca8dd9
refactor: remove async state handling from `OmnichannelRoomIcon` comp…
tassoevan Dec 23, 2025
409800d
refactor: replace record list with infinite query in `useTeamsChannel…
tassoevan Dec 23, 2025
c2d7299
refactor: replace record list with infinite query in `useVideoConfList`
tassoevan Dec 23, 2025
a8d9194
refactor: replace record list with infinite query in `useFilesList`
tassoevan Dec 23, 2025
883ae2e
refactor: replace record list with infinite query in `useImagesList`
tassoevan Dec 23, 2025
3abd947
refactor: replace record list with infinite query in `useCannedRespon…
tassoevan Dec 23, 2025
337fb24
refactor: replace record list with infinite query in `useRoomsList`
tassoevan Dec 23, 2025
8ab7644
refactor: replace record list with infinite query in `useHistoryMessa…
tassoevan Dec 24, 2025
ce750c5
refactor: remove async state API
tassoevan Dec 24, 2025
273394a
fix: change query key to match query function closure
tassoevan Dec 26, 2025
54cec61
fix: incorrect spreading object
tassoevan Dec 26, 2025
bfbff5e
refactor: ease type for possibly undefined value
tassoevan Dec 26, 2025
c2a4308
fix: correct broken reference
tassoevan Dec 26, 2025
a788c82
refactor: replace `.sort` with `.toSorted`
tassoevan Dec 26, 2025
ed17b72
refactor: simplify `AppsContext`
tassoevan Dec 26, 2025
02b93e0
refactor: breakdown redundant boolean expressions
tassoevan Dec 26, 2025
38becef
refactor: handle mutation idle state
tassoevan Dec 26, 2025
4ac4b7b
chore: add TODO
tassoevan Dec 26, 2025
b9d4383
fix: handle item count inconsistencies
tassoevan Dec 26, 2025
2991c9b
refactor: replace `.sort` with `.toSorted`
tassoevan Dec 26, 2025
b7d6dc6
fix: escape regexp
tassoevan Dec 26, 2025
47f1531
refactor: remove unused field from type
tassoevan Dec 26, 2025
ebff6b8
refactor: simplify filters
tassoevan Dec 26, 2025
d1284a3
refactor: fix `useAsyncImage` error handling
tassoevan Jan 13, 2026
22c5b17
refactor: remove unnecessary type assertion
tassoevan Jan 13, 2026
1390960
refactor: handle default value for route parameter
tassoevan Jan 13, 2026
49124fd
refactor: review
tassoevan Jan 15, 2026
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
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