diff --git a/README.md b/README.md index b9a73928..aa83a8a4 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,7 @@ So the idea is that we have a `uiCommands` object and at a point, there will be ### Dom Element Manipulation - `useChatMessageDomElements` hook: This hook will return the dom element of a chat message reactively, so one can modify whatever is inside, such as text, css, js, etc.; +- `useUserCameraDomElements` hook: This hook will return the dom element of each of the user's webcam corresponding to the streamIds passed reactively, so one can modify whatever is inside, such as text, css, js, etc., and also can get the video element within it; ### Learning Analytics Dashboard integration diff --git a/samples/sample-user-camera-dropdown-plugin/src/queries.ts b/samples/sample-user-camera-dropdown-plugin/src/queries.ts new file mode 100644 index 00000000..de6bfa30 --- /dev/null +++ b/samples/sample-user-camera-dropdown-plugin/src/queries.ts @@ -0,0 +1,9 @@ +export const VIDEO_STREAMS_SUBSCRIPTION = `subscription VideoStreams { + user_camera { + streamId + user { + name + userId + } + } +}`; diff --git a/samples/sample-user-camera-dropdown-plugin/src/sample-user-camera-dropdown-plugin-item/component.tsx b/samples/sample-user-camera-dropdown-plugin/src/sample-user-camera-dropdown-plugin-item/component.tsx index 6d07ec73..a8c8c03b 100644 --- a/samples/sample-user-camera-dropdown-plugin/src/sample-user-camera-dropdown-plugin-item/component.tsx +++ b/samples/sample-user-camera-dropdown-plugin/src/sample-user-camera-dropdown-plugin-item/component.tsx @@ -2,26 +2,114 @@ import * as React from 'react'; import { useEffect } from 'react'; import { - BbbPluginSdk, PluginApi, pluginLogger, UserCameraDropdownOption, UserCameraDropdownSeparator, + BbbPluginSdk, + PluginApi, + pluginLogger, + ScreenshareHelperItemPosition, + ScreenshareHelperButton, + UserCameraDropdownOption, + UserCameraDropdownSeparator, } from 'bigbluebutton-html-plugin-sdk'; -import { SampleUserCameraDropdownPluginProps } from './types'; +import { SampleUserCameraDropdownPluginProps, VideoStreamsSubscriptionResultType } from './types'; +import { VIDEO_STREAMS_SUBSCRIPTION } from '../queries'; function SampleUserCameraDropdownPlugin({ pluginUuid: uuid }: SampleUserCameraDropdownPluginProps): React.ReactElement { + BbbPluginSdk.initialize(uuid); const pluginApi: PluginApi = BbbPluginSdk.getPluginApi(uuid); + const { data: videoStreams } = pluginApi.useCustomSubscription< + VideoStreamsSubscriptionResultType + >(VIDEO_STREAMS_SUBSCRIPTION); + + const userCamera = pluginApi.useUserCameraDomElements( + videoStreams?.user_camera.map((vs) => vs.streamId), + ); + + pluginLogger.info(`logging the domElements manipulation for userCamera: (${userCamera}) for streams (${videoStreams})`); + useEffect(() => { + const buttonScreenshare1 = new ScreenshareHelperButton({ + icon: 'user', + disabled: false, + label: 'This will log on the console', + tooltip: 'this is a button injected by plugin', + position: ScreenshareHelperItemPosition.TOP_RIGHT, + onClick: () => { + pluginLogger.info('Logging from the screenshare extensible area'); + }, + hasSeparator: true, + }); + + const buttonScreenshare5 = new ScreenshareHelperButton({ + icon: 'popout_window', + disabled: false, + label: 'This will log on the console', + tooltip: 'this is a button injected by plugin', + position: ScreenshareHelperItemPosition.TOP_RIGHT, + onClick: () => { + pluginLogger.info('Logging from the screenshare extensible area'); + }, + hasSeparator: true, + }); + + const buttonScreenshare2 = new ScreenshareHelperButton({ + icon: 'undo', + disabled: false, + label: 'This will log on the console', + tooltip: 'this is a button injected by plugin', + position: ScreenshareHelperItemPosition.TOP_LEFT, + onClick: () => { + pluginLogger.info('Logging from the screenshare extensible area'); + }, + hasSeparator: true, + }); + + const buttonScreenshare3 = new ScreenshareHelperButton({ + icon: 'settings', + disabled: false, + label: 'This will log on the console', + tooltip: 'this is a button injected by plugin', + position: ScreenshareHelperItemPosition.BOTTOM_LEFT, + onClick: () => { + pluginLogger.info('Logging from the screenshare extensible area'); + }, + hasSeparator: true, + }); + + const buttonScreenshare4 = new ScreenshareHelperButton({ + icon: 'plus', + disabled: false, + label: 'This will log on the console', + tooltip: 'this is a button injected by plugin', + position: ScreenshareHelperItemPosition.BOTTOM_RIGHT, + onClick: () => { + pluginLogger.info('Logging from the screenshare extensible area'); + }, + hasSeparator: true, + }); + pluginApi.setScreenshareHelperItems([buttonScreenshare1, buttonScreenshare5, + buttonScreenshare2, buttonScreenshare3, buttonScreenshare4, + ]); + }, []); + + useEffect(() => { + const randomElement = videoStreams?.user_camera[ + Math.floor(Math.random() * (videoStreams?.user_camera.length ?? 0)) + ]; + pluginApi.setUserCameraDropdownItems([ new UserCameraDropdownSeparator(), new UserCameraDropdownOption({ label: 'This will log on the console', icon: 'user', - onClick: () => { - pluginLogger.info('Alert sent from plugin'); + displayFunction: ({ userId }) => randomElement?.user.userId === userId, + onClick: ({ userId, streamId, browserClickEvent }) => { + pluginLogger.info(`Alert sent from plugin, see userId: ${userId}; ${streamId}; ${browserClickEvent.clientX}`); }, }), ]); - }, []); + }, [videoStreams]); return null; } diff --git a/samples/sample-user-camera-dropdown-plugin/src/sample-user-camera-dropdown-plugin-item/types.ts b/samples/sample-user-camera-dropdown-plugin/src/sample-user-camera-dropdown-plugin-item/types.ts index bf7f66ea..fb28114e 100644 --- a/samples/sample-user-camera-dropdown-plugin/src/sample-user-camera-dropdown-plugin-item/types.ts +++ b/samples/sample-user-camera-dropdown-plugin/src/sample-user-camera-dropdown-plugin-item/types.ts @@ -3,4 +3,14 @@ interface SampleUserCameraDropdownPluginProps { pluginUuid: string, } -export { SampleUserCameraDropdownPluginProps }; +interface VideoStreamsSubscriptionResultType { + user_camera?: { + streamId: string + user: { + name: string + userId: string + }; + }[]; +} + +export { SampleUserCameraDropdownPluginProps, VideoStreamsSubscriptionResultType }; diff --git a/src/core/api/BbbPluginSdk.ts b/src/core/api/BbbPluginSdk.ts index 675cd36d..d2b0fb43 100644 --- a/src/core/api/BbbPluginSdk.ts +++ b/src/core/api/BbbPluginSdk.ts @@ -37,6 +37,7 @@ import { usePluginSettings } from '../../data-consumption/domain/settings'; import { UseLoadedChatMessagesFunction } from '../../data-consumption/domain/chat/loaded-chat-messages/types'; import { useLoadedChatMessages } from '../../data-consumption/domain/chat/loaded-chat-messages/hooks'; import { useChatMessageDomElements } from '../../dom-element-manipulation/chat/message/hooks'; +import { useUserCameraDomElements } from '../../dom-element-manipulation/user-camera/hooks'; import { UseTalkingIndicatorFunction } from '../../data-consumption/domain/user-voice/talking-indicator/types'; import { useTalkingIndicator } from '../../data-consumption/domain/user-voice/talking-indicator/hooks'; import { useUiData } from '../../ui-data-hooks/hooks'; @@ -86,6 +87,9 @@ export abstract class BbbPluginSdk { pluginApi.useChatMessageDomElements = ( messageIds: string[], ) => useChatMessageDomElements(messageIds); + pluginApi.useUserCameraDomElements = ( + streamIds: string[], + ) => useUserCameraDomElements(streamIds); pluginApi.uiCommands = uiCommands; pluginApi.serverCommands = serverCommands; pluginApi.useUiData = useUiData; @@ -149,6 +153,7 @@ export abstract class BbbPluginSdk { setAudioSettingsDropdownItems: () => [], setPresentationDropdownItems: () => [], setNavBarItems: () => [], + setScreenshareHelperItems: () => [], setOptionsDropdownItems: () => [], setCameraSettingsDropdownItems: () => [], setUserCameraDropdownItems: () => [], diff --git a/src/core/api/types.ts b/src/core/api/types.ts index bbe83962..d96674d5 100644 --- a/src/core/api/types.ts +++ b/src/core/api/types.ts @@ -28,6 +28,8 @@ import { UseUiDataFunction } from '../../ui-data-hooks/types'; import { UseMeetingFunction } from '../../data-consumption/domain/meeting/from-core/types'; import { ServerCommands } from '../../server-commands/types'; import { SendGenericDataForLearningAnalyticsDashboard } from '../../learning-analytics-dashboard/types'; +import { UseUserCameraDomElementsFunction } from '../../dom-element-manipulation/user-camera/types'; +import { ScreenshareHelperInterface } from '../../extensible-areas'; // Setter Functions for the API export type SetPresentationToolbarItems = (presentationToolbarItem: @@ -54,7 +56,11 @@ export type SetPresentationDropdownItems = ( ) => string[]; export type SetNavBarItems = ( - userListDropdownItem: NavBarInterface[] + navBarItem: NavBarInterface[] +) => string[]; + +export type SetScreenshareHelperItems = ( + screenshareHelperItem: ScreenshareHelperInterface[] ) => string[]; export type SetOptionsDropdownItems = ( @@ -90,6 +96,7 @@ export interface PluginApi { setAudioSettingsDropdownItems: SetAudioSettingsDropdownItems; setPresentationDropdownItems: SetPresentationDropdownItems; setNavBarItems: SetNavBarItems; + setScreenshareHelperItems: SetScreenshareHelperItems; setOptionsDropdownItems: SetOptionsDropdownItems; setCameraSettingsDropdownItems: SetCameraSettingsDropdownItems; setUserCameraDropdownItems: SetUserCameraDropdownItems; @@ -211,6 +218,15 @@ export interface PluginApi { * */ useChatMessageDomElements?: UseChatMessageDomElementsFunction; + /** + * Returns an array with the DOM elements for the webcams corresponding to each streamId passed. + * + * @param streamIds - Ids of the user-camera streams one wants to retrieve in the form of an array + * @returns The array of an object with DOM elements (in this case, div) and the id + * of the stream + * + */ + useUserCameraDomElements?: UseUserCameraDomElementsFunction; // --- Auxiliary functions --- getSessionToken?: GetSessionTokenFunction; getJoinUrl?: GetJoinUrlFunction; diff --git a/src/dom-element-manipulation/enums.ts b/src/dom-element-manipulation/enums.ts index 17130d8c..cdad1170 100644 --- a/src/dom-element-manipulation/enums.ts +++ b/src/dom-element-manipulation/enums.ts @@ -1,3 +1,4 @@ export enum DomElementManipulationHooks { CHAT_MESSAGE = 'CHAT_MESSAGE', + USER_CAMERA = 'USER_CAMERA', } diff --git a/src/dom-element-manipulation/type.ts b/src/dom-element-manipulation/type.ts index 288af264..7bb37bb4 100644 --- a/src/dom-element-manipulation/type.ts +++ b/src/dom-element-manipulation/type.ts @@ -1,3 +1,5 @@ import { ChatMessageDomElementsArguments } from './chat/message/types'; +import { UserCameraDomElementsArguments } from './user-camera/types'; -export type DomElementManipulationArguments = ChatMessageDomElementsArguments; +export type DomElementManipulationArguments = ChatMessageDomElementsArguments + | UserCameraDomElementsArguments; diff --git a/src/dom-element-manipulation/user-camera/hooks.ts b/src/dom-element-manipulation/user-camera/hooks.ts new file mode 100644 index 00000000..4eb8cc1a --- /dev/null +++ b/src/dom-element-manipulation/user-camera/hooks.ts @@ -0,0 +1,62 @@ +import { useEffect, useState } from 'react'; +import { DomElementManipulationHooks } from '../enums'; +import { HookEvents } from '../../core/enum'; +import { + HookEventWrapper, SubscribedEventDetails, UnsubscribedEventDetails, UpdatedEventDetails, +} from '../../core/types'; +import { UserCameraDomElementsArguments, UpdatedEventDetailsForUserCameraDomElement } from './types'; +import { sortedStringify } from '../../data-consumption/utils'; + +export const useUserCameraDomElements = (streamIds: string[]) => { + const [domElement, setDomElement] = useState(); + const [streamIdsState, setStreamIdsState] = useState(streamIds); + + const handleCustomSubscriptionUpdateEvent: EventListener = ( + (event: HookEventWrapper< + UpdatedEventDetails>) => { + const detail = event.detail as UpdatedEventDetails< + UpdatedEventDetailsForUserCameraDomElement[]>; + if (detail.hook === DomElementManipulationHooks.USER_CAMERA + && sortedStringify( + detail.data.map((userCamera) => userCamera.streamId), + ) === sortedStringify(streamIdsState)) { + setDomElement(detail.data.map((userCamera) => userCamera.userCameraDomElement)); + } + }) as EventListener; + + useEffect(() => { + if (streamIdsState) { + window.addEventListener(HookEvents.UPDATED, handleCustomSubscriptionUpdateEvent); + return () => { + window.removeEventListener(HookEvents.UPDATED, handleCustomSubscriptionUpdateEvent); + window.dispatchEvent(new CustomEvent(HookEvents.UNSUBSCRIBED, { + detail: { + hook: DomElementManipulationHooks.USER_CAMERA, + hookArguments: { + streamIds, + } as UserCameraDomElementsArguments, + }, + })); + }; + } + return () => {}; + }, []); + useEffect(() => { + if (streamIdsState) { + window.removeEventListener(HookEvents.UPDATED, handleCustomSubscriptionUpdateEvent); + window.addEventListener(HookEvents.UPDATED, handleCustomSubscriptionUpdateEvent); + window.dispatchEvent(new CustomEvent(HookEvents.SUBSCRIBED, { + detail: { + hook: DomElementManipulationHooks.USER_CAMERA, + hookArguments: { + streamIds: streamIdsState, + } as UserCameraDomElementsArguments, + }, + })); + } + }, [streamIdsState]); + if (sortedStringify(streamIds) !== sortedStringify(streamIdsState)) { + setStreamIdsState(streamIds); + } + return domElement; +}; diff --git a/src/dom-element-manipulation/user-camera/types.ts b/src/dom-element-manipulation/user-camera/types.ts new file mode 100644 index 00000000..597321c8 --- /dev/null +++ b/src/dom-element-manipulation/user-camera/types.ts @@ -0,0 +1,12 @@ +export type UseUserCameraDomElementsFunction = ( + streamIds: string[] +) => HTMLDivElement[] | undefined; + +export interface UserCameraDomElementsArguments { + streamIds: string[]; +} + +export interface UpdatedEventDetailsForUserCameraDomElement { + streamId: string; + userCameraDomElement: HTMLDivElement; +} diff --git a/src/extensible-areas/base.ts b/src/extensible-areas/base.ts index 0859e209..a211bcd8 100644 --- a/src/extensible-areas/base.ts +++ b/src/extensible-areas/base.ts @@ -8,6 +8,7 @@ import { NavBarItemType } from './nav-bar-item/enums'; import { OptionsDropdownItemType } from './options-dropdown-item/enums'; import { PresentationDropdownItemType } from './presentation-dropdown-item/enums'; import { PresentationToolbarItemType } from './presentation-toolbar-item/enums'; +import { ScreenshareHelperItemType } from './screenshare-helper-item/enums'; import { UserCameraDropdownItemType } from './user-camera-dropdown-item/enums'; import { UserListDropdownItemType } from './user-list-dropdown-item/enums'; import { UserListItemAdditionalInformationType } from './user-list-item-additional-information/enums'; @@ -17,7 +18,8 @@ type PluginProvidedUiItemType = PresentationToolbarItemType | ActionsBarItemType | AudioSettingsDropdownItemType | PresentationDropdownItemType | NavBarItemType | OptionsDropdownItemType | CameraSettingsDropdownItemType | UserCameraDropdownItemType | - UserListItemAdditionalInformationType | FloatingWindowType | GenericContentType; + UserListItemAdditionalInformationType | FloatingWindowType | GenericContentType | + ScreenshareHelperItemType; export interface PluginProvidedUiItemDescriptor { /** Defined by BigBlueButton Plugin Engine. */ diff --git a/src/extensible-areas/index.ts b/src/extensible-areas/index.ts index ca405237..e7d074ce 100644 --- a/src/extensible-areas/index.ts +++ b/src/extensible-areas/index.ts @@ -5,6 +5,7 @@ export * from './actions-bar-item'; export * from './audio-settings-dropdown-item'; export * from './presentation-dropdown-item'; export * from './nav-bar-item'; +export * from './screenshare-helper-item'; export * from './options-dropdown-item'; export * from './camera-settings-dropdown-item'; export * from './user-camera-dropdown-item'; diff --git a/src/extensible-areas/screenshare-helper-item/component.ts b/src/extensible-areas/screenshare-helper-item/component.ts new file mode 100644 index 00000000..fa6c9b0c --- /dev/null +++ b/src/extensible-areas/screenshare-helper-item/component.ts @@ -0,0 +1,58 @@ +import { ScreenshareHelperItemType, ScreenshareHelperItemPosition } from './enums'; +import { + ScreenshareHelperButtonProps, + ScreenshareHelperButtonInterface, +} from './types'; + +// ScreenshareHelper Extensible Area + +export class ScreenshareHelperButton implements ScreenshareHelperButtonInterface { + id: string = ''; + + type: ScreenshareHelperItemType; + + label: string; + + icon: string; + + tooltip: string; + + disabled: boolean; + + position: ScreenshareHelperItemPosition; + + onClick: () => void; + + /** + * Returns object to be used in the setter for the Screenshare Helper. In this case, + * a button. + * + * @param label - label to be displayed in screenshare helper button (Not mandatory). + * @param tooltip - label to be displayed when hovering the screenshare helper button. + * @param icon - icon to be used in the screenshare helper button. It goes in the left side of it. + * @param onClick - function to be called when clicking the button. + * @param position - position to place the screenshare helper button. + * See {@link ScreenshareHelperItemPosition} + * @param hasSeparator - boolean indicating whether the screenshare helper button has separator + * (vertical bar) + * @param disabled - if true, the screenshare helper button will not be clickable + * + * @returns Object that will be interpreted by the core of Bigbluebutton (HTML5). + */ + constructor({ + label = '', icon = '', tooltip = '', disabled = true, onClick = () => {}, + position = ScreenshareHelperItemPosition.TOP_RIGHT, + }: ScreenshareHelperButtonProps) { + this.label = label; + this.icon = icon; + this.tooltip = tooltip; + this.disabled = disabled; + this.onClick = onClick; + this.type = ScreenshareHelperItemType.BUTTON; + this.position = position; + } + + setItemId: (id: string) => void = (id: string) => { + this.id = `ScreenshareHelperButton_${id}`; + }; +} diff --git a/src/extensible-areas/screenshare-helper-item/enums.ts b/src/extensible-areas/screenshare-helper-item/enums.ts new file mode 100644 index 00000000..d71353f7 --- /dev/null +++ b/src/extensible-areas/screenshare-helper-item/enums.ts @@ -0,0 +1,14 @@ +// Screenshare Helper items types: +export enum ScreenshareHelperItemType { + BUTTON = 'SCREENSHARE_HELPER_BUTTON', +} + +/** + * Enum with the position to insert the screenshare helper item (Information or Button) + */ +export enum ScreenshareHelperItemPosition { + TOP_RIGHT = 'TOP_RIGHT', + TOP_LEFT = 'TOP_LEFT', + BOTTOM_RIGHT = 'BOTTOM_RIGHT', + BOTTOM_LEFT = 'BOTTOM_LEFT', +} diff --git a/src/extensible-areas/screenshare-helper-item/index.ts b/src/extensible-areas/screenshare-helper-item/index.ts new file mode 100644 index 00000000..dc3b8239 --- /dev/null +++ b/src/extensible-areas/screenshare-helper-item/index.ts @@ -0,0 +1,9 @@ +export { + ScreenshareHelperButton, +} from './component'; +export { + ScreenshareHelperInterface, +} from './types'; +export { + ScreenshareHelperItemPosition, +} from './enums'; diff --git a/src/extensible-areas/screenshare-helper-item/types.ts b/src/extensible-areas/screenshare-helper-item/types.ts new file mode 100644 index 00000000..2c4cfa04 --- /dev/null +++ b/src/extensible-areas/screenshare-helper-item/types.ts @@ -0,0 +1,30 @@ +import { PluginProvidedUiItemDescriptor } from '../base'; +import { ScreenshareHelperItemPosition } from './enums'; + +export interface ScreenshareHelperInterface extends PluginProvidedUiItemDescriptor{ + position: ScreenshareHelperItemPosition; +} + +export interface ScreenshareHelperButtonInterface extends ScreenshareHelperInterface{ + label: string; + + icon: string; + + tooltip: string; + + disabled: boolean; + + position: ScreenshareHelperItemPosition; + + onClick: () => void; +} + +export interface ScreenshareHelperButtonProps { + label?: string; + icon: string; + tooltip: string; + disabled: boolean; + hasSeparator: boolean; + position: ScreenshareHelperItemPosition; + onClick: () => void; +} diff --git a/src/extensible-areas/user-camera-dropdown-item/component.ts b/src/extensible-areas/user-camera-dropdown-item/component.ts index 27012d6b..b1b44fda 100644 --- a/src/extensible-areas/user-camera-dropdown-item/component.ts +++ b/src/extensible-areas/user-camera-dropdown-item/component.ts @@ -1,6 +1,9 @@ import { UserCameraDropdownItemType } from './enums'; import { + OnclickFunctionCallbackArguments, + UserCameraDropdownCallbackFunctionsArguments, UserCameraDropdownInterface, UserCameraDropdownOptionProps, + UserCameraDropdownSeparatorProps, } from './types'; // UserCameraDropdown Extensible Area @@ -14,7 +17,9 @@ export class UserCameraDropdownOption implements UserCameraDropdownInterface { icon: string; - onClick: () => void; + onClick: (args: OnclickFunctionCallbackArguments) => void; + + displayFunction?: (args: UserCameraDropdownCallbackFunctionsArguments) => boolean; /** * Returns object to be used in the setter for User Camera Dropdown. In this case @@ -28,7 +33,9 @@ export class UserCameraDropdownOption implements UserCameraDropdownInterface { */ constructor({ label = '', icon = '', onClick = () => {}, + displayFunction = () => true, }: UserCameraDropdownOptionProps) { + this.displayFunction = displayFunction; this.label = label; this.icon = icon; this.onClick = onClick; @@ -45,13 +52,18 @@ export class UserCameraDropdownSeparator implements UserCameraDropdownInterface type: UserCameraDropdownItemType; + displayFunction?: (args: UserCameraDropdownCallbackFunctionsArguments) => boolean; + /** * Returns object to be used in the setter for User Camera Dropdown. In this case * a separator. * * @returns Object that will be interpreted by the core of Bigbluebutton (HTML5) */ - constructor() { + constructor({ + displayFunction, + }: UserCameraDropdownSeparatorProps = { displayFunction: () => true }) { + this.displayFunction = displayFunction; this.type = UserCameraDropdownItemType.SEPARATOR; } diff --git a/src/extensible-areas/user-camera-dropdown-item/types.ts b/src/extensible-areas/user-camera-dropdown-item/types.ts index d08e6398..a69a6dfe 100644 --- a/src/extensible-areas/user-camera-dropdown-item/types.ts +++ b/src/extensible-areas/user-camera-dropdown-item/types.ts @@ -1,5 +1,15 @@ import { PluginProvidedUiItemDescriptor } from '../base'; +export interface UserCameraDropdownCallbackFunctionsArguments { + streamId: string; + userId: string; +} + +export interface OnclickFunctionCallbackArguments + extends UserCameraDropdownCallbackFunctionsArguments{ + browserClickEvent: React.MouseEvent; +} + /** * User Camera Dropdown Item - The general user camera dropdown extensible area item * @@ -7,10 +17,16 @@ import { PluginProvidedUiItemDescriptor } from '../base'; * This dropdown is located on the bottom left corner of the user webcam area */ export interface UserCameraDropdownInterface extends PluginProvidedUiItemDescriptor{ + displayFunction?: (args: UserCameraDropdownCallbackFunctionsArguments) => boolean; +} + +export interface UserCameraDropdownSeparatorProps { + displayFunction?: (args: UserCameraDropdownCallbackFunctionsArguments) => boolean; } export interface UserCameraDropdownOptionProps { label: string; icon: string; - onClick: () => void; + onClick: (args: OnclickFunctionCallbackArguments) => void; + displayFunction?: (args: UserCameraDropdownCallbackFunctionsArguments) => boolean; }