Skip to content

Commit

Permalink
Showing 12 changed files with 231 additions and 3 deletions.
Binary file added assets/carbonio-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/notification.mp3
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -19,7 +19,8 @@
"lint": "eslint --ext .js,.jsx,.ts,.tsx --resolve-plugins-relative-to node_modules/@zextras/zapp-config src",
"build": "sdk build",
"deploy": "sdk deploy",
"start": "sdk watch"
"start": "sdk watch",
"deploy-on-module": "npm pack && rm -rf $PKG_PATH/node_modules/@zextras/carbonio-shell-ui/* && tar -xf zextras-carbonio-shell-ui-$npm_package_version.tgz -C $PKG_PATH/node_modules/@zextras/carbonio-shell-ui/ --strip-components 1"
},
"files": [
"dist/",
4 changes: 4 additions & 0 deletions src/boot/app/app-loader-functions.ts
Original file line number Diff line number Diff line change
@@ -82,6 +82,7 @@ import { getTags, useTags } from '../../store/tags';
import { useNotify, useRefresh } from '../../store/network';
import { changeTagColor, createTag, deleteTag, renameTag } from '../../network/tags';
import { runSearch } from '../../search/run-search';
import { getNotificationManager } from '../../notification/NotificationManager';

// eslint-disable-next-line @typescript-eslint/ban-types
export const getAppFunctions = (pkg: CarbonioModule): Record<string, Function> => ({
@@ -162,6 +163,9 @@ export const getAppFunctions = (pkg: CarbonioModule): Record<string, Function> =
renameTag,
changeTagColor,
deleteTag,
// NOTIFICATION
getNotificationManager,

// STUFF
runSearch,
useIsMobile,
2 changes: 2 additions & 0 deletions src/boot/bootstrapper.tsx
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ import StoreFactory from '../redux/store-factory';
import { unloadAllApps } from './app/load-apps';
import { registerDefaultViews } from './app/default-views';
import { useBridge } from '../store/context-bridge';
import { NotificationPermissionChecker } from '../notification/NotificationPermissionChecker';

const DefaultViewsRegister: FC = () => {
const [t] = useTranslation();
@@ -51,6 +52,7 @@ const Bootstrapper: FC = () => {
<BootstrapperContextProvider i18nFactory={i18nFactory} storeFactory={storeFactory}>
<TBridge i18nFactory={i18nFactory} />
<DefaultViewsRegister />
<NotificationPermissionChecker />
<BootstrapperRouter />
</BootstrapperContextProvider>
</ModalManager>
2 changes: 2 additions & 0 deletions src/globals.d.ts
Original file line number Diff line number Diff line change
@@ -38,3 +38,5 @@ declare const cliSettings: cliSettingsNamespace | undefined;
declare module '@zextras/carbonio-design-system';
declare module 'tinymce';
declare module '*.svg';
declare module '*.mp3';
declare module '*.png';
162 changes: 162 additions & 0 deletions src/notification/NotificationManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2022 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { debounce, noop } from 'lodash';
import {
NotificationConfig,
PopupNotificationConfig,
AudioNotificationConfig,
INotificationManager
} from '../../types/notification';
import defaultAudio from '../../assets/notification.mp3';
import defaultIcon from '../../assets/carbonio-logo.png';

const PopupNotificationDefaultConfig = {
title: 'Carbonio client',
icon: defaultIcon,
vibrate: [200, 100, 200]
};

const AudioNotificationDefaultConfig = {
sound: defaultAudio
};

const NotificationDefaultConfig: NotificationConfig = {
...PopupNotificationDefaultConfig,
...AudioNotificationDefaultConfig,
showPopup: true,
playSound: false
};

/**
* The main goals of the NotificationManager are:
* - to provide a single and rich implementation for all the Carbonio modules,
* reducing the boilerplate code needed to send a notification to the user
* - to optimize the audio notifications avoiding to spam the same sound file
* in a short period of time
* - to act as a collector for all the notification (for possible future
* implementations)
*
* In order to reduce the effort needed to send a notification the class
* provided a set of default values/assets (e.g. icon, sound, title, ...)
*
* The class is provided as a singleton
*/
export class NotificationManager implements INotificationManager {
private static instance: NotificationManager;

/**
* Minimum time (ms) to wait before the same audio file will be played
* @private
*/
private static DEBOUNCE_TIME = 1000;

/**
* Map of functions to play a specific audio file
* @private
*/
private functions = new Map<string, () => void>();

/**
* Gets or creates the (debounced) function to play the audio file
* @param sound - relative path to the audio file to play
*/
private getAudioFileFunction = (sound: string): (() => void) => {
if (!this.functions.has(sound)) {
this.functions.set(
sound,
debounce(() => {
new Audio(sound).play().then();
this.functions.delete(sound);
}, NotificationManager.DEBOUNCE_TIME)
);
}
const result = this.functions.get(sound);
return result ?? noop;
};

/**
* Executes the debounced function to play the audio file
* @param config - Configuration for the audio notification. In case of
* missing properties default values are used
*/
public playSound = (config: AudioNotificationConfig): void => {
const defConfig = {
...AudioNotificationDefaultConfig,
...config
};
if (!defConfig.sound) {
return;
}

this.getAudioFileFunction(defConfig.sound)();
};

/**
* Shows a popup notification
* @param config - Configuration for the popup notification. In case of
* missing properties default values are used
*/
public showPopup = (config: PopupNotificationConfig): void => {
const defConfig = {
...PopupNotificationDefaultConfig,
...config
};

const n = new Notification(defConfig.title, {
body: defConfig.message,
vibrate: defConfig.vibrate,
icon: defConfig.icon,
tag: defConfig?.tag
});

if (defConfig.onClick) {
n.addEventListener('click', defConfig.onClick);
}
};

/**
* Sends a popup/audio notification to the user
* @param config - Configuration for the notification. In case of
* missing properties default values are used
*/
public notify = (config: NotificationConfig): void => {
const defConfig = {
...NotificationDefaultConfig,
...config
};

if (defConfig?.showPopup) {
this.showPopup(defConfig);
}

if (defConfig?.playSound) {
this.playSound(defConfig);
}
};

/**
* Sends multiple notifications
* @param config - Array of configurations for the notifications. In case of
* missing properties default values are used
*/
public multipleNotify = (config: NotificationConfig[]): void => {
config.forEach((conf) => this.notify(conf));
};

/**
* Return the singleton instance
*/
public static getInstance(): NotificationManager {
if (!NotificationManager.instance) {
NotificationManager.instance = new NotificationManager();
}

return NotificationManager.instance;
}
}

export const getNotificationManager = (): INotificationManager => NotificationManager.getInstance();
19 changes: 19 additions & 0 deletions src/notification/NotificationPermissionChecker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2022 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { FC, ReactElement, useEffect } from 'react';

export const NotificationPermissionChecker: FC = (): ReactElement => {
useEffect(() => {
if (!('Notification' in window)) {
// eslint-disable-next-line no-console
console.warn('This browser does not support desktop notifications');
} else {
Notification.requestPermission();
}
}, []);

return <></>;
};
6 changes: 4 additions & 2 deletions src/shell/shell-header.tsx
Original file line number Diff line number Diff line change
@@ -4,15 +4,17 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React, { FC } from 'react';
import React, { FC, useState } from 'react';
import {
Container,
IconButton,
Padding,
Responsive,
useScreenMode,
Catcher
Catcher,
Button
} from '@zextras/carbonio-design-system';
import { times } from 'lodash';
import Logo from '../svg/carbonio.svg';
import { SearchBar } from '../search/search-bar';
import { CreationButton } from './creation-button';
4 changes: 4 additions & 0 deletions types/exports/index.d.ts
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ import { HistoryParams, ShellModes, AccordionFolder } from '../misc';
import { Tag, Tags } from '../tags';
import { Folder, Folders } from '../folder';
import { QueryChip } from '../search';
import { INotificationManager } from '../notification';

export const getBridgedFunctions: () => {
addBoard: (path: string, context?: unknown | { app: string }) => void;
@@ -226,6 +227,9 @@ export const useFoldersAccordionByView: (
itemProps?: (item: AccordionFolder) => Record<string, any>
) => Array<AccordionFolder>;

// NOTIFICATION
export const getNotificationManager: () => INotificationManager;

// Run Search
export const runSearch: (query: Array<QueryChip>, module: string) => void;

1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ export * from './network';
export * from './exports';
export * from './account';
export * from './apps';
export * from './notification';
export * from './integrations';
export * from './theme';
export * from './search';
31 changes: 31 additions & 0 deletions types/notification/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2022 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

export type PopupNotificationConfig = {
title?: string;
message?: string;
icon?: string;
vibrate?: Array<number>;
tag?: string;
onClick?: (event: Event) => void;
};

export type AudioNotificationConfig = {
sound?: string;
};

export type NotificationConfig = {
showPopup?: boolean;
playSound?: boolean;
} & PopupNotificationConfig &
AudioNotificationConfig;

export interface INotificationManager {
playSound: (config: AudioNotificationConfig) => void;
showPopup: (config: PopupNotificationConfig) => void;
notify: (config: NotificationConfig) => void;
multipleNotify: (config: NotificationConfig[]) => void;
}

0 comments on commit 5a09697

Please sign in to comment.