Skip to content

Add feature to enable Time based DND Mode #896

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 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions app/renderer/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,70 @@ body {
flex-direction: column;
}


/* DND Menu */

@keyframes fading {
0% {
opacity: 0;
}
20% {
opacity: 1;
}
80% {
opacity: 1;
}
100% {
opacity: 0;
}
}

.toast {
font-family: Verdana;
border-radius: 10px;
font-size: 13.5px;
left: 45%;
top: 10px;
position: fixed;
background-color: rgba(200, 200, 200, 1.000);
padding: 15px 30px 15px 30px;
color: rgb(0, 0, 0);
animation: fading 4s;
}

.dnd-menu {
font-family: Verdana;
font-size: 13px;
left: 60px;
bottom: 10px;
position: fixed;
background-color: transparent;
padding: 10px;
width: 150px;
min-height: 150px;
color: rgb(255, 255, 255);
list-style-type: none;
}

.dnd-time-btn {
background-color: rgba(34, 44, 49, 1.000);
border-style: solid;
border-color: rgba(255, 255, 255, 1.000);
border-width: 1.5px;
border-radius: 20px;
padding: 10px;
text-align: center;
margin: 2px 0;
transition: .1s;
}

.dnd-time-btn:hover {
background-color: rgba(59, 76, 85, 1.000);
transition: .2s;
cursor: pointer;
}


.material-icons {
font-family: 'Material Icons';
font-weight: normal;
Expand Down
20 changes: 18 additions & 2 deletions app/renderer/js/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,23 @@ class ServerManagerView {

initLeftSidebarEvents(): void {
this.$dndButton.addEventListener('click', () => {
const dndUtil = DNDUtil.toggle();
ipcRenderer.send('forward-message', 'toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
const dnd = ConfigUtil.getConfigItem('dnd', false);

// Case: DND is turned on and user clicks DND button -> switch it off
if (dnd) {
const dndUtil = DNDUtil.toggle();
ipcRenderer.send('forward-message', 'toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
} // eslint-disable-line @typescript-eslint/brace-style

// Case: DND is turned off, there is no DND menu and user clicks DND button -> create menu and schedule
else if (document.querySelector('.dnd-menu') === null) {
DNDUtil.scheduleDND();
} // eslint-disable-line @typescript-eslint/brace-style

// Case: DND is turned off, DND menu is already created and user clicks DND button -> remove the menu
else {
document.querySelector('.dnd-menu').remove();
}
});
this.$reloadButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.reload();
Expand All @@ -428,6 +443,7 @@ class ServerManagerView {
}

initDNDButton(): void {
DNDUtil.setDNDTimer();
const dnd = ConfigUtil.getConfigItem('dnd', false);
this.toggleDNDButton(dnd);
}
Expand Down
136 changes: 135 additions & 1 deletion app/renderer/js/utils/dnd-util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as ConfigUtil from './config-util';

import {ipcRenderer} from 'electron';
import schedule from 'node-schedule';
type SettingName = 'showNotification' | 'silent' | 'flashTaskbarOnMessage';

export interface DNDSettings {
Expand All @@ -13,6 +14,9 @@ interface Toggle {
newSettings: DNDSettings;
}

/* A node-schedule job instance accepts time parameter and schedules the operations provided at that time. */
let job: schedule.Job = null;

export function toggle(): Toggle {
const dnd = !ConfigUtil.getConfigItem('dnd', false);
const dndSettingList: SettingName[] = ['showNotification', 'silent'];
Expand All @@ -37,6 +41,10 @@ export function toggle(): Toggle {
ConfigUtil.setConfigItem('dndPreviousSettings', oldSettings);
} else {
newSettings = ConfigUtil.getConfigItem('dndPreviousSettings');
ConfigUtil.setConfigItem('dndEndTime', null);
if (job !== null) {
job.cancel();
}
}

for (const settingName of dndSettingList) {
Expand All @@ -46,3 +54,129 @@ export function toggle(): Toggle {
ConfigUtil.setConfigItem('dnd', dnd);
return {dnd, newSettings};
}

/* Driver function to be called from main.ts
Create the DND menu and closed it when focussed out. */
export const scheduleDND = (): void => {
const actionsContainer = document.querySelector('#actions-container');
const dndMenuExists = Boolean(document.querySelectorAll('.dnd-menu').length);
if (!dndMenuExists) {
const dndMenu = document.createElement('ul');
dndMenu.className = 'dnd-menu';
const dndOptions: {[key: string]: string} = {
'30 Minutes': 'thirty_min',
'1 Hour': 'one_hr',
'6 Hours': 'six_hr',
'12 Hours': 'twelve_hr',
'Until I Resume': 'custom'
};

Object.keys(dndOptions).forEach(key => {
const opt = document.createElement('li');
opt.className = 'dnd-time-btn';
opt.append(key);
opt.id = dndOptions[key];
opt.addEventListener('click', () => {
configureDND(opt.id, dndMenu);
}, false);
dndMenu.append(opt);
});

actionsContainer.append(dndMenu);

const dndMenuFocusOut = (event: Event) => {
if ((event.target as HTMLLIElement).innerHTML !== 'notifications') {
dndMenu.remove();
document.removeEventListener('click', dndMenuFocusOut);
}
};

document.addEventListener('keydown', event => {
if (event.key === 'Escape') {
dndMenu.remove();
document.removeEventListener('click', dndMenuFocusOut);
}
});

document.addEventListener('click', dndMenuFocusOut);
}
};

/* All the functionality required by scheduleDND()
Configure DND as per user's selected option and pops a toast showing dnd switch off time. */
const configureDND = (dndTime: string, dndMenu: HTMLUListElement): void => {
let dndOffTime = new Date();
const optionElement = document.querySelector('#' + dndTime);
dndMenu.style.height = '75px';
optionElement.className = 'dnd-options-btn';
switch (dndTime) {
case 'thirty_min':
dndOffTime.setMinutes(dndOffTime.getMinutes() + 30);
break;
case 'one_hr':
dndOffTime.setMinutes(dndOffTime.getMinutes() + 60);
break;
case 'six_hr':
dndOffTime.setMinutes(dndOffTime.getMinutes() + 360);
break;
case 'twelve_hr':
dndOffTime.setMinutes(dndOffTime.getMinutes() + 720);
break;
default:
dndOffTime = null;
break;
}

ConfigUtil.setConfigItem('dndEndTime', dndOffTime);

dndMenu.remove();
showDNDTimeLeft();
setDNDTimer();
const state = toggle();
ipcRenderer.send('forward-message', 'toggle-dnd', state.dnd, state.newSettings);
};

/* Handle DND state when configuring DND and when the application starts. */
export const setDNDTimer = (): void => {
const dbTime = ConfigUtil.getConfigItem('dndEndTime');
if (dbTime !== null) {
const time = new Date(dbTime);

// Handle the case when user closes the app before switch off time and stars it after the time has elapsed.
if (time < new Date()) {
toggle();
return;
}

if (job !== null) { // Prevent scheduling of stale job
job.cancel();
}

job = schedule.scheduleJob(time, () => { // Perform the following operations at parameter `time`
ConfigUtil.setConfigItem('dndEndTime', null);
const state = toggle();
ipcRenderer.send('forward-message', 'toggle-dnd', state.dnd, state.newSettings);
showToast('DND has ended');
});
}
};

/* Show a toast with the clock time when DND will go off. */
const showDNDTimeLeft = (): void => {
const check = ConfigUtil.getConfigItem('dndEndTime');
if (check !== null) {
const dbTime = new Date(check);
const timeLeft = `${dbTime.getHours()} : ${(dbTime.getMinutes() < 10 ? (`0${dbTime.getMinutes()}`) : dbTime.getMinutes())}`;
showToast(`DND goes off at ${timeLeft}`);
}
};

/* Toast - can be exported for future use */
const showToast = (t: string): void => {
const actionsContainer = document.querySelector('#actions-container');
const toast = document.createElement('p');
toast.className = 'toast';
toast.append(t);
actionsContainer.append(toast);
setTimeout(() => toast.remove(), 4000);
};
58 changes: 58 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
"dependencies": {
"@electron-elements/send-feedback": "^2.0.3",
"@sentry/electron": "^1.3.0",
"@types/node-schedule": "^1.3.0",
"adm-zip": "^0.4.14",
"auto-launch": "^5.0.5",
"backoff": "^2.5.0",
Expand All @@ -160,6 +161,7 @@
"fs-extra": "^9.0.0",
"i18n": "^0.9.1",
"node-json-db": "^1.1.0",
"node-schedule": "^1.3.2",
"request": "^2.88.2",
"rxjs": "^5.5.12",
"semver": "^7.3.2"
Expand Down