From 9f9b8be0f4debc28ff0bb7199ae0e1f4534024b2 Mon Sep 17 00:00:00 2001 From: Jeremy Schomery Date: Wed, 3 May 2017 16:51:15 +0430 Subject: [PATCH] scheduler implementation part/1 --- _locales/en/messages.json | 9 +++ background.js | 6 +- manage.css | 27 +++++++++ manage.html | 10 ++++ manifest.json | 6 +- schedule/core.js | 121 ++++++++++++++++++++++++++++++++++++++ schedule/ui.js | 61 +++++++++++++++++++ 7 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 schedule/core.js create mode 100644 schedule/ui.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 32fc1343..8a89754f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -706,5 +706,14 @@ }, "optionsCheck": { "message": "Update styles" + }, + "scheduleButton": { + "message": "Schedule" + }, + "scheduleButtonActive": { + "message": "Scheduled" + }, + "scheduleMSG": { + "message": "Either clear both start and end values to disable the schedule or set both to enable it" } } diff --git a/background.js b/background.js index 2f70e159..ec16d00d 100644 --- a/background.js +++ b/background.js @@ -1,4 +1,4 @@ -/* global dbExec, getStyles, saveStyle */ +/* global dbExec, getStyles, saveStyle, schedule, download */ 'use strict'; // eslint-disable-next-line no-var @@ -278,5 +278,9 @@ function onRuntimeMessage(request, sender, sendResponse) { .then(sendResponse) .catch(() => sendResponse(null)); return KEEP_CHANNEL_OPEN; + case 'schedule': + schedule.entry(request) + .then(sendResponse) + .catch(e => sendResponse(e)); } } diff --git a/manage.css b/manage.css index f576411e..7ede6f87 100644 --- a/manage.css +++ b/manage.css @@ -145,6 +145,33 @@ a:hover { margin-left: 1ex; } +.schedule { + position: relative; + opacity: 0.5; +} +.schedule:hover { + opacity: 1; +} +.schedule[data-edit=true], +.schedule[data-active=true] { + opacity: 1; +} +.schedule>input { + margin-right: 5px; +} +.schedule>div { + position: absolute; + right: 10px; + top: 25%; + white-space: nowrap; +} +.schedule[data-edit=false]>div { + display: none; +} +.schedule[data-edit=true]>input { + display: none; +} + summary { font-weight: bold; cursor: pointer; diff --git a/manage.html b/manage.html index eb405e0a..87d455db 100644 --- a/manage.html +++ b/manage.html @@ -57,6 +57,14 @@
... +
+ +
+ + - + +
+
@@ -218,6 +226,8 @@ + + diff --git a/manifest.json b/manifest.json index 5c085f36..f95c773f 100644 --- a/manifest.json +++ b/manifest.json @@ -18,8 +18,12 @@ "storage", "" ], + "optional_permissions": [ + "idle", + "alarms" + ], "background": { - "scripts": ["messaging.js", "storage.js", "prefs.js", "background.js", "update.js"] + "scripts": ["messaging.js", "storage.js", "prefs.js", "schedule/core.js" ,"background.js", "update.js"] }, "commands": { "openManage": { diff --git a/schedule/core.js b/schedule/core.js new file mode 100644 index 00000000..45d1d646 --- /dev/null +++ b/schedule/core.js @@ -0,0 +1,121 @@ +/* globals getStylesSafe, saveStyleSafe, BG */ +'use strict'; + +var SCHEDULE_PREFIX = 'schedule'; + +var schedule = {}; + +schedule.prefs = { + get (name, callback) { + chrome.storage.local.get(name, callback); + }, + getAll (callback) { + schedule.prefs.get(null, prefs => { + callback( + Object.keys(prefs).filter(n => n.startsWith(SCHEDULE_PREFIX + '.')) + .map(n => [n, prefs[n]]) + ); + }); + }, + set (name, value, callback = () => {}) { + chrome.storage.local.set({ + [name]: value + }, callback); + }, + remove (name) { + chrome.storage.local.remove(name); + }, + subscribe (callback) { + chrome.storage.onChanged.addListener(prefs => { + Object.keys(prefs) + .filter(n => prefs[n].newValue) + .forEach(n => callback(n, prefs[n].newValue)); + }); + } +}; + +schedule.entry = request => { + console.error('schedule.entry', request); + return new Promise((resolve, reject) => { + chrome.permissions.request({ + permissions: ['idle', 'alarms'] + }, (granted) => { + if (granted) { + schedule.prefs.set(SCHEDULE_PREFIX + '.' + request.id, { + id: request.id, + start: request.start, + end: request.end, + enabled: request.enabled + }); + resolve(); + } + else { + reject(new Error('Required permissions are not granted')); + } + }); + }); +}; + +schedule.execute = (name, request) => { + console.error('schedule.execute', name, request); + chrome.alarms.clear(name, () => { + if (request.enabled) { + const now = new Date(); + let start = new Date(now.toDateString() + ' ' + request.start).getTime() - now; + let end = new Date(now.toDateString() + ' ' + request.end).getTime() - now; + console.error('next alarm is set for', request.id); + chrome.alarms.create(name, { + when: now.getTime() + Math.min( + start < 0 ? start + 24 * 60 * 60 * 1000 : start, + end < 0 ? end + 24 * 60 * 60 * 1000 : end + ) + }); + getStylesSafe({id: request.id}).then(([style]) => { + if (style) { + const enabled = start <= 0 && end > 0; + console.error('Changing state', enabled, style.id); + + saveStyleSafe({ + id: request.id, + enabled + }); + } + else { + // clear schedule if style is not found + console.error('removing since stlye is not found', request); + schedule.execute(name, Object.assign( + request, {enabled: false} + )); + } + }); + } + else { + console.error('removing pref', name); + schedule.prefs.remove(name); + } + }); +}; + +// background only +if (BG === window) { + schedule.prefs.subscribe((name, pref) => name.startsWith(SCHEDULE_PREFIX + '.') && schedule.execute(name, pref)); + + chrome.alarms.onAlarm.addListener(({name}) => { + schedule.prefs.get(name, prefs => { + if (prefs[name]) { + schedule.execute(name, prefs[name]); + } + }); + }); + + (function (callback) { + chrome.idle.onStateChanged.addListener(state => { + if (state === 'active') { + callback(); + } + }); + window.setTimeout(callback); + })(function () { + schedule.prefs.getAll(prefs => prefs.forEach(a => schedule.execute(...a))); + }); +} diff --git a/schedule/ui.js b/schedule/ui.js new file mode 100644 index 00000000..1bee241b --- /dev/null +++ b/schedule/ui.js @@ -0,0 +1,61 @@ +/* global t, schedule */ +'use strict'; + +document.addEventListener('click', e => { + const target = e.target; + let parent; + // hide schedule panel + function observe (e) { + if (!parent.contains(e.target)) { + const [start, end] = parent.querySelectorAll('input[type=time]'); + const id = target.closest('.entry').id.replace('style-', ''); + switch ([start.value, end.value].filter(v => v).length) { + case 0: + chrome.runtime.sendMessage({ + method: 'schedule', + enabled: false, + id + }); + break; + case 1: // when only start or end value is set; display an alert + window.alert(t('scheduleMSG')); + [start, end].filter(o => !o.value).forEach(o => o.focus()); + return; + default: + chrome.runtime.sendMessage({ + method: 'schedule', + enabled: true, + id, + start: start.value, + end: end.value + }); + } + + document.removeEventListener('click', observe); + parent.dataset.edit = false; + } + } + // display schedule panel + if (target.dataset.cmd === 'schedule') { + parent = target.closest('div'); + parent.dataset.edit = true; + document.addEventListener('click', observe); + } +}); + +function test () { + schedule.prefs.getAll(prefs => { + prefs.forEach(([name, pref]) => { + const parent = document.querySelector(`[id="style-${pref.id}"] .schedule`); + if (parent) { + parent.dataset.active = true; + parent.querySelector('input[type=button]').value = t('scheduleButtonActive'); + const [start, end] = parent.querySelectorAll('input[type=time'); + start.value = pref.start; + end.value = pref.end; + } + }); + }); +} + +window.setTimeout(test, 1000);