snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
This commit is contained in:
3340
packages/ui-contexts/CHANGELOG.md
Normal file
3340
packages/ui-contexts/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
12
packages/ui-contexts/jest.config.ts
Normal file
12
packages/ui-contexts/jest.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import client from '@rocket.chat/jest-presets/client';
|
||||
import type { Config } from 'jest';
|
||||
|
||||
export default {
|
||||
preset: client.preset,
|
||||
setupFilesAfterEnv: [...client.setupFilesAfterEnv],
|
||||
moduleNameMapper: {
|
||||
'^react($|/.+)': '<rootDir>/../../node_modules/react$1',
|
||||
'^react-dom($|/.+)': '<rootDir>/../../node_modules/react-dom$1',
|
||||
'^react-i18next($|/.+)': '<rootDir>/../../node_modules/react-i18next$1',
|
||||
},
|
||||
} satisfies Config;
|
||||
58
packages/ui-contexts/package.json
Normal file
58
packages/ui-contexts/package.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@rocket.chat/ui-contexts",
|
||||
"version": "34.0.0",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"/dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
||||
"dev": "tsc --watch --preserveWatchOutput -p tsconfig.build.json",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"test": "jest",
|
||||
"testunit": "jest",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rocket.chat/password-policies": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rocket.chat/core-typings": "workspace:^",
|
||||
"@rocket.chat/ddp-client": "workspace:~",
|
||||
"@rocket.chat/emitter": "workspace:~",
|
||||
"@rocket.chat/fuselage": "^0.89.0",
|
||||
"@rocket.chat/fuselage-hooks": "^0.43.1",
|
||||
"@rocket.chat/fuselage-tokens": "^0.34.0",
|
||||
"@rocket.chat/i18n": "workspace:~",
|
||||
"@rocket.chat/icons": "^0.49.0",
|
||||
"@rocket.chat/jest-presets": "workspace:~",
|
||||
"@rocket.chat/rest-typings": "workspace:^",
|
||||
"@rocket.chat/tools": "workspace:~",
|
||||
"@types/jest": "~30.0.0",
|
||||
"@types/react": "~19.2.18",
|
||||
"@types/react-dom": "~19.2.5",
|
||||
"eslint": "~9.39.5",
|
||||
"i18next": "~23.4.9",
|
||||
"jest": "~30.2.0",
|
||||
"mongodb": "6.16.0",
|
||||
"react": "~19.2.8",
|
||||
"react-dom": "~19.2.8",
|
||||
"react-virtuoso": "~4.18.12",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rocket.chat/core-typings": "workspace:^",
|
||||
"@rocket.chat/ddp-client": "workspace:^",
|
||||
"@rocket.chat/emitter": "workspace:~",
|
||||
"@rocket.chat/fuselage-hooks": "*",
|
||||
"@rocket.chat/i18n": "workspace:~",
|
||||
"@rocket.chat/rest-typings": "workspace:^",
|
||||
"react": "*"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
30
packages/ui-contexts/src/ActionManagerContext.ts
Normal file
30
packages/ui-contexts/src/ActionManagerContext.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { DistributiveOmit } from '@rocket.chat/core-typings';
|
||||
import type * as UiKit from '@rocket.chat/ui-kit';
|
||||
import { createContext } from 'react';
|
||||
|
||||
/**
|
||||
* An action manager is responsible for handling interactions with the UiKit.
|
||||
*/
|
||||
export interface IActionManager {
|
||||
on(viewId: string, listener: (data: any) => void): void;
|
||||
on(eventName: 'busy', listener: ({ busy }: { busy: boolean }) => void): void;
|
||||
off(viewId: string, listener: (data: any) => any): void;
|
||||
off(eventName: 'busy', listener: ({ busy }: { busy: boolean }) => void): void;
|
||||
notifyBusy(): void;
|
||||
notifyIdle(): void;
|
||||
generateTriggerId(appId: string | undefined): string;
|
||||
emitInteraction(appId: string, userInteraction: DistributiveOmit<UiKit.UserInteraction, 'triggerId'>): Promise<void>;
|
||||
handleServerInteraction(interaction: UiKit.ServerInteraction): UiKit.ServerInteraction['type'] | undefined;
|
||||
getInteractionPayloadByViewId(viewId: UiKit.ContextualBarView['id']):
|
||||
| {
|
||||
view: UiKit.ContextualBarView;
|
||||
}
|
||||
| undefined;
|
||||
openView(surface: 'modal', view: UiKit.ModalView): void;
|
||||
openView(surface: 'banner', view: UiKit.BannerView): void;
|
||||
openView(surface: 'contextual_bar', view: UiKit.ContextualBarView): void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents
|
||||
disposeView(viewId: UiKit.ModalView['id'] | UiKit.BannerView['viewId'] | UiKit.ContextualBarView['id']): void;
|
||||
}
|
||||
|
||||
export const ActionManagerContext = createContext<IActionManager | undefined>(undefined);
|
||||
21
packages/ui-contexts/src/AttachmentContext.ts
Normal file
21
packages/ui-contexts/src/AttachmentContext.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type AttachmentContextValue = {
|
||||
getURL: (url: string) => string;
|
||||
dimensions: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
collapsedByDefault: boolean;
|
||||
autoLoadEmbedMedias: boolean;
|
||||
};
|
||||
|
||||
export const AttachmentContext = createContext<AttachmentContextValue>({
|
||||
getURL: (url: string) => url,
|
||||
dimensions: {
|
||||
width: 368,
|
||||
height: 368,
|
||||
},
|
||||
collapsedByDefault: false,
|
||||
autoLoadEmbedMedias: true,
|
||||
});
|
||||
51
packages/ui-contexts/src/AuthenticationContext.ts
Normal file
51
packages/ui-contexts/src/AuthenticationContext.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import type { LoginServiceConfiguration } from '@rocket.chat/core-typings';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type LoginService = LoginServiceConfiguration & {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export type AuthenticationContextValue = {
|
||||
readonly isLoggingIn: boolean;
|
||||
loginWithPassword: (user: string | { username: string } | { email: string } | { id: string }, password: string) => Promise<void>;
|
||||
loginWithToken: (user: string, callback?: (error: Error | null | undefined) => void) => Promise<void>;
|
||||
loginWithService<T extends LoginServiceConfiguration>(service: T): () => Promise<true>;
|
||||
loginWithCustomOauth: (service: string, options: { redirectUrl: string }, callback?: (response: unknown) => void) => void;
|
||||
loginWithIframe: (token: string, callback?: (error: Error | null | undefined) => void) => Promise<void>;
|
||||
loginWithTokenRoute: (token: string, callback?: (error: Error | null | undefined) => void) => Promise<void>;
|
||||
getLoginToken: () => string | null;
|
||||
unstoreLoginToken: (callback: () => void) => () => void;
|
||||
wipeLocalAuth: () => void;
|
||||
queryLoginServices: {
|
||||
getCurrentValue: () => LoginService[];
|
||||
subscribe: (onStoreChange: () => void) => () => void;
|
||||
};
|
||||
};
|
||||
|
||||
export const AuthenticationContext = createContext<AuthenticationContextValue>({
|
||||
isLoggingIn: false,
|
||||
loginWithService: () => () => Promise.reject(new Error('loginWithService not implemented')),
|
||||
loginWithCustomOauth: () => {
|
||||
throw new Error('loginWithCustomOauth not implemented');
|
||||
},
|
||||
loginWithPassword: async () => Promise.reject(new Error('loginWithPassword not implemented')),
|
||||
loginWithToken: async () => Promise.reject(new Error('loginWithToken not implemented')),
|
||||
loginWithIframe: async () => Promise.reject(new Error('loginWithIframe not implemented')),
|
||||
loginWithTokenRoute: async () => Promise.reject(new Error('loginWithTokenRoute not implemented')),
|
||||
getLoginToken: () => {
|
||||
throw new Error('getLoginToken not implemented');
|
||||
},
|
||||
unstoreLoginToken: () => {
|
||||
throw new Error('unstoreLoginToken not implemented');
|
||||
},
|
||||
wipeLocalAuth: () => {
|
||||
throw new Error('wipeLocalAuth not implemented');
|
||||
},
|
||||
queryLoginServices: {
|
||||
getCurrentValue: () => [],
|
||||
subscribe: (_: () => void) => {
|
||||
throw new Error('queryLoginServices not implemented');
|
||||
},
|
||||
},
|
||||
});
|
||||
38
packages/ui-contexts/src/AuthorizationContext.ts
Normal file
38
packages/ui-contexts/src/AuthorizationContext.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { IRole, IRoom } from '@rocket.chat/core-typings';
|
||||
import type { ObjectId } from 'mongodb';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type AuthorizationContextValue = {
|
||||
queryPermission(
|
||||
permission: string | ObjectId,
|
||||
scope?: string | ObjectId,
|
||||
scopedRoles?: IRole['_id'][],
|
||||
): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean];
|
||||
queryAtLeastOnePermission(
|
||||
permission: (string | ObjectId)[],
|
||||
scope?: string | ObjectId,
|
||||
scopedRoles?: IRole['_id'][],
|
||||
): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean];
|
||||
queryAllPermissions(
|
||||
permission: (string | ObjectId)[],
|
||||
scope?: string | ObjectId,
|
||||
scopedRoles?: IRole['_id'][],
|
||||
): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean];
|
||||
queryRole(
|
||||
role: string | ObjectId,
|
||||
scope?: IRoom['_id'],
|
||||
): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean];
|
||||
getRoles(): ReadonlyMap<IRole['_id'], IRole>;
|
||||
subscribeToRoles(callback: () => void): () => void;
|
||||
};
|
||||
|
||||
const dummyRolesMap: ReadonlyMap<IRole['_id'], IRole> = new Map();
|
||||
|
||||
export const AuthorizationContext = createContext<AuthorizationContextValue>({
|
||||
queryPermission: () => [() => (): void => undefined, (): boolean => false],
|
||||
queryAtLeastOnePermission: () => [() => (): void => undefined, (): boolean => false],
|
||||
queryAllPermissions: () => [() => (): void => undefined, (): boolean => false],
|
||||
queryRole: () => [() => (): void => undefined, (): boolean => false],
|
||||
getRoles: (): ReadonlyMap<IRole['_id'], IRole> => dummyRolesMap,
|
||||
subscribeToRoles: (): (() => void) => (): void => undefined,
|
||||
});
|
||||
17
packages/ui-contexts/src/AvatarUrlContext.ts
Normal file
17
packages/ui-contexts/src/AvatarUrlContext.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
const dummy = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2Oora39DwAFaQJ3y3rKeAAAAABJRU5ErkJggg==';
|
||||
|
||||
export type AvatarUrlContextValue = {
|
||||
getUserPathAvatar: {
|
||||
(username: string, etag?: string | null): string;
|
||||
(params: { userId: string; etag?: string | null }): string;
|
||||
(params: { username: string; etag?: string | null }): string;
|
||||
};
|
||||
getRoomPathAvatar: (...args: any) => string;
|
||||
};
|
||||
|
||||
export const AvatarUrlContext = createContext<AvatarUrlContextValue>({
|
||||
getUserPathAvatar: () => dummy,
|
||||
getRoomPathAvatar: () => dummy,
|
||||
});
|
||||
68
packages/ui-contexts/src/CustomSoundContext.ts
Normal file
68
packages/ui-contexts/src/CustomSoundContext.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import type { ICustomSound } from '@rocket.chat/core-typings';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type CustomSoundContextValue = {
|
||||
play: (
|
||||
soundId: string,
|
||||
options?: {
|
||||
volume?: number | undefined;
|
||||
loop?: boolean | undefined;
|
||||
},
|
||||
) => void;
|
||||
pause: (sound: ICustomSound['_id']) => void;
|
||||
stop: (sound: ICustomSound['_id']) => void;
|
||||
callSounds: {
|
||||
playRinger: () => void;
|
||||
playDialer: () => void;
|
||||
stopRinger: () => void;
|
||||
stopDialer: () => void;
|
||||
};
|
||||
voipSounds: {
|
||||
playRinger: () => void;
|
||||
playDialer: () => void;
|
||||
playCallEnded: () => void;
|
||||
stopRinger: () => void;
|
||||
stopDialer: () => void;
|
||||
stopCallEnded: () => void;
|
||||
stopAll: () => void;
|
||||
};
|
||||
notificationSounds: {
|
||||
playNewRoom: () => void;
|
||||
playNewRoomLoop: () => void;
|
||||
playNewMessage: () => void;
|
||||
stopNewRoom: () => void;
|
||||
stopNewMessage: () => void;
|
||||
playNewMessageCustom: (soundId: ICustomSound['_id']) => void;
|
||||
};
|
||||
list: Omit<ICustomSound, '_updatedAt'>[];
|
||||
};
|
||||
|
||||
export const CustomSoundContext = createContext<CustomSoundContextValue>({
|
||||
play: () => undefined,
|
||||
pause: () => undefined,
|
||||
stop: () => undefined,
|
||||
callSounds: {
|
||||
playRinger: () => undefined,
|
||||
playDialer: () => undefined,
|
||||
stopRinger: () => undefined,
|
||||
stopDialer: () => undefined,
|
||||
},
|
||||
voipSounds: {
|
||||
playRinger: () => undefined,
|
||||
playDialer: () => undefined,
|
||||
playCallEnded: () => undefined,
|
||||
stopRinger: () => undefined,
|
||||
stopDialer: () => undefined,
|
||||
stopCallEnded: () => undefined,
|
||||
stopAll: () => undefined,
|
||||
},
|
||||
notificationSounds: {
|
||||
playNewRoom: () => undefined,
|
||||
playNewRoomLoop: () => undefined,
|
||||
playNewMessage: () => undefined,
|
||||
stopNewRoom: () => undefined,
|
||||
stopNewMessage: () => undefined,
|
||||
playNewMessageCustom: () => undefined,
|
||||
},
|
||||
list: [],
|
||||
});
|
||||
34
packages/ui-contexts/src/DeviceContext.ts
Normal file
34
packages/ui-contexts/src/DeviceContext.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type Device = {
|
||||
id: string;
|
||||
label: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type EnabledDeviceContextValue = {
|
||||
enabled: true;
|
||||
availableAudioOutputDevices: Device[];
|
||||
availableAudioInputDevices: Device[];
|
||||
// availableVideoInputDevices: Device[]
|
||||
selectedAudioOutputDevice?: Device;
|
||||
selectedAudioInputDevice?: Device;
|
||||
// selectedVideoInputDevice?: Device;
|
||||
setAudioOutputDevice: (data: { outputDevice: Device; HTMLAudioElement: HTMLAudioElement }) => void;
|
||||
setAudioInputDevice: (device: Device) => void;
|
||||
// setVideoInputDevice: (device: Device) => void;
|
||||
permissionStatus: PermissionStatus | undefined;
|
||||
};
|
||||
|
||||
type DisabledDeviceContextValue = {
|
||||
enabled: false;
|
||||
};
|
||||
|
||||
export type DeviceContextValue = EnabledDeviceContextValue | DisabledDeviceContextValue;
|
||||
|
||||
export const isDeviceContextEnabled = (context: DeviceContextValue): context is EnabledDeviceContextValue =>
|
||||
(context as EnabledDeviceContextValue).enabled;
|
||||
|
||||
export const DeviceContext = createContext<DeviceContextValue>({
|
||||
enabled: false,
|
||||
});
|
||||
83
packages/ui-contexts/src/LayoutContext.ts
Normal file
83
packages/ui-contexts/src/LayoutContext.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type SizeLayout = {
|
||||
sidebar: string;
|
||||
contextualBar: string;
|
||||
};
|
||||
|
||||
export type LayoutContextValue = {
|
||||
isEmbedded: boolean;
|
||||
showTopNavbarEmbeddedLayout: boolean;
|
||||
isTablet: boolean;
|
||||
isMobile: boolean;
|
||||
roomToolboxExpanded: boolean;
|
||||
sidebar: {
|
||||
overlayed: boolean;
|
||||
setOverlayed: (value: boolean) => void;
|
||||
isCollapsed: boolean;
|
||||
shouldToggle: boolean;
|
||||
toggle: () => void;
|
||||
collapse: () => void;
|
||||
expand: () => void;
|
||||
close: () => void;
|
||||
};
|
||||
sidePanel: {
|
||||
displaySidePanel: boolean;
|
||||
closeSidePanel: () => void;
|
||||
openSidePanel: () => void;
|
||||
};
|
||||
navbar: {
|
||||
searchExpanded: boolean;
|
||||
expandSearch?: () => void;
|
||||
collapseSearch?: () => void;
|
||||
};
|
||||
size: SizeLayout;
|
||||
contextualBarExpanded: boolean;
|
||||
contextualBarPosition: 'absolute' | 'relative' | 'fixed';
|
||||
hiddenActions: {
|
||||
roomToolbox: Array<string>;
|
||||
messageToolbox: Array<string>;
|
||||
composerToolbox: Array<string>;
|
||||
userToolbox: Array<string>;
|
||||
};
|
||||
};
|
||||
|
||||
export const LayoutContext = createContext<LayoutContextValue>({
|
||||
isEmbedded: false,
|
||||
showTopNavbarEmbeddedLayout: false,
|
||||
isTablet: false,
|
||||
isMobile: false,
|
||||
roomToolboxExpanded: true,
|
||||
navbar: {
|
||||
searchExpanded: false,
|
||||
expandSearch: () => undefined,
|
||||
collapseSearch: () => undefined,
|
||||
},
|
||||
sidebar: {
|
||||
overlayed: false,
|
||||
setOverlayed: () => undefined,
|
||||
isCollapsed: false,
|
||||
shouldToggle: false,
|
||||
toggle: () => undefined,
|
||||
collapse: () => undefined,
|
||||
expand: () => undefined,
|
||||
close: () => undefined,
|
||||
},
|
||||
sidePanel: {
|
||||
displaySidePanel: true,
|
||||
closeSidePanel: () => undefined,
|
||||
openSidePanel: () => undefined,
|
||||
},
|
||||
size: {
|
||||
sidebar: '240px',
|
||||
contextualBar: '380px',
|
||||
},
|
||||
contextualBarPosition: 'relative',
|
||||
contextualBarExpanded: false,
|
||||
hiddenActions: {
|
||||
roomToolbox: [],
|
||||
messageToolbox: [],
|
||||
composerToolbox: [],
|
||||
userToolbox: [],
|
||||
},
|
||||
});
|
||||
12
packages/ui-contexts/src/ModalContext.ts
Normal file
12
packages/ui-contexts/src/ModalContext.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type ModalContextValue = {
|
||||
modal: {
|
||||
setModal(modal?: ReactNode): void;
|
||||
};
|
||||
currentModal: { component: ReactNode; region?: symbol };
|
||||
region?: symbol;
|
||||
};
|
||||
|
||||
export const ModalContext = createContext<ModalContextValue | undefined>(undefined);
|
||||
46
packages/ui-contexts/src/RoomToolboxContext.ts
Normal file
46
packages/ui-contexts/src/RoomToolboxContext.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { Box } from '@rocket.chat/fuselage';
|
||||
import type { Keys as IconName } from '@rocket.chat/icons';
|
||||
import { createContext } from 'react';
|
||||
import type { ReactNode, ComponentProps, ComponentType } from 'react';
|
||||
|
||||
import type { TranslationKey } from './TranslationContext';
|
||||
|
||||
export type RenderToolboxItemParams = RoomToolboxActionConfig & {
|
||||
className?: ComponentProps<typeof Box>['className'];
|
||||
toolbox: RoomToolboxContextValue;
|
||||
};
|
||||
|
||||
export type RoomToolboxActionConfig = {
|
||||
id: string;
|
||||
icon: IconName;
|
||||
title: TranslationKey;
|
||||
anonymous?: boolean;
|
||||
tooltip?: string;
|
||||
disabled?: boolean;
|
||||
full?: true;
|
||||
order?: number;
|
||||
groups: Array<'group' | 'channel' | 'live' | 'direct' | 'direct_multiple' | 'team'>;
|
||||
hotkey?: string;
|
||||
action?: () => void;
|
||||
featured?: boolean;
|
||||
renderToolboxItem?: (params: RenderToolboxItemParams) => ReactNode;
|
||||
tabComponent?: ComponentType<{
|
||||
onClickBack?: () => void;
|
||||
}>;
|
||||
type?: 'organization' | 'communication' | 'customization' | 'apps' | 'moderation';
|
||||
variant?: 'danger';
|
||||
};
|
||||
|
||||
export type RoomToolboxContextValue = {
|
||||
actions: RoomToolboxActionConfig[];
|
||||
tab?: RoomToolboxActionConfig;
|
||||
context?: string;
|
||||
openTab: (actionId: string, context?: string) => void;
|
||||
closeTab: () => void;
|
||||
};
|
||||
|
||||
export const RoomToolboxContext = createContext<RoomToolboxContextValue>({
|
||||
actions: [],
|
||||
openTab: () => undefined,
|
||||
closeTab: () => undefined,
|
||||
});
|
||||
112
packages/ui-contexts/src/RouterContext.ts
Normal file
112
packages/ui-contexts/src/RouterContext.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { RoomType, RoomRouteData, DirectRoomRouteData, OmnichannelRoomRouteData, ChannelRouteData } from '@rocket.chat/core-typings';
|
||||
import type { ReactNode } from 'react';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export interface IRouterPaths {
|
||||
index: {
|
||||
pattern: '/';
|
||||
pathname: '/';
|
||||
};
|
||||
home: {
|
||||
pattern: '/home';
|
||||
pathname: '/home';
|
||||
};
|
||||
}
|
||||
|
||||
export type LocationPathname = IRouterPaths[keyof IRouterPaths]['pathname'];
|
||||
export type LocationSearch = string;
|
||||
export type LocationHash = string;
|
||||
|
||||
export type RouteParameters = Record<string, string>;
|
||||
export type SearchParameters = Record<string, string>;
|
||||
|
||||
export type RouteName = keyof IRouterPaths;
|
||||
export type RouterPathPattern = IRouterPaths[keyof IRouterPaths]['pattern'];
|
||||
|
||||
export type To =
|
||||
| LocationPathname
|
||||
| {
|
||||
name: RouteName;
|
||||
params?: RouteParameters;
|
||||
search?: SearchParameters;
|
||||
}
|
||||
| {
|
||||
pattern: RouterPathPattern;
|
||||
params?: RouteParameters;
|
||||
search?: SearchParameters;
|
||||
}
|
||||
| {
|
||||
pathname: LocationPathname;
|
||||
search?: SearchParameters;
|
||||
};
|
||||
|
||||
type RelativeRoutingType = 'route' | 'path';
|
||||
|
||||
export type RouteObject =
|
||||
| {
|
||||
path: RouterPathPattern;
|
||||
id: RouteName;
|
||||
element: ReactNode;
|
||||
}
|
||||
| {
|
||||
path: '*';
|
||||
id: 'not-found';
|
||||
element: ReactNode;
|
||||
};
|
||||
|
||||
export type RouterContextValue = {
|
||||
subscribeToRouteChange(onRouteChange: () => void): () => void;
|
||||
getLocationPathname(): LocationPathname;
|
||||
getLocationSearch(): LocationSearch;
|
||||
getLocationHash(): LocationHash;
|
||||
getRouteParameters(): RouteParameters;
|
||||
getSearchParameters(): SearchParameters;
|
||||
getRouteName(): RouteName | undefined;
|
||||
getPreviousRouteName(): RouteName | undefined;
|
||||
buildRoutePath(to: To): LocationPathname | `${LocationPathname}?${LocationSearch}`;
|
||||
navigate(to: To, options?: { replace?: boolean; state?: any; relative?: RelativeRoutingType }): void;
|
||||
navigate(delta: number): void;
|
||||
defineRoutes(routes: RouteObject[]): () => void;
|
||||
getRoomRoute(roomType: 'd', routeData: DirectRoomRouteData): { path: LocationPathname };
|
||||
getRoomRoute(roomType: 'l', routeData: OmnichannelRoomRouteData): { path: LocationPathname };
|
||||
getRoomRoute(roomType: 'p' | 'c', routeData: ChannelRouteData): { path: LocationPathname };
|
||||
getRoomRoute(
|
||||
roomType: RoomType,
|
||||
routeData: RoomRouteData,
|
||||
): {
|
||||
path: LocationPathname;
|
||||
};
|
||||
};
|
||||
|
||||
export const RouterContext = createContext<RouterContextValue>({
|
||||
subscribeToRouteChange: () => () => undefined,
|
||||
getLocationPathname: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
getRouteParameters: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
getLocationSearch: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
getLocationHash: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
getSearchParameters: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
getRouteName: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
getPreviousRouteName: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
buildRoutePath: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
navigate: () => undefined,
|
||||
defineRoutes: () => () => undefined,
|
||||
getRoomRoute: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
});
|
||||
84
packages/ui-contexts/src/ServerContext.ts
Normal file
84
packages/ui-contexts/src/ServerContext.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { IServerInfo, Serialized } from '@rocket.chat/core-typings';
|
||||
import type {
|
||||
ServerMethodName,
|
||||
ServerMethodParameters,
|
||||
ServerMethodReturn,
|
||||
StreamKeys,
|
||||
StreamNames,
|
||||
StreamerCallbackArgs,
|
||||
StreamerEvents,
|
||||
} from '@rocket.chat/ddp-client';
|
||||
import type { Method, OperationParams, OperationResult, PathFor, PathPattern, UrlParams } from '@rocket.chat/rest-typings';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type UploadResult = {
|
||||
success: boolean;
|
||||
status: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type ServerContextValue = {
|
||||
connected: boolean;
|
||||
status: 'connected' | 'connecting' | 'failed' | 'waiting' | 'offline';
|
||||
retryCount: number;
|
||||
retryTime?: number | undefined;
|
||||
info?: IServerInfo;
|
||||
absoluteUrl: (path: string) => string;
|
||||
callMethod?: <MethodName extends ServerMethodName>(
|
||||
methodName: MethodName,
|
||||
...args: ServerMethodParameters<MethodName>
|
||||
) => Promise<ServerMethodReturn<MethodName>>;
|
||||
callEndpoint: <TMethod extends Method, TPathPattern extends PathPattern>(args: {
|
||||
method: TMethod;
|
||||
pathPattern: TPathPattern;
|
||||
keys: UrlParams<TPathPattern>;
|
||||
params: OperationParams<TMethod, TPathPattern>;
|
||||
signal?: AbortSignal;
|
||||
}) => Promise<Serialized<OperationResult<TMethod, TPathPattern>>>;
|
||||
uploadToEndpoint: (
|
||||
endpoint: PathFor<'POST'>,
|
||||
formData: any,
|
||||
) =>
|
||||
| Promise<UploadResult>
|
||||
| {
|
||||
promise: Promise<UploadResult>;
|
||||
};
|
||||
getStream: <N extends StreamNames, K extends StreamKeys<N>>(
|
||||
streamName: N,
|
||||
_options?: {
|
||||
retransmit?: boolean | undefined;
|
||||
retransmitToSelf?: boolean | undefined;
|
||||
},
|
||||
) => (eventName: K, callback: (...args: StreamerCallbackArgs<N, K>) => void) => () => void;
|
||||
getStreamAll: <N extends StreamNames>(
|
||||
streamName: N,
|
||||
) => (callback: (eventName: string, args: StreamerEvents[N][number]['args']) => void) => () => void;
|
||||
writeStream: <N extends StreamNames, K extends StreamKeys<N>>(streamName: N, eventName: K, ...args: StreamerCallbackArgs<N, K>) => void;
|
||||
disconnect: () => void;
|
||||
reconnect: () => void;
|
||||
};
|
||||
|
||||
export const ServerContext = createContext<ServerContextValue>({
|
||||
connected: true,
|
||||
status: 'connected',
|
||||
retryCount: 0,
|
||||
info: undefined,
|
||||
absoluteUrl: (path) => path,
|
||||
callEndpoint: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
uploadToEndpoint: async () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
getStream: () => () => (): void => undefined,
|
||||
getStreamAll: () => () => (): void => undefined,
|
||||
writeStream: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
disconnect: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
reconnect: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
});
|
||||
11
packages/ui-contexts/src/SessionContext.ts
Normal file
11
packages/ui-contexts/src/SessionContext.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type SessionContextValue = {
|
||||
query: (name: string) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => unknown];
|
||||
dispatch: (name: string, value: unknown) => void;
|
||||
};
|
||||
|
||||
export const SessionContext = createContext<SessionContextValue>({
|
||||
query: () => [() => (): void => undefined, (): undefined => undefined],
|
||||
dispatch: (): void => undefined,
|
||||
});
|
||||
29
packages/ui-contexts/src/SettingsContext.ts
Normal file
29
packages/ui-contexts/src/SettingsContext.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { ISetting } from '@rocket.chat/core-typings';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type SettingsContextQuery = {
|
||||
readonly _id?: ISetting['_id'][] | RegExp;
|
||||
readonly group?: ISetting['_id'];
|
||||
readonly section?: string;
|
||||
readonly tab?: ISetting['_id'];
|
||||
readonly skip?: number;
|
||||
readonly limit?: number;
|
||||
};
|
||||
|
||||
export type SettingsContextValue = {
|
||||
readonly hasPrivateAccess: boolean;
|
||||
readonly querySetting: (
|
||||
_id: ISetting['_id'],
|
||||
) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting | undefined];
|
||||
readonly querySettings: (
|
||||
query: SettingsContextQuery,
|
||||
) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting[]];
|
||||
readonly dispatch: (changes: Partial<ISetting>[], onSaved?: () => void) => Promise<void>;
|
||||
};
|
||||
|
||||
export const SettingsContext = createContext<SettingsContextValue>({
|
||||
hasPrivateAccess: false,
|
||||
querySetting: () => [(): (() => void) => (): void => undefined, (): undefined => undefined],
|
||||
querySettings: () => [(): (() => void) => (): void => undefined, (): ISetting[] => []],
|
||||
dispatch: async () => undefined,
|
||||
});
|
||||
24
packages/ui-contexts/src/ToastMessagesContext.ts
Normal file
24
packages/ui-contexts/src/ToastMessagesContext.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { createContext } from 'react';
|
||||
|
||||
type ToastMessagePayload =
|
||||
| {
|
||||
type: 'success' | 'info' | 'warning';
|
||||
message: ReactNode | string;
|
||||
title?: string;
|
||||
options?: object;
|
||||
}
|
||||
| {
|
||||
type: 'error';
|
||||
message: unknown;
|
||||
title?: string;
|
||||
options?: object;
|
||||
};
|
||||
|
||||
export type ToastMessagesContextValue = {
|
||||
dispatch: (payload: ToastMessagePayload) => void;
|
||||
};
|
||||
|
||||
export const ToastMessagesContext = createContext<ToastMessagesContextValue>({
|
||||
dispatch: () => undefined,
|
||||
});
|
||||
12
packages/ui-contexts/src/TooltipContext.ts
Normal file
12
packages/ui-contexts/src/TooltipContext.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type TooltipContextValue = {
|
||||
open: (payload: ReactNode, anchor: HTMLElement) => void;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
export const TooltipContext = createContext<TooltipContextValue>({
|
||||
open: () => undefined,
|
||||
close: () => undefined,
|
||||
});
|
||||
51
packages/ui-contexts/src/TranslationContext.ts
Normal file
51
packages/ui-contexts/src/TranslationContext.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import type { RocketchatI18nKeys } from '@rocket.chat/i18n';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type TranslationLanguage = {
|
||||
en: string;
|
||||
name: string;
|
||||
ogName: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
type KeysWithoutSuffix = {
|
||||
[
|
||||
K in RocketchatI18nKeys as K extends `${infer T extends string}_${'one' | 'other' | 'zero' | 'few' | 'many' | 'two' | 'three' | 'four'}`
|
||||
? T
|
||||
: K
|
||||
]: never;
|
||||
};
|
||||
|
||||
export type TranslationKey = keyof KeysWithoutSuffix | `app-${string}.${string}`;
|
||||
|
||||
export type TranslationContextValue = {
|
||||
languages: TranslationLanguage[];
|
||||
language: TranslationLanguage['key'];
|
||||
loadLanguage: (language: TranslationLanguage['key']) => Promise<void>;
|
||||
translate: {
|
||||
(key: TranslationKey, options?: unknown): string;
|
||||
(key: TranslationKey, ...options: unknown[]): string;
|
||||
has: (
|
||||
key: string,
|
||||
options?: {
|
||||
lng?: string;
|
||||
},
|
||||
) => key is TranslationKey;
|
||||
};
|
||||
};
|
||||
|
||||
export const TranslationContext = createContext<TranslationContextValue>({
|
||||
languages: [
|
||||
{
|
||||
name: 'Default',
|
||||
en: 'Default',
|
||||
ogName: 'Default',
|
||||
key: '',
|
||||
},
|
||||
],
|
||||
language: '',
|
||||
loadLanguage: async () => console.warn('TranslationContext: loadLanguage not implemented'),
|
||||
translate: Object.assign((key: string) => key, {
|
||||
has: (key: string): key is TranslationKey => Boolean(key),
|
||||
}),
|
||||
});
|
||||
20
packages/ui-contexts/src/UserCardContext.ts
Normal file
20
packages/ui-contexts/src/UserCardContext.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { AriaButtonProps } from '@react-aria/button';
|
||||
import type { OverlayTriggerState } from '@react-stately/overlays';
|
||||
import type { MutableRefObject, UIEvent } from 'react';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type UserCardContextValue = {
|
||||
openUserCard: (e: UIEvent, username: string) => void;
|
||||
closeUserCard: () => void;
|
||||
triggerProps: AriaButtonProps<'button'>;
|
||||
triggerRef: MutableRefObject<Element | null>;
|
||||
state: OverlayTriggerState;
|
||||
};
|
||||
|
||||
export const UserCardContext = createContext<UserCardContextValue>({
|
||||
openUserCard: () => undefined,
|
||||
closeUserCard: () => undefined,
|
||||
triggerProps: {},
|
||||
triggerRef: { current: null },
|
||||
state: { isOpen: false, setOpen: () => undefined, open: () => undefined, close: () => undefined, toggle: () => undefined },
|
||||
});
|
||||
63
packages/ui-contexts/src/UserContext.ts
Normal file
63
packages/ui-contexts/src/UserContext.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings';
|
||||
import type { OffCallbackHandler } from '@rocket.chat/emitter';
|
||||
import type { ObjectId, Filter, FindOptions as MongoFindOptions, Document } from 'mongodb';
|
||||
import { createContext } from 'react';
|
||||
|
||||
import type { SubscriptionWithRoom } from './types/SubscriptionWithRoom';
|
||||
|
||||
export type SubscriptionQuery =
|
||||
| {
|
||||
rid: string | ObjectId;
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
open: boolean;
|
||||
}
|
||||
| object;
|
||||
|
||||
export type Fields<TSchema extends Document = Document> = Exclude<MongoFindOptions<TSchema>['projection'], undefined>;
|
||||
|
||||
export type Sort<TSchema extends Document = Document> = Exclude<MongoFindOptions<TSchema>['sort'], undefined>;
|
||||
|
||||
export type FindOptions<TSchema extends Document = Document> = {
|
||||
fields?: Fields<TSchema>;
|
||||
sort?: Sort<TSchema>;
|
||||
skip?: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export type UserContextValue = {
|
||||
userId: string | undefined;
|
||||
user: IUser | null;
|
||||
queryPreference: <T>(
|
||||
key: string | ObjectId,
|
||||
defaultValue?: T,
|
||||
) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => T | undefined];
|
||||
querySubscription: (
|
||||
query: Filter<Pick<ISubscription, 'rid' | 'name'>>,
|
||||
) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISubscription | undefined];
|
||||
queryRoom: (
|
||||
query: Filter<Pick<IRoom, '_id'>>,
|
||||
fields?: Fields,
|
||||
sort?: Sort,
|
||||
) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => IRoom | undefined];
|
||||
querySubscriptions: (
|
||||
query: SubscriptionQuery,
|
||||
options?: FindOptions,
|
||||
) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => SubscriptionWithRoom[]];
|
||||
logout: () => Promise<void>;
|
||||
onLogout: (callback: () => void) => OffCallbackHandler;
|
||||
};
|
||||
|
||||
export const UserContext = createContext<UserContextValue>({
|
||||
userId: undefined,
|
||||
user: null,
|
||||
queryPreference: () => [() => (): void => undefined, (): undefined => undefined],
|
||||
querySubscription: () => [() => (): void => undefined, (): undefined => undefined],
|
||||
queryRoom: () => [() => (): void => undefined, (): undefined => undefined],
|
||||
querySubscriptions: () => [() => (): void => undefined, (): [] => []],
|
||||
logout: () => Promise.resolve(),
|
||||
onLogout: () => (): void => undefined,
|
||||
});
|
||||
13
packages/ui-contexts/src/UserPresenceContext.ts
Normal file
13
packages/ui-contexts/src/UserPresenceContext.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { UserPresence } from '@rocket.chat/core-typings';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type UserPresenceContextValue = {
|
||||
queryUserData: (uid: string | undefined) => {
|
||||
get(): UserPresence | undefined;
|
||||
subscribe(callback: () => void): () => void;
|
||||
};
|
||||
};
|
||||
|
||||
export const UserPresenceContext = createContext<UserPresenceContextValue>({
|
||||
queryUserData: () => ({ get: () => undefined, subscribe: () => () => undefined }),
|
||||
});
|
||||
5
packages/ui-contexts/src/hooks/useAbsoluteUrl.ts
Normal file
5
packages/ui-contexts/src/hooks/useAbsoluteUrl.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { ServerContext } from '../ServerContext';
|
||||
|
||||
export const useAbsoluteUrl = (): ((path: string) => string) => useContext(ServerContext).absoluteUrl;
|
||||
24
packages/ui-contexts/src/hooks/useAccountsCustomFields.ts
Normal file
24
packages/ui-contexts/src/hooks/useAccountsCustomFields.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { CustomFieldMetadata } from '@rocket.chat/core-typings';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useSetting } from './useSetting';
|
||||
|
||||
export const useAccountsCustomFields = () => {
|
||||
const accountsCustomFieldsJSON = useSetting('Accounts_CustomFields');
|
||||
|
||||
return useMemo((): CustomFieldMetadata[] => {
|
||||
if (typeof accountsCustomFieldsJSON !== 'string' || accountsCustomFieldsJSON.trim() === '') {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
return Object.entries<Omit<CustomFieldMetadata, 'name'>>(JSON.parse(accountsCustomFieldsJSON)).map(
|
||||
([fieldName, fieldData]): CustomFieldMetadata => {
|
||||
return { ...fieldData, name: fieldName };
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
console.error('Invalid JSON for Accounts_CustomFields');
|
||||
}
|
||||
return [];
|
||||
}, [accountsCustomFieldsJSON]);
|
||||
};
|
||||
10
packages/ui-contexts/src/hooks/useAllPermissions.ts
Normal file
10
packages/ui-contexts/src/hooks/useAllPermissions.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { ObjectId } from 'mongodb';
|
||||
import { useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
|
||||
import { AuthorizationContext } from '../AuthorizationContext';
|
||||
|
||||
export const useAllPermissions = (permissions: (string | ObjectId)[], scope?: string | ObjectId): boolean => {
|
||||
const { queryAllPermissions } = useContext(AuthorizationContext);
|
||||
const [subscribe, getSnapshot] = useMemo(() => queryAllPermissions(permissions, scope), [queryAllPermissions, permissions, scope]);
|
||||
return useSyncExternalStore(subscribe, getSnapshot);
|
||||
};
|
||||
11
packages/ui-contexts/src/hooks/useAssetPath.ts
Normal file
11
packages/ui-contexts/src/hooks/useAssetPath.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { IRocketChatAssets, ISettingAsset } from '@rocket.chat/core-typings';
|
||||
|
||||
import { useAbsoluteUrl } from './useAbsoluteUrl';
|
||||
import { useSetting } from './useSetting';
|
||||
|
||||
export const useAssetPath = (assetId: keyof IRocketChatAssets): string | undefined => {
|
||||
const asset = useSetting<ISettingAsset>(`Assets_${assetId}`);
|
||||
const absoluteUrl = useAbsoluteUrl();
|
||||
|
||||
return (asset?.url ?? asset?.defaultUrl) && absoluteUrl(asset?.url ?? asset?.defaultUrl ?? '');
|
||||
};
|
||||
15
packages/ui-contexts/src/hooks/useAssetWithDarkModePath.ts
Normal file
15
packages/ui-contexts/src/hooks/useAssetWithDarkModePath.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { IRocketChatAssets } from '@rocket.chat/core-typings';
|
||||
import { useDarkMode } from '@rocket.chat/fuselage-hooks';
|
||||
|
||||
import { useAssetPath } from './useAssetPath';
|
||||
import { useUserPreference } from './useUserPreference';
|
||||
|
||||
export const useAssetWithDarkModePath = <A extends keyof IRocketChatAssets>(
|
||||
assetId: A extends `${infer T}_dark` ? T : never,
|
||||
): string | undefined => {
|
||||
const userThemePreference = useUserPreference('themeAppearence') || 'auto';
|
||||
const theme = useDarkMode(userThemePreference === 'auto' ? undefined : userThemePreference === 'dark') ? 'dark' : 'light';
|
||||
const themeAssetId = theme === 'dark' ? (`${assetId}_dark` as const) : assetId;
|
||||
|
||||
return useAssetPath(themeAssetId);
|
||||
};
|
||||
13
packages/ui-contexts/src/hooks/useAtLeastOnePermission.ts
Normal file
13
packages/ui-contexts/src/hooks/useAtLeastOnePermission.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { ObjectId } from 'mongodb';
|
||||
import { useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
|
||||
import { AuthorizationContext } from '../AuthorizationContext';
|
||||
|
||||
export const useAtLeastOnePermission = (permissions: (string | ObjectId)[], scope?: string | ObjectId): boolean => {
|
||||
const { queryAtLeastOnePermission } = useContext(AuthorizationContext);
|
||||
const [subscribe, getSnapshot] = useMemo(
|
||||
() => queryAtLeastOnePermission(permissions, scope),
|
||||
[queryAtLeastOnePermission, permissions, scope],
|
||||
);
|
||||
return useSyncExternalStore(subscribe, getSnapshot);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AttachmentContext } from '../AttachmentContext';
|
||||
|
||||
export const useAttachmentAutoLoadEmbedMedia = (): boolean => useContext(AttachmentContext).autoLoadEmbedMedias;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AttachmentContext } from '../AttachmentContext';
|
||||
|
||||
export const useAttachmentDimensions = (): {
|
||||
width: number;
|
||||
height: number;
|
||||
} => useContext(AttachmentContext).dimensions;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AttachmentContext } from '../AttachmentContext';
|
||||
|
||||
export const useAttachmentIsCollapsedByDefault = (): boolean => useContext(AttachmentContext).collapsedByDefault;
|
||||
25
packages/ui-contexts/src/hooks/useAvailableDevices.ts
Normal file
25
packages/ui-contexts/src/hooks/useAvailableDevices.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { Device } from '../DeviceContext';
|
||||
import { DeviceContext, isDeviceContextEnabled } from '../DeviceContext';
|
||||
|
||||
type AvailableDevices = {
|
||||
audioInput?: Device[];
|
||||
audioOutput?: Device[];
|
||||
};
|
||||
|
||||
export const useAvailableDevices = (): AvailableDevices | null => {
|
||||
const context = useContext(DeviceContext);
|
||||
|
||||
if (!isDeviceContextEnabled(context)) {
|
||||
console.warn(
|
||||
'Device Management is disabled on unsecure contexts, see https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
audioInput: context.availableAudioInputDevices,
|
||||
audioOutput: context.availableAudioOutputDevices,
|
||||
};
|
||||
};
|
||||
8
packages/ui-contexts/src/hooks/useConnectionStatus.ts
Normal file
8
packages/ui-contexts/src/hooks/useConnectionStatus.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { ServerContext } from '../ServerContext';
|
||||
|
||||
export const useConnectionStatus = () => {
|
||||
const { connected, retryTime, status, reconnect, disconnect } = useContext(ServerContext);
|
||||
return { connected, retryTime, status, reconnect, disconnect };
|
||||
};
|
||||
21
packages/ui-contexts/src/hooks/useCurrentModal.ts
Normal file
21
packages/ui-contexts/src/hooks/useCurrentModal.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { ModalContextValue } from '../ModalContext';
|
||||
import { ModalContext } from '../ModalContext';
|
||||
|
||||
/**
|
||||
* Similar to useModal this hook return the current modal from the context value
|
||||
*/
|
||||
export const useCurrentModal = (): ModalContextValue['currentModal']['component'] => {
|
||||
const context = useContext(ModalContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useCurrentModal must be used inside Modal Context');
|
||||
}
|
||||
|
||||
if (context.currentModal?.region !== context.region) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return context.currentModal.component;
|
||||
};
|
||||
20
packages/ui-contexts/src/hooks/useCurrentRoutePath.ts
Normal file
20
packages/ui-contexts/src/hooks/useCurrentRoutePath.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useCallback, useContext, useSyncExternalStore } from 'react';
|
||||
|
||||
import { RouterContext } from '../RouterContext';
|
||||
|
||||
export const useCurrentRoutePath = () => {
|
||||
const router = useContext(RouterContext);
|
||||
|
||||
const getSnapshot = useCallback(() => {
|
||||
const name = router.getRouteName();
|
||||
return name
|
||||
? router.buildRoutePath({
|
||||
name,
|
||||
params: router.getRouteParameters(),
|
||||
search: router.getSearchParameters(),
|
||||
})
|
||||
: undefined;
|
||||
}, [router]);
|
||||
|
||||
return useSyncExternalStore(router.subscribeToRouteChange, getSnapshot);
|
||||
};
|
||||
6
packages/ui-contexts/src/hooks/useCustomSound.ts
Normal file
6
packages/ui-contexts/src/hooks/useCustomSound.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { CustomSoundContextValue } from '../CustomSoundContext';
|
||||
import { CustomSoundContext } from '../CustomSoundContext';
|
||||
|
||||
export const useCustomSound = (): CustomSoundContextValue => useContext(CustomSoundContext);
|
||||
17
packages/ui-contexts/src/hooks/useDeviceConstraints.ts
Normal file
17
packages/ui-contexts/src/hooks/useDeviceConstraints.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { DeviceContext, isDeviceContextEnabled } from '../DeviceContext';
|
||||
|
||||
export const useDeviceConstraints = (): MediaStreamConstraints | null => {
|
||||
const context = useContext(DeviceContext);
|
||||
|
||||
if (!isDeviceContextEnabled(context)) {
|
||||
console.warn(
|
||||
'Device Management is disabled on unsecure contexts, see https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedAudioInputDeviceId = context.selectedAudioInputDevice?.id;
|
||||
return { audio: selectedAudioInputDeviceId === 'default' ? true : { deviceId: { exact: selectedAudioInputDeviceId } } };
|
||||
};
|
||||
42
packages/ui-contexts/src/hooks/useEndpoint.ts
Normal file
42
packages/ui-contexts/src/hooks/useEndpoint.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { Serialized } from '@rocket.chat/core-typings';
|
||||
import type { Method, OperationParams, OperationResult, PathPattern, UrlParams } from '@rocket.chat/rest-typings';
|
||||
import { useCallback, useContext, useRef } from 'react';
|
||||
|
||||
import { ServerContext } from '../ServerContext';
|
||||
|
||||
type EndpointOptions = {
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
export type EndpointFunction<TMethod extends Method, TPathPattern extends PathPattern> =
|
||||
undefined extends OperationParams<TMethod, TPathPattern>
|
||||
? (
|
||||
params?: OperationParams<TMethod, TPathPattern>,
|
||||
options?: EndpointOptions,
|
||||
) => Promise<Serialized<OperationResult<TMethod, TPathPattern>>>
|
||||
: (
|
||||
params: OperationParams<TMethod, TPathPattern>,
|
||||
options?: EndpointOptions,
|
||||
) => Promise<Serialized<OperationResult<TMethod, TPathPattern>>>;
|
||||
|
||||
export function useEndpoint<TMethod extends Method, TPathPattern extends PathPattern>(
|
||||
method: TMethod,
|
||||
pathPattern: TPathPattern,
|
||||
...[keys]: NoInfer<undefined extends UrlParams<TPathPattern> ? [keys?: UrlParams<TPathPattern>] : [keys: UrlParams<TPathPattern>]>
|
||||
): EndpointFunction<TMethod, TPathPattern> {
|
||||
const { callEndpoint } = useContext(ServerContext);
|
||||
const keysRef = useRef(keys);
|
||||
keysRef.current = keys;
|
||||
|
||||
return useCallback(
|
||||
(params: OperationParams<TMethod, TPathPattern> | undefined, options?: EndpointOptions) =>
|
||||
callEndpoint({
|
||||
method,
|
||||
pathPattern,
|
||||
keys: keysRef.current as UrlParams<TPathPattern>,
|
||||
params: params as OperationParams<TMethod, TPathPattern>,
|
||||
signal: options?.signal,
|
||||
}),
|
||||
[callEndpoint, pathPattern, method],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { DeviceContext } from '../DeviceContext';
|
||||
|
||||
export const useIsDeviceManagementEnabled = (): boolean => useContext(DeviceContext).enabled;
|
||||
8
packages/ui-contexts/src/hooks/useIsLoggingIn.ts
Normal file
8
packages/ui-contexts/src/hooks/useIsLoggingIn.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useIsLoggingIn = () => {
|
||||
const { isLoggingIn } = useContext(AuthenticationContext);
|
||||
return isLoggingIn;
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { SettingsContext } from '../SettingsContext';
|
||||
|
||||
export const useIsPrivilegedSettingsContext = (): boolean => useContext(SettingsContext).hasPrivateAccess;
|
||||
6
packages/ui-contexts/src/hooks/useLanguage.ts
Normal file
6
packages/ui-contexts/src/hooks/useLanguage.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { TranslationContextValue } from '../TranslationContext';
|
||||
import { TranslationContext } from '../TranslationContext';
|
||||
|
||||
export const useLanguage = (): TranslationContextValue['language'] => useContext(TranslationContext).language;
|
||||
6
packages/ui-contexts/src/hooks/useLanguages.ts
Normal file
6
packages/ui-contexts/src/hooks/useLanguages.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { TranslationContextValue } from '../TranslationContext';
|
||||
import { TranslationContext } from '../TranslationContext';
|
||||
|
||||
export const useLanguages = (): TranslationContextValue['languages'] => useContext(TranslationContext).languages;
|
||||
6
packages/ui-contexts/src/hooks/useLayout.ts
Normal file
6
packages/ui-contexts/src/hooks/useLayout.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { LayoutContextValue } from '../LayoutContext';
|
||||
import { LayoutContext } from '../LayoutContext';
|
||||
|
||||
export const useLayout = (): LayoutContextValue => useContext(LayoutContext);
|
||||
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { LayoutContext } from '../LayoutContext';
|
||||
|
||||
export const useLayoutContextualBarExpanded = (): boolean => useContext(LayoutContext).contextualBarExpanded;
|
||||
@@ -0,0 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { LayoutContextValue } from '../LayoutContext';
|
||||
import { LayoutContext } from '../LayoutContext';
|
||||
|
||||
export const useLayoutContextualBarPosition = (): LayoutContextValue['contextualBarPosition'] =>
|
||||
useContext(LayoutContext).contextualBarPosition;
|
||||
6
packages/ui-contexts/src/hooks/useLayoutHiddenActions.ts
Normal file
6
packages/ui-contexts/src/hooks/useLayoutHiddenActions.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { LayoutContextValue } from '../LayoutContext';
|
||||
import { LayoutContext } from '../LayoutContext';
|
||||
|
||||
export const useLayoutHiddenActions = (): LayoutContextValue['hiddenActions'] => useContext(LayoutContext).hiddenActions;
|
||||
6
packages/ui-contexts/src/hooks/useLayoutSizes.ts
Normal file
6
packages/ui-contexts/src/hooks/useLayoutSizes.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { LayoutContextValue } from '../LayoutContext';
|
||||
import { LayoutContext } from '../LayoutContext';
|
||||
|
||||
export const useLayoutSizes = (): LayoutContextValue['size'] => useContext(LayoutContext).size;
|
||||
6
packages/ui-contexts/src/hooks/useLoadLanguage.ts
Normal file
6
packages/ui-contexts/src/hooks/useLoadLanguage.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { TranslationContextValue } from '../TranslationContext';
|
||||
import { TranslationContext } from '../TranslationContext';
|
||||
|
||||
export const useLoadLanguage = (): TranslationContextValue['loadLanguage'] => useContext(TranslationContext).loadLanguage;
|
||||
8
packages/ui-contexts/src/hooks/useLocationHash.ts
Normal file
8
packages/ui-contexts/src/hooks/useLocationHash.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useContext, useSyncExternalStore } from 'react';
|
||||
|
||||
import { RouterContext } from '../RouterContext';
|
||||
|
||||
export const useLocationHash = () => {
|
||||
const { getLocationHash, subscribeToRouteChange } = useContext(RouterContext);
|
||||
return useSyncExternalStore(subscribeToRouteChange, getLocationHash);
|
||||
};
|
||||
9
packages/ui-contexts/src/hooks/useLoginServices.ts
Normal file
9
packages/ui-contexts/src/hooks/useLoginServices.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { useContext, useSyncExternalStore } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useLoginServices = () => {
|
||||
const { queryLoginServices } = useContext(AuthenticationContext);
|
||||
|
||||
return useSyncExternalStore(queryLoginServices.subscribe, queryLoginServices.getCurrentValue);
|
||||
};
|
||||
5
packages/ui-contexts/src/hooks/useLoginToken.ts
Normal file
5
packages/ui-contexts/src/hooks/useLoginToken.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useLoginToken = (): string | null => useContext(AuthenticationContext).getLoginToken();
|
||||
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useLoginWithCustomOauth = () => useContext(AuthenticationContext).loginWithCustomOauth;
|
||||
6
packages/ui-contexts/src/hooks/useLoginWithIframe.ts
Normal file
6
packages/ui-contexts/src/hooks/useLoginWithIframe.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useLoginWithIframe = (): ((token: string, callback?: (error: Error | null | undefined) => void) => Promise<void>) =>
|
||||
useContext(AuthenticationContext).loginWithIframe;
|
||||
5
packages/ui-contexts/src/hooks/useLoginWithPassword.ts
Normal file
5
packages/ui-contexts/src/hooks/useLoginWithPassword.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useLoginWithPassword = () => useContext(AuthenticationContext).loginWithPassword;
|
||||
12
packages/ui-contexts/src/hooks/useLoginWithService.ts
Normal file
12
packages/ui-contexts/src/hooks/useLoginWithService.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { LoginServiceConfiguration } from '@rocket.chat/core-typings';
|
||||
import { useContext, useMemo } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useLoginWithService = <T extends LoginServiceConfiguration>(service: T): (() => Promise<true>) => {
|
||||
const { loginWithService } = useContext(AuthenticationContext);
|
||||
|
||||
return useMemo(() => {
|
||||
return loginWithService(service);
|
||||
}, [loginWithService, service]);
|
||||
};
|
||||
5
packages/ui-contexts/src/hooks/useLoginWithToken.ts
Normal file
5
packages/ui-contexts/src/hooks/useLoginWithToken.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useLoginWithToken = () => useContext(AuthenticationContext).loginWithToken;
|
||||
6
packages/ui-contexts/src/hooks/useLoginWithTokenRoute.ts
Normal file
6
packages/ui-contexts/src/hooks/useLoginWithTokenRoute.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useLoginWithTokenRoute = (): ((token: string, callback?: (error: Error | null | undefined) => void) => Promise<void>) =>
|
||||
useContext(AuthenticationContext).loginWithTokenRoute;
|
||||
15
packages/ui-contexts/src/hooks/useLogout.ts
Normal file
15
packages/ui-contexts/src/hooks/useLogout.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { UserContext } from '../UserContext';
|
||||
import { useRouter } from './useRouter';
|
||||
|
||||
export const useLogout = (): (() => void) => {
|
||||
const router = useRouter();
|
||||
const { logout } = useContext(UserContext);
|
||||
|
||||
return useStableCallback(() => {
|
||||
void logout();
|
||||
router.navigate('/');
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
import { useMediaDeviceMicrophonePermission } from './useMediaDevicePermission';
|
||||
import { DeviceContext } from '../DeviceContext';
|
||||
|
||||
const states = [
|
||||
{ expectedState: 'granted', state: 'granted', requestDevice: 'function' },
|
||||
{ expectedState: 'denied', state: 'denied', requestDevice: 'undefined' },
|
||||
{ expectedState: 'prompt', state: 'prompt', requestDevice: 'function' },
|
||||
];
|
||||
|
||||
const getWrapper =
|
||||
(state: PermissionState | undefined, availableAudioInputDevices: any[] = [], enabled = true) =>
|
||||
({ children }: { children: any }) => {
|
||||
return (
|
||||
<DeviceContext.Provider
|
||||
value={{
|
||||
enabled,
|
||||
selectedAudioOutputDevice: undefined,
|
||||
selectedAudioInputDevice: undefined,
|
||||
availableAudioOutputDevices: [],
|
||||
availableAudioInputDevices,
|
||||
permissionStatus: state ? ({ state } as PermissionStatus) : undefined,
|
||||
setAudioOutputDevice: () => undefined,
|
||||
setAudioInputDevice: () => undefined,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DeviceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('useMediaDeviceMicrophonePermission', () => {
|
||||
it('Should return permission state denied and requestDevice is undefined if context is disabled', async () => {
|
||||
const { result } = renderHook(() => useMediaDeviceMicrophonePermission(), {
|
||||
wrapper: getWrapper(undefined, ['device1', 'device2'], false),
|
||||
});
|
||||
|
||||
expect(result.current.state).toBe('denied');
|
||||
expect(result.current.requestDevice).toBeUndefined();
|
||||
});
|
||||
it.each(states)('Should return permission state $state and requestDevice is $requestDevice', async ({ state, requestDevice }) => {
|
||||
const { result } = renderHook(() => useMediaDeviceMicrophonePermission(), {
|
||||
wrapper: getWrapper(state as PermissionState),
|
||||
});
|
||||
|
||||
expect(result.current.state).toBe(state);
|
||||
expect(typeof result.current.requestDevice).toBe(requestDevice);
|
||||
});
|
||||
|
||||
it('Should return permission state granted and requestDevice is function if permissionStatus is undefined and availableAudioInputDevices has records', async () => {
|
||||
const { result } = renderHook(() => useMediaDeviceMicrophonePermission(), {
|
||||
wrapper: getWrapper(undefined, ['device1', 'device2']),
|
||||
});
|
||||
|
||||
expect(result.current.state).toBe('granted');
|
||||
expect(typeof result.current.requestDevice).toBe('function');
|
||||
});
|
||||
|
||||
it('Should return permission state prompt and requestDevice is function if permissionStatus is undefined and availableAudioInputDevices is empty', async () => {
|
||||
const { result } = renderHook(() => useMediaDeviceMicrophonePermission(), {
|
||||
wrapper: getWrapper(undefined),
|
||||
});
|
||||
|
||||
expect(result.current.state).toBe('prompt');
|
||||
expect(typeof result.current.requestDevice).toBe('function');
|
||||
});
|
||||
});
|
||||
61
packages/ui-contexts/src/hooks/useMediaDevicePermission.ts
Normal file
61
packages/ui-contexts/src/hooks/useMediaDevicePermission.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { DeviceContext, isDeviceContextEnabled } from '../DeviceContext';
|
||||
|
||||
const requestDevice = async ({
|
||||
onAccept,
|
||||
onReject,
|
||||
constraints = { audio: true },
|
||||
}: {
|
||||
onAccept?: (stream: MediaStream) => void;
|
||||
onReject?: (error: DOMException) => void;
|
||||
constraints?: MediaStreamConstraints;
|
||||
}): Promise<void> => {
|
||||
if (!navigator.mediaDevices) {
|
||||
return;
|
||||
}
|
||||
|
||||
await navigator.mediaDevices.getUserMedia(constraints).then(onAccept, onReject);
|
||||
};
|
||||
|
||||
const isPermissionDenied = (state: PermissionState): state is 'denied' => {
|
||||
return state === 'denied';
|
||||
};
|
||||
|
||||
type DeniedReturn = { state: 'denied'; requestDevice?: never };
|
||||
type PromptOrGrantedReturn = { state: 'prompt' | 'granted'; requestDevice: typeof requestDevice };
|
||||
|
||||
/**
|
||||
* @description Hook to check if the microphone permission is granted. If the permission is denied, or the permission is not requested, the hook will return a function to request the permission. Right now just the microphone permission is handled with this hook, since DeviceContext is only used for audio input and output.
|
||||
* @returns { state: 'granted' } if the permission is granted
|
||||
* @returns { state: 'denied' } if the permission is denied
|
||||
* @returns { state: 'prompt', requestPrompt: function ({onAccept, onReject}) {} } if the permission is in prompt state.
|
||||
*/
|
||||
export const useMediaDeviceMicrophonePermission = (): DeniedReturn | PromptOrGrantedReturn => {
|
||||
const context = useContext(DeviceContext);
|
||||
|
||||
if (!isDeviceContextEnabled(context)) {
|
||||
return {
|
||||
state: 'denied',
|
||||
};
|
||||
}
|
||||
|
||||
const { permissionStatus, availableAudioInputDevices } = context;
|
||||
|
||||
if (permissionStatus) {
|
||||
if (isPermissionDenied(permissionStatus.state)) {
|
||||
return { state: permissionStatus.state };
|
||||
}
|
||||
|
||||
return { state: permissionStatus.state, requestDevice };
|
||||
}
|
||||
|
||||
if (availableAudioInputDevices.length > 0) {
|
||||
return { state: 'granted', requestDevice };
|
||||
}
|
||||
|
||||
return {
|
||||
state: 'prompt',
|
||||
requestDevice,
|
||||
};
|
||||
};
|
||||
8
packages/ui-contexts/src/hooks/useMediaUrl.ts
Normal file
8
packages/ui-contexts/src/hooks/useMediaUrl.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AttachmentContext } from '../AttachmentContext';
|
||||
|
||||
export const useMediaUrl = (): ((path: string) => string) => {
|
||||
const { getURL } = useContext(AttachmentContext);
|
||||
return getURL;
|
||||
};
|
||||
24
packages/ui-contexts/src/hooks/useMethod.ts
Normal file
24
packages/ui-contexts/src/hooks/useMethod.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { ServerMethodName, ServerMethodParameters, ServerMethodReturn, ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { useCallback, useContext } from 'react';
|
||||
|
||||
import { ServerContext } from '../ServerContext';
|
||||
|
||||
type ServerMethodFunction<MethodName extends ServerMethodName> = (
|
||||
...args: ServerMethodParameters<MethodName>
|
||||
) => Promise<ServerMethodReturn<MethodName>>;
|
||||
|
||||
/** @deprecated prefer the use of api endpoints (useEndpoint) */
|
||||
export const useMethod = <MethodName extends keyof ServerMethods>(methodName: MethodName): ServerMethodFunction<MethodName> => {
|
||||
const { callMethod } = useContext(ServerContext);
|
||||
|
||||
return useCallback(
|
||||
(...args: ServerMethodParameters<MethodName>) => {
|
||||
if (!callMethod) {
|
||||
throw new Error(`cannot use useMethod(${methodName}) hook without a wrapping ServerContext`);
|
||||
}
|
||||
|
||||
return callMethod(methodName, ...args);
|
||||
},
|
||||
[callMethod, methodName],
|
||||
);
|
||||
};
|
||||
17
packages/ui-contexts/src/hooks/useModal.ts
Normal file
17
packages/ui-contexts/src/hooks/useModal.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { ModalContextValue } from '../ModalContext';
|
||||
import { ModalContext } from '../ModalContext';
|
||||
|
||||
/**
|
||||
* Consider using useCurrentModal to get the current modal
|
||||
*/
|
||||
export const useModal = (): ModalContextValue['modal'] => {
|
||||
const context = useContext(ModalContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useModal must be used inside Modal Context');
|
||||
}
|
||||
|
||||
return context.modal;
|
||||
};
|
||||
6
packages/ui-contexts/src/hooks/useOnLogout.ts
Normal file
6
packages/ui-contexts/src/hooks/useOnLogout.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { OffCallbackHandler } from '@rocket.chat/emitter';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { UserContext } from '../UserContext';
|
||||
|
||||
export const useOnLogout = (): ((callback: () => void) => OffCallbackHandler) => useContext(UserContext).onLogout;
|
||||
28
packages/ui-contexts/src/hooks/usePasswordPolicy.ts
Normal file
28
packages/ui-contexts/src/hooks/usePasswordPolicy.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { PasswordPolicy, type PasswordPolicyOptions, type PasswordPolicyValidation } from '@rocket.chat/password-policies';
|
||||
import { useMemo, useCallback } from 'react';
|
||||
|
||||
export type { PasswordPolicyValidation };
|
||||
|
||||
export type UsePasswordPolicyResult = {
|
||||
validations: PasswordPolicyValidation[];
|
||||
valid: boolean;
|
||||
};
|
||||
|
||||
export type UsePasswordPolicyReturn = (password: string) => UsePasswordPolicyResult;
|
||||
|
||||
export type UsePasswordPolicy = (options: PasswordPolicyOptions) => UsePasswordPolicyReturn;
|
||||
|
||||
export const usePasswordPolicy: UsePasswordPolicy = (options) => {
|
||||
const policy = useMemo(() => new PasswordPolicy(options), [options]);
|
||||
|
||||
return useCallback(
|
||||
(password: string) => {
|
||||
const validations = policy.sendValidationMessage(password);
|
||||
return {
|
||||
validations,
|
||||
valid: validations.every(({ isValid }) => isValid),
|
||||
};
|
||||
},
|
||||
[policy],
|
||||
);
|
||||
};
|
||||
28
packages/ui-contexts/src/hooks/usePasswordPolicyOptions.ts
Normal file
28
packages/ui-contexts/src/hooks/usePasswordPolicyOptions.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { PasswordPolicyOptions } from '@rocket.chat/password-policies';
|
||||
|
||||
import { useSetting } from './useSetting';
|
||||
|
||||
export const usePasswordPolicyOptions = (): PasswordPolicyOptions => {
|
||||
const enabled = useSetting('Accounts_Password_Policy_Enabled', true);
|
||||
const minLength = useSetting('Accounts_Password_Policy_MinLength', 14);
|
||||
const maxLength = useSetting('Accounts_Password_Policy_MaxLength', -1);
|
||||
const forbidRepeatingCharacters = useSetting('Accounts_Password_Policy_ForbidRepeatingCharacters', true);
|
||||
const forbidRepeatingCharactersCount = useSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount', 3);
|
||||
const mustContainAtLeastOneLowercase = useSetting('Accounts_Password_Policy_AtLeastOneLowercase', true);
|
||||
const mustContainAtLeastOneUppercase = useSetting('Accounts_Password_Policy_AtLeastOneUppercase', true);
|
||||
const mustContainAtLeastOneNumber = useSetting('Accounts_Password_Policy_AtLeastOneNumber', true);
|
||||
const mustContainAtLeastOneSpecialCharacter = useSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter', true);
|
||||
|
||||
return {
|
||||
enabled,
|
||||
minLength,
|
||||
maxLength,
|
||||
forbidRepeatingCharacters,
|
||||
forbidRepeatingCharactersCount,
|
||||
mustContainAtLeastOneLowercase,
|
||||
mustContainAtLeastOneUppercase,
|
||||
mustContainAtLeastOneNumber,
|
||||
mustContainAtLeastOneSpecialCharacter,
|
||||
throwError: false,
|
||||
};
|
||||
};
|
||||
10
packages/ui-contexts/src/hooks/usePermission.ts
Normal file
10
packages/ui-contexts/src/hooks/usePermission.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { ObjectId } from 'mongodb';
|
||||
import { useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
|
||||
import { AuthorizationContext } from '../AuthorizationContext';
|
||||
|
||||
export const usePermission = (permission: string | ObjectId, scope?: string | ObjectId): boolean => {
|
||||
const { queryPermission } = useContext(AuthorizationContext);
|
||||
const [subscribe, getSnapshot] = useMemo(() => queryPermission(permission, scope), [queryPermission, permission, scope]);
|
||||
return useSyncExternalStore(subscribe, getSnapshot);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { IRole } from '@rocket.chat/core-typings';
|
||||
import { useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
|
||||
import { AuthorizationContext } from '../AuthorizationContext';
|
||||
|
||||
/**
|
||||
* Check if the user has the permission considering the user have the scoped roles
|
||||
* @param permission The permission to check
|
||||
* @param scopedRoles The roles to be also considered
|
||||
* @returns boolean
|
||||
*/
|
||||
|
||||
export const usePermissionWithScopedRoles = (permission: string, scopedRoles: IRole['_id'][]): boolean => {
|
||||
const { queryPermission } = useContext(AuthorizationContext);
|
||||
const [subscribe, getSnapshot] = useMemo(
|
||||
() => queryPermission(permission, undefined, scopedRoles),
|
||||
[queryPermission, permission, scopedRoles],
|
||||
);
|
||||
return useSyncExternalStore(subscribe, getSnapshot);
|
||||
};
|
||||
10
packages/ui-contexts/src/hooks/useRole.ts
Normal file
10
packages/ui-contexts/src/hooks/useRole.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { ObjectId } from 'mongodb';
|
||||
import { useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
|
||||
import { AuthorizationContext } from '../AuthorizationContext';
|
||||
|
||||
export const useRole = (role: string | ObjectId): boolean => {
|
||||
const { queryRole } = useContext(AuthorizationContext);
|
||||
const [subscribe, getSnapshot] = useMemo(() => queryRole(role), [queryRole, role]);
|
||||
return useSyncExternalStore(subscribe, getSnapshot);
|
||||
};
|
||||
40
packages/ui-contexts/src/hooks/useRoleIdResolver.ts
Normal file
40
packages/ui-contexts/src/hooks/useRoleIdResolver.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { IRole } from '@rocket.chat/core-typings';
|
||||
import { useCallback, useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
|
||||
import { AuthorizationContext } from '../AuthorizationContext';
|
||||
|
||||
/**
|
||||
* Returns a function that maps a role reference to a role id.
|
||||
*
|
||||
* Authorization checks such as `queryRole` match on `IRole._id`. The default roles
|
||||
* are seeded with `_id === name`, but a custom role gets a random id, so its name
|
||||
* never matches. Use this resolver whenever the caller only knows the role name —
|
||||
* an app that declares an action button filter, for example.
|
||||
*
|
||||
* A known id wins over a name, so an existing caller that already passes an id is
|
||||
* unaffected. An unknown value passes through unchanged, which keeps the failure
|
||||
* mode of the check it feeds.
|
||||
*/
|
||||
export const useRoleIdResolver = (): ((role: string) => IRole['_id']) => {
|
||||
const { getRoles, subscribeToRoles } = useContext(AuthorizationContext);
|
||||
|
||||
const roles = useSyncExternalStore(subscribeToRoles, getRoles);
|
||||
|
||||
const idsByName = useMemo(() => {
|
||||
const index = new Map<IRole['name'], IRole['_id']>();
|
||||
for (const role of roles.values()) {
|
||||
// `roles.create` and `roles.update` reject a name already taken by another role, so a
|
||||
// name maps to at most one role. Should duplicates still exist (e.g. written straight
|
||||
// to the database), the first one iterated wins instead of the last, so adding another
|
||||
// duplicate later does not silently repoint every check that names it.
|
||||
if (index.has(role.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
index.set(role.name, role._id);
|
||||
}
|
||||
return index;
|
||||
}, [roles]);
|
||||
|
||||
return useCallback((role: string) => (roles.has(role) ? role : (idsByName.get(role) ?? role)), [idsByName, roles]);
|
||||
};
|
||||
11
packages/ui-contexts/src/hooks/useRolesDescription.ts
Normal file
11
packages/ui-contexts/src/hooks/useRolesDescription.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useCallback, useContext, useSyncExternalStore } from 'react';
|
||||
|
||||
import { AuthorizationContext } from '../AuthorizationContext';
|
||||
|
||||
export const useRolesDescription = () => {
|
||||
const { getRoles, subscribeToRoles } = useContext(AuthorizationContext);
|
||||
|
||||
const roles = useSyncExternalStore(subscribeToRoles, getRoles);
|
||||
|
||||
return useCallback((ids: string[]) => ids.map((role) => roles.get(role)?.description || roles.get(role)?.name || role), [roles]);
|
||||
};
|
||||
5
packages/ui-contexts/src/hooks/useRoomAvatarPath.ts
Normal file
5
packages/ui-contexts/src/hooks/useRoomAvatarPath.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AvatarUrlContext } from '../AvatarUrlContext';
|
||||
|
||||
export const useRoomAvatarPath = (): ((...args: any) => string) => useContext(AvatarUrlContext).getRoomPathAvatar;
|
||||
6
packages/ui-contexts/src/hooks/useRoomToolbox.ts
Normal file
6
packages/ui-contexts/src/hooks/useRoomToolbox.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { RoomToolboxContextValue } from '../RoomToolboxContext';
|
||||
import { RoomToolboxContext } from '../RoomToolboxContext';
|
||||
|
||||
export const useRoomToolbox = (): RoomToolboxContextValue => useContext(RoomToolboxContext);
|
||||
36
packages/ui-contexts/src/hooks/useRoute.ts
Normal file
36
packages/ui-contexts/src/hooks/useRoute.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useContext, useMemo } from 'react';
|
||||
|
||||
import type { RouteName, RouteParameters } from '../RouterContext';
|
||||
import { RouterContext } from '../RouterContext';
|
||||
|
||||
type Route = {
|
||||
push: (
|
||||
parameters?: RouteParameters,
|
||||
queryStringParameters?: ((prev: Record<string, string>) => Record<string, string>) | Record<string, string>,
|
||||
) => void;
|
||||
replace: (
|
||||
parameters?: RouteParameters,
|
||||
queryStringParameters?: ((prev: Record<string, string>) => Record<string, string>) | Record<string, string>,
|
||||
) => void;
|
||||
};
|
||||
|
||||
/** @deprecated prefer `useRouter` */
|
||||
export const useRoute = (name: RouteName): Route => {
|
||||
const router = useContext(RouterContext);
|
||||
|
||||
return useMemo<Route>(
|
||||
() => ({
|
||||
push: (params, queryStringParameters) => {
|
||||
const search =
|
||||
typeof queryStringParameters === 'function' ? queryStringParameters(router.getSearchParameters()) : queryStringParameters;
|
||||
router.navigate({ name, params, search }, { replace: false });
|
||||
},
|
||||
replace: (params, queryStringParameters) => {
|
||||
const search =
|
||||
typeof queryStringParameters === 'function' ? queryStringParameters(router.getSearchParameters()) : queryStringParameters;
|
||||
router.navigate({ name, params, search }, { replace: true });
|
||||
},
|
||||
}),
|
||||
[name, router],
|
||||
);
|
||||
};
|
||||
13
packages/ui-contexts/src/hooks/useRouteParameter.ts
Normal file
13
packages/ui-contexts/src/hooks/useRouteParameter.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useCallback, useContext, useSyncExternalStore } from 'react';
|
||||
|
||||
import { RouterContext } from '../RouterContext';
|
||||
|
||||
export const useRouteParameter = (name: string): string | undefined => {
|
||||
const router = useContext(RouterContext);
|
||||
|
||||
const getSnapshot = useCallback(() => {
|
||||
return router.getRouteParameters()[name];
|
||||
}, [router, name]);
|
||||
|
||||
return useSyncExternalStore(router.subscribeToRouteChange, getSnapshot);
|
||||
};
|
||||
5
packages/ui-contexts/src/hooks/useRouter.ts
Normal file
5
packages/ui-contexts/src/hooks/useRouter.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { RouterContext } from '../RouterContext';
|
||||
|
||||
export const useRouter = () => useContext(RouterContext);
|
||||
14
packages/ui-contexts/src/hooks/useSearchParameter.ts
Normal file
14
packages/ui-contexts/src/hooks/useSearchParameter.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useCallback, useContext, useSyncExternalStore } from 'react';
|
||||
|
||||
import { RouterContext } from '../RouterContext';
|
||||
|
||||
export const useSearchParameter = (name: string): string | undefined => {
|
||||
const { getSearchParameters, subscribeToRouteChange } = useContext(RouterContext);
|
||||
|
||||
const getSnapshot = useCallback(() => {
|
||||
const searchParameters = getSearchParameters();
|
||||
return searchParameters[name];
|
||||
}, [getSearchParameters, name]);
|
||||
|
||||
return useSyncExternalStore(subscribeToRouteChange, getSnapshot);
|
||||
};
|
||||
8
packages/ui-contexts/src/hooks/useSearchParameters.ts
Normal file
8
packages/ui-contexts/src/hooks/useSearchParameters.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useContext, useSyncExternalStore } from 'react';
|
||||
|
||||
import { RouterContext } from '../RouterContext';
|
||||
|
||||
export const useSearchParameters = () => {
|
||||
const { getSearchParameters, subscribeToRouteChange } = useContext(RouterContext);
|
||||
return useSyncExternalStore(subscribeToRouteChange, getSearchParameters);
|
||||
};
|
||||
25
packages/ui-contexts/src/hooks/useSelectedDevices.ts
Normal file
25
packages/ui-contexts/src/hooks/useSelectedDevices.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { Device } from '../DeviceContext';
|
||||
import { DeviceContext, isDeviceContextEnabled } from '../DeviceContext';
|
||||
|
||||
type SelectedDevices = {
|
||||
audioInput?: Device;
|
||||
audioOutput?: Device;
|
||||
};
|
||||
|
||||
export const useSelectedDevices = (): SelectedDevices | null => {
|
||||
const context = useContext(DeviceContext);
|
||||
|
||||
if (!isDeviceContextEnabled(context)) {
|
||||
console.warn(
|
||||
'Device Management is disabled on unsecure contexts, see https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
audioInput: context.selectedAudioInputDevice,
|
||||
audioOutput: context.selectedAudioOutputDevice,
|
||||
};
|
||||
};
|
||||
12
packages/ui-contexts/src/hooks/useServerInformation.ts
Normal file
12
packages/ui-contexts/src/hooks/useServerInformation.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { IServerInfo } from '@rocket.chat/core-typings';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { ServerContext } from '../ServerContext';
|
||||
|
||||
export const useServerInformation = (): IServerInfo => {
|
||||
const { info } = useContext(ServerContext);
|
||||
if (!info) {
|
||||
throw new Error('useServerInformation: no info available');
|
||||
}
|
||||
return info;
|
||||
};
|
||||
9
packages/ui-contexts/src/hooks/useSession.ts
Normal file
9
packages/ui-contexts/src/hooks/useSession.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
|
||||
import { SessionContext } from '../SessionContext';
|
||||
|
||||
export const useSession = (name: string): unknown => {
|
||||
const { query } = useContext(SessionContext);
|
||||
const [subscribe, getSnapshot] = useMemo(() => query(name), [query, name]);
|
||||
return useSyncExternalStore(subscribe, getSnapshot);
|
||||
};
|
||||
8
packages/ui-contexts/src/hooks/useSessionDispatch.ts
Normal file
8
packages/ui-contexts/src/hooks/useSessionDispatch.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useCallback, useContext } from 'react';
|
||||
|
||||
import { SessionContext } from '../SessionContext';
|
||||
|
||||
export const useSessionDispatch = (name: string) => {
|
||||
const { dispatch } = useContext(SessionContext);
|
||||
return useCallback((value: unknown) => dispatch(name, value), [dispatch, name]);
|
||||
};
|
||||
18
packages/ui-contexts/src/hooks/useSetInputMediaDevice.ts
Normal file
18
packages/ui-contexts/src/hooks/useSetInputMediaDevice.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { Device } from '../DeviceContext';
|
||||
import { DeviceContext, isDeviceContextEnabled } from '../DeviceContext';
|
||||
|
||||
type setInputMediaDevice = (inputDevice: Device) => void;
|
||||
|
||||
export const useSetInputMediaDevice = (): setInputMediaDevice => {
|
||||
const context = useContext(DeviceContext);
|
||||
|
||||
if (!isDeviceContextEnabled(context)) {
|
||||
console.warn(
|
||||
'Device Management is disabled on unsecure contexts, see https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts',
|
||||
);
|
||||
return () => undefined;
|
||||
}
|
||||
return context.setAudioInputDevice;
|
||||
};
|
||||
5
packages/ui-contexts/src/hooks/useSetModal.ts
Normal file
5
packages/ui-contexts/src/hooks/useSetModal.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { useModal } from './useModal';
|
||||
|
||||
export const useSetModal = (): ((modal?: ReactNode) => void) => useModal().setModal;
|
||||
21
packages/ui-contexts/src/hooks/useSetOutputMediaDevice.ts
Normal file
21
packages/ui-contexts/src/hooks/useSetOutputMediaDevice.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { Device } from '../DeviceContext';
|
||||
import { DeviceContext, isDeviceContextEnabled } from '../DeviceContext';
|
||||
|
||||
// This allows different places to set the output device by providing a HTMLAudioElement
|
||||
|
||||
type setOutputMediaDevice = ({ outputDevice, HTMLAudioElement }: { outputDevice: Device; HTMLAudioElement: HTMLAudioElement }) => void;
|
||||
|
||||
export const useSetOutputMediaDevice = (): setOutputMediaDevice => {
|
||||
const context = useContext(DeviceContext);
|
||||
|
||||
if (!isDeviceContextEnabled(context)) {
|
||||
console.warn(
|
||||
'Device Management is disabled on unsecure contexts, see https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts',
|
||||
);
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
return context.setAudioOutputDevice;
|
||||
};
|
||||
13
packages/ui-contexts/src/hooks/useSetting.ts
Normal file
13
packages/ui-contexts/src/hooks/useSetting.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { ISetting } from '@rocket.chat/core-typings';
|
||||
|
||||
import { useSettingStructure } from './useSettingStructure';
|
||||
|
||||
export function useSetting<TSetting extends ISetting>(settingId: TSetting['_id']): TSetting['value'] | undefined;
|
||||
export function useSetting(settingId: ISetting['_id'], fallbackValue: string): string;
|
||||
export function useSetting(settingId: ISetting['_id'], fallbackValue: boolean): boolean;
|
||||
export function useSetting(settingId: ISetting['_id'], fallbackValue: number): number;
|
||||
export function useSetting<TValue>(settingId: ISetting['_id'], fallbackValue: TValue): TValue;
|
||||
export function useSetting<TValue>(settingId: ISetting['_id'], fallbackValue?: TValue): TValue | undefined {
|
||||
const setting = useSettingStructure(settingId);
|
||||
return (setting?.value as TValue | undefined) ?? fallbackValue;
|
||||
}
|
||||
9
packages/ui-contexts/src/hooks/useSettingSetValue.ts
Normal file
9
packages/ui-contexts/src/hooks/useSettingSetValue.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { ISetting } from '@rocket.chat/core-typings';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useSettingsDispatch } from './useSettingsDispatch';
|
||||
|
||||
export const useSettingSetValue = <T extends ISetting['value']>(_id: ISetting['_id']): ((value: T) => Promise<void>) => {
|
||||
const dispatch = useSettingsDispatch();
|
||||
return useCallback((value: T) => dispatch([{ _id, value }]), [dispatch, _id]);
|
||||
};
|
||||
10
packages/ui-contexts/src/hooks/useSettingStructure.ts
Normal file
10
packages/ui-contexts/src/hooks/useSettingStructure.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { ISetting } from '@rocket.chat/core-typings';
|
||||
import { useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
|
||||
import { SettingsContext } from '../SettingsContext';
|
||||
|
||||
export const useSettingStructure = (_id: ISetting['_id']): ISetting | undefined => {
|
||||
const { querySetting } = useContext(SettingsContext);
|
||||
const [subscribe, getSnapshot] = useMemo(() => querySetting(_id), [querySetting, _id]);
|
||||
return useSyncExternalStore(subscribe, getSnapshot);
|
||||
};
|
||||
11
packages/ui-contexts/src/hooks/useSettings.ts
Normal file
11
packages/ui-contexts/src/hooks/useSettings.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { ISetting } from '@rocket.chat/core-typings';
|
||||
import { useContext, useMemo, useSyncExternalStore } from 'react';
|
||||
|
||||
import type { SettingsContextQuery } from '../SettingsContext';
|
||||
import { SettingsContext } from '../SettingsContext';
|
||||
|
||||
export const useSettings = (query?: SettingsContextQuery): ISetting[] => {
|
||||
const { querySettings } = useContext(SettingsContext);
|
||||
const [subscribe, getSnapshot] = useMemo(() => querySettings(query ?? {}), [querySettings, query]);
|
||||
return useSyncExternalStore(subscribe, getSnapshot);
|
||||
};
|
||||
6
packages/ui-contexts/src/hooks/useSettingsDispatch.ts
Normal file
6
packages/ui-contexts/src/hooks/useSettingsDispatch.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { SettingsContextValue } from '../SettingsContext';
|
||||
import { SettingsContext } from '../SettingsContext';
|
||||
|
||||
export const useSettingsDispatch = (): SettingsContextValue['dispatch'] => useContext(SettingsContext).dispatch;
|
||||
30
packages/ui-contexts/src/hooks/useStream.ts
Normal file
30
packages/ui-contexts/src/hooks/useStream.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { StreamNames, StreamerEvents, StreamKeys, StreamerCallbackArgs } from '@rocket.chat/ddp-client';
|
||||
import { useContext, useMemo } from 'react';
|
||||
|
||||
import { ServerContext } from '../ServerContext';
|
||||
|
||||
// Define a type for a function that can be used to unsubscribe from a stream
|
||||
export type ServerStreamFunction<N extends StreamNames, K extends StreamKeys<N>> = (
|
||||
args: K,
|
||||
callback: (...args: StreamerCallbackArgs<N, K>) => void,
|
||||
) => () => void;
|
||||
|
||||
// Define a type for the configuration of a stream, based on its name
|
||||
type StreamsConfigs<N extends StreamNames> = StreamerEvents[N][number];
|
||||
|
||||
// Define a type for the callback function that will be returned by useStream/useSingleStream
|
||||
// This type will depend on the type of the stream it subscribes to
|
||||
type StreamerCallback<S extends StreamNames, N extends StreamsConfigs<S> = StreamsConfigs<S>> = <E extends N['key']>(
|
||||
eventName: E,
|
||||
cb: (...event: StreamerCallbackArgs<S, E>) => void,
|
||||
) => () => void;
|
||||
export function useStream<N extends StreamNames>(
|
||||
streamName: N,
|
||||
options?: {
|
||||
retransmit?: boolean;
|
||||
retransmitToSelf?: boolean;
|
||||
},
|
||||
): StreamerCallback<N> {
|
||||
const { getStream } = useContext(ServerContext);
|
||||
return useMemo(() => getStream(streamName, options), [getStream, streamName, options]);
|
||||
}
|
||||
13
packages/ui-contexts/src/hooks/useStreamAll.ts
Normal file
13
packages/ui-contexts/src/hooks/useStreamAll.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { StreamNames, StreamerEvents } from '@rocket.chat/ddp-client';
|
||||
import { useContext, useMemo } from 'react';
|
||||
|
||||
import { ServerContext } from '../ServerContext';
|
||||
|
||||
export type StreamAllSubscriber<N extends StreamNames> = (
|
||||
callback: (eventName: string, args: StreamerEvents[N][number]['args']) => void,
|
||||
) => () => void;
|
||||
|
||||
export function useStreamAll<N extends StreamNames>(streamName: N): StreamAllSubscriber<N> {
|
||||
const { getStreamAll } = useContext(ServerContext);
|
||||
return useMemo(() => getStreamAll(streamName), [getStreamAll, streamName]);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { ToastMessagesContextValue } from '../ToastMessagesContext';
|
||||
import { ToastMessagesContext } from '../ToastMessagesContext';
|
||||
|
||||
export const useToastMessageDispatch = (): ToastMessagesContextValue['dispatch'] => useContext(ToastMessagesContext).dispatch;
|
||||
6
packages/ui-contexts/src/hooks/useTooltipClose.ts
Normal file
6
packages/ui-contexts/src/hooks/useTooltipClose.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { TooltipContextValue } from '../TooltipContext';
|
||||
import { TooltipContext } from '../TooltipContext';
|
||||
|
||||
export const useTooltipClose = (): TooltipContextValue['close'] => useContext(TooltipContext).close;
|
||||
6
packages/ui-contexts/src/hooks/useTooltipOpen.ts
Normal file
6
packages/ui-contexts/src/hooks/useTooltipOpen.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { TooltipContextValue } from '../TooltipContext';
|
||||
import { TooltipContext } from '../TooltipContext';
|
||||
|
||||
export const useTooltipOpen = (): TooltipContextValue['open'] => useContext(TooltipContext).open;
|
||||
7
packages/ui-contexts/src/hooks/useTranslation.ts
Normal file
7
packages/ui-contexts/src/hooks/useTranslation.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import type { TranslationContextValue } from '../TranslationContext';
|
||||
import { TranslationContext } from '../TranslationContext';
|
||||
|
||||
/** @deprecated prefer `useTranslation` from `react-i18next` */
|
||||
export const useTranslation = (): TranslationContextValue['translate'] => useContext(TranslationContext).translate;
|
||||
5
packages/ui-contexts/src/hooks/useUnstoreLoginToken.ts
Normal file
5
packages/ui-contexts/src/hooks/useUnstoreLoginToken.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AuthenticationContext } from '../AuthenticationContext';
|
||||
|
||||
export const useUnstoreLoginToken = (): ((callback: () => void) => () => void) => useContext(AuthenticationContext).unstoreLoginToken;
|
||||
10
packages/ui-contexts/src/hooks/useUpload.ts
Normal file
10
packages/ui-contexts/src/hooks/useUpload.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { PathFor } from '@rocket.chat/rest-typings';
|
||||
import { useCallback, useContext } from 'react';
|
||||
|
||||
import type { UploadResult } from '../ServerContext';
|
||||
import { ServerContext } from '../ServerContext';
|
||||
|
||||
export const useUpload = (endpoint: PathFor<'POST'>): ((formData: any) => Promise<UploadResult> | { promise: Promise<UploadResult> }) => {
|
||||
const { uploadToEndpoint } = useContext(ServerContext);
|
||||
return useCallback((formData: any) => uploadToEndpoint(endpoint, formData), [endpoint, uploadToEndpoint]);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user