Skip to content

feat: separate browser notification from ping sound in plugin settings #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: v0.0.x
Choose a base branch
from
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,43 @@ Down below, we list all the possible configurations this plugin supports, and th
```yaml
- name: PickRandomUserPlugin
settings:
pingSoundEnabled: false
pingSoundEnabled: true
pingSoundUrl: resources/sounds/doorbell.mp3
browserNotificationEnabled: true
pickedUserTimeWindow: 10 # seconds
```

| Name | Description | Default |
|------------------------|--------------------------------------|-----------------------------|
| `pingSoundEnabled` | Whether the ping sound is enabled | `false` |
| `pingSoundEnabled` | Flag that decides whether the ping sound is enabled for picked user | `true` |
| `pingSoundUrl` | URL of the ping sound file | `resources/sounds/doorbell.mp3` |
| `browserNotificationEnabled` | Flag that decides whether to send browser notification when user is picked | `false` |
| `pickedUserTimeWindow` | Time window to consider a user as recently picked (users that join after that time will not see the last modal) | `30` |


### Notification/Ping sound
### Notification

By default, no ping sound is played for the randomly picked user. To activate this feature, one must add the following configurations in their `/etc/bigbluebutton/bbb-html5.yml` file.
By default, browser notification when user is randomly picked is not enabled. To enable it, add the following settings in the `/etc/bigbluebutton/bbb-html5.yml` file:

```yaml
public:
# ...
plugins:
- name: PickRandomUserPlugin
settings:
browserNotificationEnabled: false
```

### Ping sound

By default, ping sound is played for the randomly picked user. To remove this feature, one must add the following configurations in their `/etc/bigbluebutton/bbb-html5.yml` file.

So within that file and in `public.plugins` add the following configurations:

```yaml
- name: PickRandomUserPlugin
settings:
pingSoundEnabled: true
pingSoundUrl: resources/sounds/doorbell.mp3
pingSoundEnabled: false
```

The result yaml will look like:
Expand All @@ -59,8 +73,7 @@ public:
plugins:
- name: PickRandomUserPlugin
settings:
pingSoundEnabled: true # Enables the ping sound for this plugin true/false
pingSoundUrl: resources/sounds/doorbell.mp3 # This is the default and is not mandatory
pingSoundEnabled: false
```

Just a minor comment: This relative URLs can only be configured if the server on which BBB is running is not a cluster setup. If that's your case, you'll need to put the whole URL into the configuration. It's also worth mentioning that the default `pingSoundUrl` will work in cluser setups, so no worries on that.
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"localesBaseUrl": "locales",
"dataChannels": [
{
"name": "modalInformationFromPresenter",
"name": "filterOptions",
"pushPermission": ["presenter"],
"replaceOrDeletePermission": ["creator", "moderator"]
},
Expand Down
2 changes: 0 additions & 2 deletions public/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
"pickRandomUserPlugin.modal.pickedUserView.backButton.label": "zurück",
"pickRandomUserPlugin.modal.pickedUserView.avatarImage.alternativeText": "Avatar von Teilnehmer {0}",
"pickRandomUserPlugin.modal.presenterView.optionSection.title": "Optionen",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipModeratorsLabel": "Überspringe Moderatoren",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipPresenterLabel": "Überspringe Präsentator",
"pickRandomUserPlugin.modal.presenterView.optionSection.includePickedUsersLabel": "Wähle bereits gewählte Teilnehmer",
"pickRandomUserPlugin.modal.presenterView.availableSection.title": "Zur Auswahl stehen",
"pickRandomUserPlugin.modal.presenterView.availableSection.userLabel": "Teilnehmer",
Expand Down
4 changes: 2 additions & 2 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"pickRandomUserPlugin.modal.pickedUserView.backButton.label": "back",
"pickRandomUserPlugin.modal.pickedUserView.avatarImage.alternativeText": "Avatar image of user {0}",
"pickRandomUserPlugin.modal.presenterView.optionSection.title": "Options",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipModeratorsLabel": "Skip moderators",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipPresenterLabel": "Skip presenter",
"pickRandomUserPlugin.modal.presenterView.optionSection.includeModeratorsLabel": "Include moderators",
"pickRandomUserPlugin.modal.presenterView.optionSection.includePresenterLabel": "Include presenter",
"pickRandomUserPlugin.modal.presenterView.optionSection.includePickedUsersLabel": "Include already picked user",
"pickRandomUserPlugin.modal.presenterView.availableSection.title": "Available for selection",
"pickRandomUserPlugin.modal.presenterView.availableSection.userLabel": "user",
Expand Down
2 changes: 0 additions & 2 deletions public/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
"pickRandomUserPlugin.modal.pickedUserView.title.randomUserPicked": "Participant sélectionné au hasard",
"pickRandomUserPlugin.modal.pickedUserView.backButton.label": "retour",
"pickRandomUserPlugin.modal.presenterView.optionSection.title": "Options",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipModeratorsLabel": "Ignorer les modérateurs",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipPresenterLabel": "Ignorer le présentateur",
"pickRandomUserPlugin.modal.presenterView.optionSection.includePickedUsersLabel": "Inclure un participant déjà sélectionné",
"pickRandomUserPlugin.modal.presenterView.availableSection.title": "Disponible pour la sélection",
"pickRandomUserPlugin.modal.presenterView.availableSection.userLabel": "participant",
Expand Down
2 changes: 0 additions & 2 deletions public/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
"pickRandomUserPlugin.modal.pickedUserView.title.randomUserPicked": "Utente scelto a caso",
"pickRandomUserPlugin.modal.pickedUserView.backButton.label": "indietro",
"pickRandomUserPlugin.modal.presenterView.optionSection.title": "Opzioni",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipModeratorsLabel": "Salta i moderatori",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipPresenterLabel": "Salta il presentatore",
"pickRandomUserPlugin.modal.presenterView.optionSection.includePickedUsersLabel": "Includi l'utente già selezionato",
"pickRandomUserPlugin.modal.presenterView.availableSection.title": "Disponibile per la selezione",
"pickRandomUserPlugin.modal.presenterView.availableSection.userLabel": "utente",
Expand Down
2 changes: 0 additions & 2 deletions public/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
"pickRandomUserPlugin.modal.pickedUserView.backButton.label": "戻る",
"pickRandomUserPlugin.modal.pickedUserView.avatarImage.alternativeText": "ユーザー {0} のアバター画像",
"pickRandomUserPlugin.modal.presenterView.optionSection.title": "設定",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipModeratorsLabel": "司会者を含めない",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipPresenterLabel": "発表者を含めない",
"pickRandomUserPlugin.modal.presenterView.optionSection.includePickedUsersLabel": "既に指名した人を含める",
"pickRandomUserPlugin.modal.presenterView.availableSection.title": "指名可能な人",
"pickRandomUserPlugin.modal.presenterView.availableSection.userLabel": "人のユーザー",
Expand Down
2 changes: 0 additions & 2 deletions public/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
"pickRandomUserPlugin.modal.pickedUserView.backButton.label": "voltar",
"pickRandomUserPlugin.modal.pickedUserView.avatarImage.alternativeText": "Iamgem avatar do usuário {0}",
"pickRandomUserPlugin.modal.presenterView.optionSection.title": "Opções",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipModeratorsLabel": "Ignorar moderadores",
"pickRandomUserPlugin.modal.presenterView.optionSection.skipPresenterLabel": "Ignorar apresentador",
"pickRandomUserPlugin.modal.presenterView.optionSection.includePickedUsersLabel": "Incluir usuário já selecionado",
"pickRandomUserPlugin.modal.presenterView.availableSection.title": "Disponíveis para seleção",
"pickRandomUserPlugin.modal.presenterView.availableSection.userLabel": "usuário",
Expand Down
5 changes: 5 additions & 0 deletions src/commons/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const PICKED_USER_TIME_WINDOW = 10; // seconds

export const TIMEOUT_CLOSE_NOTIFICATION = 5000;

export const DEFAULT_PING_SOUND_URL = 'resources/sounds/doorbell.mp3';
29 changes: 29 additions & 0 deletions src/commons/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PluginApi } from 'bigbluebutton-html-plugin-sdk';
import { createIntl, createIntlCache } from 'react-intl';

const LOCALE_REQUEST_OBJECT = (!process.env.NODE_ENV || process.env.NODE_ENV === 'development')
? {
headers: {
'ngrok-skip-browser-warning': 'any',
},
} : undefined;

export const useGetInternationalization = (pluginApi: PluginApi) => {
const {
messages: localeMessages,
currentLocale,
loading: localeMessagesLoading,
} = pluginApi.useLocaleMessages!(LOCALE_REQUEST_OBJECT);

const cache = createIntlCache();
const intl = (!localeMessagesLoading && localeMessages) ? createIntl({
locale: currentLocale,
messages: localeMessages,
fallbackOnEmptyString: true,
}, cache) : null;

return {
intl,
localeMessagesLoading,
};
};
6 changes: 6 additions & 0 deletions src/commons/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface PickRandomUserSettings {
pingSoundEnabled: boolean;
pingSoundUrl: string;
browserNotificationEnabled: boolean;
pickedUserTimeWindow: number;
}
4 changes: 2 additions & 2 deletions src/utils/utils.ts → src/commons/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { PickedUserSeenEntryDataChannel } from '../components/pick-random-user/t
* @param pickedUserId userId from the picked user
* @returns boolean indicating if the current user has seen the picked-user
*/
const hasCurrentUserSeenPickedUser = (
export const hasCurrentUserSeenPickedUser = (
pickedUserSeenEntries: GraphqlResponseWrapper<
DataChannelEntryResponseType<PickedUserSeenEntryDataChannel>[]>,
currentUserId: string,
Expand All @@ -20,4 +20,4 @@ const hasCurrentUserSeenPickedUser = (
&& view.payloadJson.seenByUserId === currentUserId
&& view.payloadJson.pickedUserId === pickedUserId);

export default hasCurrentUserSeenPickedUser;
export const isNumber = (obj: unknown): boolean => obj && typeof obj && !Number.isNaN(obj);
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const intlMessages = defineMessages({
function ActionButtonDropdownManager(props: ActionButtonDropdownManagerProps): React.ReactNode {
const {
intl,
pickedUserWithEntryId,
currentPickedUser,
currentUser,
pluginApi,
setShowModal,
Expand All @@ -42,7 +42,7 @@ function ActionButtonDropdownManager(props: ActionButtonDropdownManagerProps): R
},
}),
]);
} else if (!currentUser?.presenter && pickedUserWithEntryId) {
} else if (!currentUser?.presenter && currentPickedUser) {
pluginApi.setActionButtonDropdownItems([
new ActionButtonDropdownSeparator(),
new ActionButtonDropdownOption({
Expand All @@ -58,7 +58,7 @@ function ActionButtonDropdownManager(props: ActionButtonDropdownManagerProps): R
} else {
pluginApi.setActionButtonDropdownItems([]);
}
}, [currentUserInfo, pickedUserWithEntryId, intl]);
}, [currentUserInfo, currentPickedUser, intl]);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PickedUserWithEntryId } from '../../pick-random-user/types';

export interface ActionButtonDropdownManagerProps {
intl: IntlShape
pickedUserWithEntryId: PickedUserWithEntryId;
currentPickedUser: PickedUserWithEntryId;
currentUser: CurrentUserData;
pluginApi: PluginApi;
setShowModal: React.Dispatch<React.SetStateAction<boolean>>;
Expand Down
87 changes: 16 additions & 71 deletions src/components/modal/component.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useEffect, useState } from 'react';
import * as React from 'react';
import { defineMessages } from 'react-intl';
import { pluginLogger } from 'bigbluebutton-html-plugin-sdk';
import * as Styled from './styles';
import { PickUserModalProps, WindowClientSettings } from './types';
import { PickUserModalProps } from './types';
import { PickedUserViewComponent } from './picked-user-view/component';
import { PresenterViewComponent } from './presenter-view/component';
import hasCurrentUserSeenPickedUser from '../../utils/utils';
import { useHandleCurrentUserNotification } from './hooks';

const intlMessages = defineMessages({
currentUserPicked: {
Expand All @@ -16,83 +15,37 @@ const intlMessages = defineMessages({
},
});

const TIMEOUT_CLOSE_NOTIFICATION = 5000;

declare const window: WindowClientSettings;

function notifyRandomlyPickedUser(message: string) {
if (!('Notification' in window)) {
pluginLogger.warn('This browser does not support notifications');
} else if (Notification.permission === 'granted') {
const notification = new Notification(message);
setTimeout(() => {
notification.close();
}, TIMEOUT_CLOSE_NOTIFICATION);
} else if (Notification.permission !== 'denied') {
pluginLogger.warn('Browser notification permission has been denied');
}
}

export function PickUserModal(props: PickUserModalProps) {
const {
pluginSettings,
isPluginSettingsLoading,
pickRandomUserSettings,
intl,
showModal,
handleCloseModal,
users,
pickedUserWithEntryId,
currentPickedUser,
handlePickRandomUser,
currentUser,
filterOutPresenter,
setFilterOutPresenter,
userFilterViewer,
setUserFilterViewer,
filterOutPickedUsers,
setFilterOutPickedUsers,
dataChannelPickedUsers,
deletionFunction,
dispatcherPickedUser,
pickedUserSeenEntries,
pushPickedUserSeen,
} = props;

const [showPresenterView, setShowPresenterView] = useState<boolean>(
currentUser?.presenter && !pickedUserWithEntryId,
currentUser?.presenter && !currentPickedUser,
);

const [userId, setUserId] = useState(currentUser?.userId || '');

useEffect(() => {
// Play audio when user is selected

const hasCurrentUserSeen = hasCurrentUserSeenPickedUser(
pickedUserSeenEntries,
userId,
pickedUserWithEntryId?.pickedUser?.userId,
);
const isPingSoundEnabled = !isPluginSettingsLoading && pluginSettings?.pingSoundEnabled;
if (isPingSoundEnabled && pickedUserWithEntryId
&& pickedUserWithEntryId?.pickedUser?.userId === userId
// Current user must not have seen this entry and data should be done loading
&& !hasCurrentUserSeen && !pickedUserSeenEntries?.loading) {
const { cdn, basename } = window.meetingClientSettings.public.app;
const host = cdn + basename;
const pingSoundUrl: string = pluginSettings?.pingSoundUrl
? String(pluginSettings?.pingSoundUrl)
: `${host}/resources/sounds/doorbell.mp3`;
const audio = new Audio(pingSoundUrl);
audio.play();
notifyRandomlyPickedUser(intl.formatMessage(intlMessages.currentUserPicked));
}
}, [userId, pickedUserWithEntryId, pickedUserSeenEntries]);
useHandleCurrentUserNotification(
currentUser,
pickedUserSeenEntries,
currentPickedUser,
pickRandomUserSettings,
intl.formatMessage(intlMessages.currentUserPicked),
);

useEffect(() => {
setShowPresenterView(currentUser?.presenter && !pickedUserWithEntryId);
if (userId === '') {
setUserId(currentUser.userId);
}
}, [currentUser, pickedUserWithEntryId]);
setShowPresenterView(currentUser?.presenter && !currentPickedUser);
}, [currentUser, currentPickedUser]);
return (
<Styled.PluginModal
overlayClassName="modalOverlay"
Expand All @@ -118,31 +71,23 @@ export function PickUserModal(props: PickUserModalProps) {
<PresenterViewComponent
{...{
intl,
filterOutPresenter,
setFilterOutPresenter,
userFilterViewer,
setUserFilterViewer,
filterOutPickedUsers,
setFilterOutPickedUsers,
deletionFunction,
handlePickRandomUser,
dataChannelPickedUsers,
pickedUserWithEntryId,
pickedUserWithEntryId: currentPickedUser,
users,
dispatcherPickedUser,
}}
/>
) : (
<PickedUserViewComponent
{...{
pickedUserSeenEntries,
pushPickedUserSeen,
pickedUserWithEntryId,
pickedUserWithEntryId: currentPickedUser,
intl,
currentUser,
showModal,
setShowPresenterView,
dispatcherPickedUser,
}}
/>
)
Expand Down
Loading