Compare commits
	
		
			4 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ec4e0a69f2 | ||
|  | 52ae781960 | ||
|  | c2f993db27 | ||
|  | 9f9b8be0f4 | 
|  | @ -706,5 +706,37 @@ | |||
|   }, | ||||
|   "optionsCheck": { | ||||
|     "message": "Update styles" | ||||
|   }, | ||||
|   "scheduleButton": { | ||||
|     "message": "Schedule" | ||||
|   }, | ||||
|   "scheduleButtonActive": { | ||||
|     "message": "Scheduled" | ||||
|   }, | ||||
|   "scheduleTitle": { | ||||
|     "message": "Add/Edit this schedule", | ||||
|     "description": "Title for the messageBox" | ||||
|   }, | ||||
|   "scheduleButtonRetry": { | ||||
|     "message": "Retry", | ||||
|     "description": "First button for the messageBox" | ||||
|   }, | ||||
|   "scheduleButtonGiveUp": { | ||||
|     "message": "I give up ", | ||||
|     "description": "Second button for the messageBox" | ||||
|   }, | ||||
|   "scheduleOneEntry": { | ||||
|     "message": "Either clear both start and end values to disable the schedule or set both values to enable it", | ||||
|     "description": "Description for the messageBox when only one time value is present" | ||||
|   }, | ||||
|   "scheduleEqualEntries": { | ||||
|     "message": "Start and end time values cannot be equal", | ||||
|     "description": "Description for the messageBox when both start and end time values are equal" | ||||
|   }, | ||||
|   "scheduleFrom": { | ||||
|     "message": "From:" | ||||
|   }, | ||||
|   "scheduleTo": { | ||||
|     "message": "To:" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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,10 @@ function onRuntimeMessage(request, sender, sendResponse) { | |||
|         .then(sendResponse) | ||||
|         .catch(() => sendResponse(null)); | ||||
|       return KEEP_CHANNEL_OPEN; | ||||
|     case 'schedule': | ||||
|       schedule.entry(request) | ||||
|         .then(() => sendResponse(true)) | ||||
|         .catch(() => sendResponse(false)); | ||||
|       return KEEP_CHANNEL_OPEN; | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										30
									
								
								manage.css
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								manage.css
									
									
									
									
									
								
							|  | @ -145,6 +145,36 @@ 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 span { | ||||
|   margin: 0 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; | ||||
|  |  | |||
							
								
								
									
										11
									
								
								manage.html
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								manage.html
									
									
									
									
									
								
							|  | @ -57,6 +57,15 @@ | |||
|         <div class="targets"></div> | ||||
|         <span class="expander">...</span> | ||||
|       </div> | ||||
|       <div class="schedule" data-edit="false"> | ||||
|         <input type="button" i18n-value="scheduleButton" data-cmd="schedule"> | ||||
|         <div> | ||||
|           <span i18n-text="scheduleFrom"></span> | ||||
|           <input type="time"> | ||||
|           <span i18n-text="scheduleTo"></span> | ||||
|           <input type="time"> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </template> | ||||
| 
 | ||||
|  | @ -218,6 +227,8 @@ | |||
| 
 | ||||
| <script src="backup/fileSaveLoad.js"></script> | ||||
| <script src="msgbox/msgbox.js"></script> | ||||
| <script src="/schedule/core.js"></script> | ||||
| <script src="/schedule/ui.js"></script> | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -131,6 +131,7 @@ function showStyles(styles = []) { | |||
|     if (newUI.enabled && newUI.favicons) { | ||||
|       debounce(handleEvent.loadFavicons, 16); | ||||
|     } | ||||
|     document.dispatchEvent(new Event('styles-ready')); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -436,6 +437,10 @@ function handleUpdate(style, {reason, method} = {}) { | |||
|     $('.update-note', entry).textContent = t('updateCompleted'); | ||||
|     renderUpdatesOnlyFilter(); | ||||
|   } | ||||
| 
 | ||||
|   document.dispatchEvent(new CustomEvent('style-edited', { | ||||
|     detail: style | ||||
|   })); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,8 +18,12 @@ | |||
|     "storage", | ||||
|     "<all_urls>" | ||||
|   ], | ||||
|   "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": { | ||||
|  |  | |||
							
								
								
									
										119
									
								
								schedule/core.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								schedule/core.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,119 @@ | |||
| /* globals getStylesSafe, saveStyleSafe, BG */ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var SCHEDULE_PREFIX = 'schedule'; | ||||
| 
 | ||||
| var schedule = {}; | ||||
| 
 | ||||
| schedule.prefs = { | ||||
|   name (id) { | ||||
|     return SCHEDULE_PREFIX + '.' + id; | ||||
|   }, | ||||
|   validate (name) { | ||||
|     return name.startsWith(SCHEDULE_PREFIX + '.'); | ||||
|   }, | ||||
|   get (name, callback) { | ||||
|     chrome.storage.local.get(name, callback); | ||||
|   }, | ||||
|   getAll (callback) { | ||||
|     schedule.prefs.get(null, prefs => { | ||||
|       callback( | ||||
|         Object.keys(prefs).filter(schedule.prefs.validate) | ||||
|         .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)); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
| /* call this function when schedule timing is modified; if timing is not modified, nothing happens */ | ||||
| schedule.entry = request => { | ||||
|   console.log('schedule.entry', 'schedule timing might have been changed', request); | ||||
|   return new Promise((resolve, reject) => { | ||||
|     chrome.permissions.request({ | ||||
|       permissions: ['idle', 'alarms'] | ||||
|     }, (granted) => { | ||||
|       if (granted) { | ||||
|         schedule.prefs.set(schedule.prefs.name(request.id), { | ||||
|           id: request.id, | ||||
|           start: request.start, | ||||
|           end: request.end, | ||||
|           enabled: request.enabled | ||||
|         }, resolve); | ||||
|       } | ||||
|       else { | ||||
|         reject(new Error('Required permissions are not granted')); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| /* call this to update current alarm. If request.enabled = false, then alarm is cleared and this job will be removed from the storage */ | ||||
| schedule.execute = (name, request) => { | ||||
|   console.log('schedule.execute', 'evaluating response', 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; | ||||
|       const when = now.getTime() + Math.min( | ||||
|         start < 0 ? start + 24 * 60 * 60 * 1000 : start, | ||||
|         end < 0 ? end + 24 * 60 * 60 * 1000 : end | ||||
|       ); | ||||
|       console.log(`next alarm is set for id = ${request.id}`, new Date(when), start, end); | ||||
|       chrome.alarms.create(name, {when}); | ||||
|       getStylesSafe({id: request.id}).then(([style]) => { | ||||
|         if (style) { | ||||
|           const enabled = (start <= 0 && end > 0) || (start > end && start * end > 0) ; | ||||
|           console.log(`style with id = ${style.id}; enabled = `, enabled); | ||||
| 
 | ||||
|           saveStyleSafe({ | ||||
|             id: request.id, | ||||
|             enabled | ||||
|           }); | ||||
|         } | ||||
|         else { | ||||
|           // clear schedule if style is not found
 | ||||
|           console.log('removing from storage since style is not found', request); | ||||
|           schedule.execute(name, Object.assign( | ||||
|             request, {enabled: false} | ||||
|           )); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|     else { | ||||
|       console.log('removing pref since request.enabled is false', name); | ||||
|       schedule.prefs.remove(name); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // background only
 | ||||
| if (BG === window) { | ||||
|   // listen for pref changes to update chrome.alarms
 | ||||
|   schedule.prefs.subscribe((name, pref) => schedule.prefs.validate(name) && schedule.execute(name, pref)); | ||||
| 
 | ||||
|   chrome.alarms.onAlarm.addListener(({name}) => { | ||||
|     schedule.prefs.get(name, prefs => prefs[name] && schedule.execute(name, prefs[name])); | ||||
|   }); | ||||
| 
 | ||||
|   (function (callback) { | ||||
|     chrome.idle.onStateChanged.addListener(state => state === 'active' && callback()); | ||||
|     window.setTimeout(callback); | ||||
|   })(function () { | ||||
|     console.log('updating all schedules'); | ||||
|     schedule.prefs.getAll(prefs => prefs.forEach(a => schedule.execute(...a))); | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										107
									
								
								schedule/ui.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								schedule/ui.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | |||
| /* global t, schedule, $, $$, messageBox */ | ||||
| 'use strict'; | ||||
| 
 | ||||
| schedule.ui = {}; | ||||
| 
 | ||||
| /* get start and end inputs */ | ||||
| schedule.ui.inputs = (parent) => $$('input[type=time]', parent); | ||||
| 
 | ||||
| /* updating schedule section of a single style */ | ||||
| schedule.ui.update = (request) => { | ||||
|   console.log('updating schedule ui', request); | ||||
|   const parent = $(`[id="style-${request.id}"] .schedule`); | ||||
|   if (parent) { | ||||
|     parent.dataset.active = request.enabled; | ||||
|     $('input[type=button]', parent).value = t(request.enabled ? 'scheduleButtonActive' : 'scheduleButton'); | ||||
|     const [start, end] = schedule.ui.inputs(parent); | ||||
|     start.value = request.start; | ||||
|     end.value = request.end; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /* display schedule panel and hide it when user selects outside area */ | ||||
| document.addEventListener('click', e => { | ||||
|   const target = e.target; | ||||
|   let parent; | ||||
|   // hide schedule panel
 | ||||
|   function observe (e) { | ||||
|     if (!parent.contains(e.target)) { | ||||
|       const [start, end] = schedule.ui.inputs(parent); | ||||
|       const id = target.closest('.entry').id.replace('style-', ''); | ||||
|       let len = [start.value, end.value].filter(v => v).length; | ||||
|       len = len === 2 && start.value === end.value ? 3 : len; | ||||
|       switch (len) { | ||||
|         case 0: // reset schedule for this id
 | ||||
|           chrome.runtime.sendMessage({ | ||||
|             method: 'schedule', | ||||
|             enabled: false, | ||||
|             id | ||||
|           }, () => { | ||||
|             schedule.ui.update({ // reset UI
 | ||||
|               enabled: false, | ||||
|               id, | ||||
|               start: '', | ||||
|               end: '' | ||||
|             }); | ||||
|           }); | ||||
|           break; | ||||
|         case 3: // when both start and end have equal values
 | ||||
|         case 1: // when only start or end value is set
 | ||||
|           return messageBox({ | ||||
|             title: t('scheduleTitle'), | ||||
|             contents: t(len === 1 ? 'scheduleOneEntry' : 'scheduleEqualEntries'), | ||||
|             buttons: [t('scheduleButtonGiveUp'), t('scheduleButtonRetry')], | ||||
|             onshow: e => e.addEventListener('click', e => e.stopPropagation()) | ||||
|           }).then((r) => { | ||||
|             if (r.button === 1 && len === 1) { // retry
 | ||||
|               [start, end].filter(o => !o.value).forEach(o => o.focus()); | ||||
|             } | ||||
|             else if (r.button === 1 && len === 3) { | ||||
|               start.focus(); | ||||
|             } | ||||
|             else { | ||||
|               // clear and hide UI
 | ||||
|               start.value = end.value = ''; | ||||
|               document.body.click(); | ||||
|             } | ||||
|           }); | ||||
|         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); | ||||
|   } | ||||
| }); | ||||
| /* update schedule section on styles ready */ | ||||
| document.addEventListener('styles-ready', () => { | ||||
|   console.log('"styles-ready" is called'); | ||||
|   schedule.prefs.getAll(prefs => { | ||||
|     prefs.forEach(([name, pref]) => schedule.ui.update(pref)); | ||||
|   }); | ||||
| }); | ||||
| /* update schedule section on style change */ | ||||
| document.addEventListener('style-edited', e => { | ||||
|   console.log('"style-edited" is called'); | ||||
|   const id = e.detail.id; | ||||
|   const name = schedule.prefs.name(id); | ||||
|   schedule.prefs.get(name, prefs => { | ||||
|     const pref = prefs[name]; | ||||
|     if (pref) { | ||||
|       schedule.ui.update(pref); | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user