experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #16 from autonome/webunfucked

Webunfucked

authored by

Dietrich Ayala and committed by
GitHub
c1ef9ce8 f266dbed

+774 -843
+1
README.md
··· 135 135 * tl;dr: move features to all web code, with a couple special apis 136 136 * make globalShortcut an api like openWindow 137 137 * how to load/address features 138 + * manifests for feature metadata 138 139 * feature urls 139 140 * eg peek://settings(/index.html) 140 141 * maybe fine to file urls for now?
+1
features/cmd/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 + <script src="./config.js"></script> 10 11 <script type=module src="./background.js"></script> 11 12 </body> 12 13 </html>
+32 -164
features/cmd/background.js
··· 1 - // cmd/cmd.js 1 + // cmd/background.js 2 2 (async () => { 3 3 4 - console.log('cmd/cmd'); 5 - 6 - const labels = { 7 - featureType: 'cmd', 8 - featureDisplay: 'Cmd', 9 - prefs: { 10 - shortcutKey: 'Cmd shortcut', 11 - } 4 + const log = (...args) => { 5 + window.app.log(labels.featureType, args.join(', ')); 12 6 }; 13 7 14 - const { 15 - globalShortcut, 16 - } = require('electron'); 8 + log('cmd/background'); 17 9 18 - const path = require('path'); 19 - 20 - let _store = null; 21 - let _data = {}; 22 - 23 - const prefsSchema = { 24 - "$schema": "https://json-schema.org/draft/2020-12/schema", 25 - "$id": "peek.cmd.prefs.schema.json", 26 - "title": "Cmd preferences", 27 - "description": "Peek app Cmd user preferences", 28 - "type": "object", 29 - "properties": { 30 - "shortcutKey": { 31 - "description": "Global OS hotkey to open command panel", 32 - "type": "string", 33 - "default": "Option+Space" 34 - }, 35 - }, 36 - "required": [ "shortcutKey"] 37 - }; 38 - 39 - /* 40 - const itemSchema = { 41 - "$schema": "https://json-schema.org/draft/2020-12/schema", 42 - "$id": "peek.cmd.entry.schema.json", 43 - "title": "Peek - command entry", 44 - "description": "Peek command entry", 45 - "type": "object", 46 - "properties": { 47 - "keyNum": { 48 - "description": "Number on keyboard to open this peek, 0-9", 49 - "type": "integer", 50 - "minimum": 0, 51 - "maximum": 9, 52 - "default": 0 53 - }, 54 - "title": { 55 - "description": "Name of the peek - user defined label", 56 - "type": "string", 57 - "default": "New Peek" 58 - }, 59 - "address": { 60 - "description": "URL to load", 61 - "type": "string", 62 - "default": "https://example.com" 63 - }, 64 - "persistState": { 65 - "description": "Whether to persist local state or load page into empty container - defaults to false", 66 - "type": "boolean", 67 - "default": false 68 - }, 69 - "keepLive": { 70 - "description": "Whether to keep page alive in background or load fresh when triggered - defaults to false", 71 - "type": "boolean", 72 - "default": false 73 - }, 74 - "allowSound": { 75 - "description": "Whether to allow the page to emit sound or not (eg for background music player peeks - defaults to false", 76 - "type": "boolean", 77 - "default": false 78 - }, 79 - "height": { 80 - "description": "User-defined height of peek page", 81 - "type": "integer", 82 - "default": 600 83 - }, 84 - "width": { 85 - "description": "User-defined width of peek page", 86 - "type": "integer", 87 - "default": 800 88 - }, 89 - }, 90 - "required": [ "keyNum", "title", "address", "persistState", "keepLive", "allowSound", 91 - "height", "width" ] 92 - }; 10 + //import { labels, schemas, ui, defaults } from './config.js'; 93 11 94 - const listSchema = { 95 - type: 'array', 96 - items: { "$ref": "#/$defs/entry" } 97 - }; 98 - */ 12 + //const debug = window.location.search.indexOf('debug') > 0; 13 + const debug = 1; 99 14 100 - // TODO: schemaize 0-9 constraints for peeks 101 - const schemas = { 102 - prefs: prefsSchema, 103 - //item: itemSchema, 104 - //items: listSchema 105 - }; 15 + if (debug) { 16 + log('clearing storage') 17 + localStorage.clear(); 18 + } 106 19 107 - const _defaults = { 108 - prefs: { 109 - shortcutKey: 'Option+Space' 110 - }, 111 - }; 20 + const _store = localStorage; 21 + const _api = window.app; 112 22 113 - const openInputWindow = (api) => { 114 - const height = 50; 115 - const width = 600; 23 + const openInputWindow = prefs => { 24 + const height = prefs.height || 50; 25 + const width = prefs.width || 600; 116 26 117 27 const params = { 118 - type: labels.featureType, 28 + debug, 29 + feature: labels.featureType, 119 30 file: 'features/cmd/panel.html', 120 31 height, 121 32 width ··· 124 35 _api.openWindow(params); 125 36 }; 126 37 127 - const initStore = (store, data) => { 128 - const sp = store.get('prefs'); 38 + const initStore = (data) => { 39 + const sp = _store.getItem('prefs'); 129 40 if (!sp) { 130 - store.set('prefs', data.prefs); 41 + _store.setItem('prefs', JSON.stringify(data.prefs)); 131 42 } 132 43 }; 133 44 134 - const initShortcut = (api, prefs) => { 135 - const shortcut = prefs.shortcutKey; 136 - 137 - if (globalShortcut.isRegistered(shortcut)) { 138 - globalShortcut.unregister(shortcut); 139 - } 140 - 141 - const ret = globalShortcut.register(shortcut, () => { 142 - openInputWindow(api); 45 + const initShortcut = (shortcut) => { 46 + _api.shortcuts.register(shortcut, () => { 47 + openInputWindow(prefs()); 143 48 }); 144 - 145 - if (!ret) { 146 - console.error('Unable to register shortcut', shortcut); 147 - } 148 49 }; 149 50 150 - const init = (api, store) => { 151 - _store = store; 152 - _api = api; 51 + const prefs = () => JSON.parse(_store.getItem('prefs')); 153 52 154 - initStore(_store, _defaults); 53 + const init = () => { 54 + initStore(defaults); 155 55 156 - 157 - _data = { 158 - get prefs() { return _store.get('prefs'); }, 159 - //get items() { return _store.get('items'); }, 160 - }; 161 - 162 - initShortcut(api, _data.prefs); 56 + initShortcut(prefs().shortcutKey); 163 57 }; 164 58 165 59 const onChange = (changed, old) => { 166 - console.log(labels.featureType, 'onChange', changed); 60 + log('onChange', changed); 167 61 168 62 // TODO only update store if changed 169 63 // and re-init 170 64 if (changed.prefs) { 171 - console.log('cmd: updating prefs', changed.prefs); 172 - _store.set('prefs', changed.prefs); 65 + _store.setItem('prefs', JSON.stringify(changed.prefs)); 173 66 } 174 67 175 68 if (changed.items) { 176 - _store.set('items', changed.items); 69 + _store.setItem('items', JSON.stringif(changed.items)); 177 70 } 178 71 }; 179 72 180 - const onMessage = msg => { 181 - console.log('cmd:onMessage', msg) 182 - if (msg.command == 'openWindow') { 183 - _api.openWindow(msg); 184 - } 185 - }; 186 73 187 - // ui config 188 - const config = { 189 - // allow user to create new items 190 - allowNew: false, 191 - // fields that are view only 192 - disabled: ['keyNum'], 193 - }; 194 - 195 - module.exports = { 196 - init: init, 197 - config, 198 - labels, 199 - schemas, 200 - data: { 201 - get prefs() { return _store.get('prefs'); }, 202 - }, 203 - onChange, 204 - onMessage: onMessage 205 - }; 206 - 74 + window.addEventListener('load', init); 207 75 208 76 })();
+16
features/cmd/commands.js
··· 60 60 addCommand({ 61 61 name: cmdName, 62 62 execute: msg => { 63 + 63 64 console.log(cmdName, 'msg', msg); 65 + 64 66 const parts = msg.typed.split(' '); 65 67 parts.shift(); 68 + 66 69 const address = parts.shift(); 70 + 71 + const height = 600; 72 + const width = 800; 73 + 74 + const params = { 75 + feature: labels.featureType, 76 + address, 77 + height, 78 + width 79 + }; 80 + 81 + window.app.openWindow(params); 82 + 67 83 return { 68 84 command: 'openWebWindow', 69 85 address
+114
features/cmd/config.js
··· 1 + const labels = { 2 + featureType: 'cmd', 3 + featureDisplay: 'Cmd', 4 + prefs: { 5 + shortcutKey: 'Cmd shortcut', 6 + } 7 + }; 8 + 9 + const prefsSchema = { 10 + "$schema": "https://json-schema.org/draft/2020-12/schema", 11 + "$id": "peek.cmd.prefs.schema.json", 12 + "title": "Cmd preferences", 13 + "description": "Peek app Cmd user preferences", 14 + "type": "object", 15 + "properties": { 16 + "shortcutKey": { 17 + "description": "Global OS hotkey to open command panel", 18 + "type": "string", 19 + "default": "Option+Space" 20 + }, 21 + }, 22 + "required": [ "shortcutKey"] 23 + }; 24 + 25 + /* 26 + const itemSchema = { 27 + "$schema": "https://json-schema.org/draft/2020-12/schema", 28 + "$id": "peek.cmd.entry.schema.json", 29 + "title": "Peek - command entry", 30 + "description": "Peek command entry", 31 + "type": "object", 32 + "properties": { 33 + "keyNum": { 34 + "description": "Number on keyboard to open this peek, 0-9", 35 + "type": "integer", 36 + "minimum": 0, 37 + "maximum": 9, 38 + "default": 0 39 + }, 40 + "title": { 41 + "description": "Name of the peek - user defined label", 42 + "type": "string", 43 + "default": "New Peek" 44 + }, 45 + "address": { 46 + "description": "URL to load", 47 + "type": "string", 48 + "default": "https://example.com" 49 + }, 50 + "persistState": { 51 + "description": "Whether to persist local state or load page into empty container - defaults to false", 52 + "type": "boolean", 53 + "default": false 54 + }, 55 + "keepLive": { 56 + "description": "Whether to keep page alive in background or load fresh when triggered - defaults to false", 57 + "type": "boolean", 58 + "default": false 59 + }, 60 + "allowSound": { 61 + "description": "Whether to allow the page to emit sound or not (eg for background music player peeks - defaults to false", 62 + "type": "boolean", 63 + "default": false 64 + }, 65 + "height": { 66 + "description": "User-defined height of peek page", 67 + "type": "integer", 68 + "default": 600 69 + }, 70 + "width": { 71 + "description": "User-defined width of peek page", 72 + "type": "integer", 73 + "default": 800 74 + }, 75 + }, 76 + "required": [ "keyNum", "title", "address", "persistState", "keepLive", "allowSound", 77 + "height", "width" ] 78 + }; 79 + 80 + const listSchema = { 81 + type: 'array', 82 + items: { "$ref": "#/$defs/entry" } 83 + }; 84 + */ 85 + 86 + const schemas = { 87 + prefs: prefsSchema, 88 + //item: itemSchema, 89 + //items: listSchema 90 + }; 91 + 92 + const defaults = { 93 + prefs: { 94 + shortcutKey: 'Option+Space' 95 + }, 96 + }; 97 + 98 + // ui config for tweakpane filling 99 + const ui = { 100 + // allow user to create new items 101 + allowNew: false, 102 + // fields that are view only 103 + disabled: [], 104 + }; 105 + 106 + /* 107 + export { 108 + labels, 109 + schemas, 110 + ui, 111 + defaults 112 + }; 113 + */ 114 +
+1
features/cmd/panel.html
··· 13 13 </style> 14 14 </head> 15 15 <body> 16 + <script src="./config.js"></script> 16 17 <script src="./panel.js"></script> 17 18 <script src="./commands.js"></script> 18 19 </body>
+10 -4
features/cmd/panel.js
··· 39 39 40 40 (async () => { 41 41 42 + const log = (...args) => { 43 + window.app.log(labels.featureType, args.join(', ')); 44 + }; 45 + 46 + log('cmd/panel'); 47 + 48 + 42 49 let state = { 43 50 commands: [], // array of command names 44 51 matches: [], // array of commands matching the typed text ··· 106 113 107 114 async function execute(name, typed) { 108 115 if (state.commands[name]) { 116 + log('executing cmd', name, typed); 117 + 109 118 // execute command 110 119 const msg = state.commands[name].execute({typed}); 111 - window.app.sendMessage({ 112 - feature: 'cmd', 113 - data: msg 114 - }); 120 + 115 121 // close cmd popup 116 122 // NOTE: this kills command execution 117 123 // hrghhh, gotta turn execution completion promise
+1
features/core/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 + <script src="./config.js"></script> 10 11 <script type=module src="./background.js"></script> 11 12 </body> 12 13 </html>
+31 -5
features/core/background.js
··· 1 - const debug = 1; 1 + const log = (...args) => { 2 + //console.log.apply(null, [source].concat(args)); 3 + window.app.log(source, args.join(', ')); 4 + }; 5 + 6 + log('core/background'); 2 7 3 8 const features = [ 4 9 //'features/cmd/background.html', 5 10 //'features/groups/background.html', 6 - //'features/peeks/background.html', 11 + 'features/peeks/background.html', 7 12 //'features/scripts/background.html', 8 - 'features/settings/background.html', 13 + //'features/settings/background.html', 9 14 //'features/slides/background.html' 10 15 ]; 11 16 12 17 const initFeature = file => { 18 + window.app.log(source, 'initializing feature ' + file); 19 + 13 20 const params = { 21 + feature, 14 22 debug, 15 23 file, 16 24 keepLive: true, 17 - show: debug 25 + show: true 18 26 }; 19 - window.app.openWindow(params, () => console.log('win opened')); 27 + 28 + window.app.openWindow(params); 29 + //window.app.openWindow(params, () => window.app.log(source, 'win opened')); 30 + }; 31 + 32 + const pathPrefix = 'file:///Users/dietrich/misc/peek/'; 33 + 34 + const initIframeFeature = file => { 35 + log('initiframe'); 36 + const i = document.createElement('iframe'); 37 + const src = pathPrefix + file; 38 + log('iframe src', src); 39 + document.body.appendChild(i); 40 + i.src = src; 41 + log('iframe inited'); 42 + i.addEventListener('load', () => { 43 + log('iframe loaded'); 44 + }); 20 45 }; 21 46 22 47 features.forEach(initFeature); 48 + //features.forEach(initIframeFeature);
+3
features/core/config.js
··· 1 + const feature = 'Core'; 2 + const source = 'core/background'; 3 + const debug = 1;
+1
features/peeks/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 + <script src="./config.js"></script> 10 11 <script type=module src="./background.js"></script> 11 12 </body> 12 13 </html>
+38 -172
features/peeks/background.js
··· 1 - // peeks/peek.js 2 - (async () => { 1 + // slides/slides.js 2 + //(async () => { 3 3 4 - console.log('peeks/peeks'); 5 - 6 - const labels = { 7 - featureType: 'peeks', 8 - featureDisplay: 'Peeks', 9 - itemType: 'peek', 10 - itemDisplay: 'Peek', 11 - prefs: { 12 - keyPrefix: 'Peek shortcut prefix', 13 - } 4 + const log = (...args) => { 5 + console.log(labels.featureType, window.app.shortcuts); 6 + window.app.log(labels.featureType, args.join(', ')); 14 7 }; 15 8 16 - const { 17 - BrowserWindow, 18 - globalShortcut, 19 - } = require('electron'); 20 - 21 - const path = require('path'); 22 - 23 - let _store = null; 24 - let _data = {}; 9 + log('peeks/background'); 25 10 26 - const prefsSchema = { 27 - "$schema": "https://json-schema.org/draft/2020-12/schema", 28 - "$id": "peek.peeks.prefs.schema.json", 29 - "title": "Peeks preferences", 30 - "description": "Peeks user preferences", 31 - "type": "object", 32 - "properties": { 33 - "shortcutKeyPrefix": { 34 - "description": "Global OS hotkey prefix to trigger peeks - will be followed by 0-9", 35 - "type": "string", 36 - "default": "Option+" 37 - }, 38 - }, 39 - "required": [ "shortcutKeyPrefix"] 40 - }; 41 - 42 - const itemSchema = { 43 - "$schema": "https://json-schema.org/draft/2020-12/schema", 44 - "$id": "peek.peeks.peek.schema.json", 45 - "title": "Peek - page peek", 46 - "description": "Peek page peek", 47 - "type": "object", 48 - "properties": { 49 - "keyNum": { 50 - "description": "Number on keyboard to open this peek, 0-9", 51 - "type": "integer", 52 - "minimum": 0, 53 - "maximum": 9, 54 - "default": 0 55 - }, 56 - "title": { 57 - "description": "Name of the peek - user defined label", 58 - "type": "string", 59 - "default": "New Peek" 60 - }, 61 - "address": { 62 - "description": "URL to load", 63 - "type": "string", 64 - "default": "https://example.com" 65 - }, 66 - "persistState": { 67 - "description": "Whether to persist local state or load page into empty container - defaults to false", 68 - "type": "boolean", 69 - "default": false 70 - }, 71 - "keepLive": { 72 - "description": "Whether to keep page alive in background or load fresh when triggered - defaults to false", 73 - "type": "boolean", 74 - "default": false 75 - }, 76 - "allowSound": { 77 - "description": "Whether to allow the page to emit sound or not (eg for background music player peeks - defaults to false", 78 - "type": "boolean", 79 - "default": false 80 - }, 81 - "height": { 82 - "description": "User-defined height of peek page", 83 - "type": "integer", 84 - "default": 600 85 - }, 86 - "width": { 87 - "description": "User-defined width of peek page", 88 - "type": "integer", 89 - "default": 800 90 - }, 91 - }, 92 - "required": [ "keyNum", "title", "address", "persistState", "keepLive", "allowSound", 93 - "height", "width" ] 94 - }; 95 - 96 - const listSchema = { 97 - type: 'array', 98 - items: { "$ref": "#/$defs/peek" } 99 - }; 100 - 101 - // TODO: schemaize 0-9 constraints for peeks 102 - const schemas = { 103 - prefs: prefsSchema, 104 - item: itemSchema, 105 - items: listSchema 106 - }; 11 + //import { labels, schemas, ui, defaults } from './config.js'; 107 12 108 - const _defaults = { 109 - prefs: { 110 - shortcutKeyPrefix: 'Option+' 111 - }, 112 - items: Array.from(Array(10)), 113 - }; 13 + //const debug = window.location.search.indexOf('debug') > 0; 14 + const debug = 1; 114 15 115 - for (var i = 0; i != 10; i++) { 116 - _defaults.items[i] = { 117 - keyNum: i, 118 - title: `Peek key ${i}`, 119 - address: 'https://example.com/', 120 - persistState: false, 121 - keepLive: false, 122 - allowSound: false, 123 - height: 600, 124 - width: 800, 125 - }; 16 + if (debug) { 17 + log('clearing storage') 18 + localStorage.clear(); 126 19 } 127 20 128 - const executeItem = (api, item) => { 21 + const _store = localStorage; 22 + const _api = window.app; 23 + 24 + const executeItem = (item) => { 129 25 const height = item.height || 600; 130 26 const width = item.width || 800; 131 27 ··· 136 32 width, 137 33 138 34 // peek 139 - type: labels.featureType, 35 + feature: labels.featureType, 140 36 windowKey: `${labels.featureType}:${item.keyNum}`, 141 37 keepLive: item.keepLive || false, 142 38 persistData: item.persistData || false ··· 145 41 _api.openWindow(params); 146 42 }; 147 43 148 - const initStore = (store, data) => { 149 - const sp = store.get('prefs'); 44 + const initStore = (data) => { 45 + const sp = _store.getItem('prefs'); 150 46 if (!sp) { 151 - store.set('prefs', data.prefs); 47 + _store.setItem('prefs', JSON.stringify(data.prefs)); 152 48 } 153 49 154 - const items = store.get('items'); 50 + const items = _store.getItem('items'); 155 51 if (!items) { 156 - store.set('items', data.items); 52 + _store.setItem('items', JSON.stringify(data.items)); 157 53 } 158 54 }; 159 55 160 - const initItems = (api, prefs, items) => { 56 + const initItems = (prefs, items) => { 161 57 const cmdPrefix = prefs.shortcutKeyPrefix; 162 58 163 59 items.forEach(item => { 164 60 const shortcut = `${cmdPrefix}${item.keyNum}`; 165 61 166 - if (globalShortcut.isRegistered(shortcut)) { 167 - globalShortcut.unregister(shortcut); 168 - } 169 - 170 - const ret = globalShortcut.register(shortcut, () => { 171 - executeItem(api, item); 62 + _api.shortcuts.register(shortcut, () => { 63 + executeItem(item); 172 64 }); 173 - 174 - if (!ret) { 175 - console.error('Unable to register shortcut', shortcut); 176 - } 177 65 }); 178 66 }; 179 67 180 - const init = (api, store) => { 181 - _store = store; 182 - _api = api; 68 + const init = () => { 69 + log('init'); 183 70 184 - initStore(_store, _defaults); 71 + initStore(defaults); 185 72 186 - _data = { 187 - get prefs() { return _store.get('prefs'); }, 188 - get items() { return _store.get('items'); }, 189 - }; 73 + const prefs = () => JSON.parse(_store.getItem('prefs')); 74 + const items = () => JSON.parse(_store.getItem('items')); 190 75 191 - // initialize peeks 192 - if (_data.items.length > 0) { 193 - initItems(api, _data.prefs, _data.items); 76 + // initialize slides 77 + if (items().length > 0) { 78 + initItems(prefs(), items()); 194 79 } 195 80 }; 196 81 197 82 const onChange = (changed, old) => { 198 - console.log(labels.featureType, 'onChange', changed); 83 + log('onChange', changed); 199 84 200 85 // TODO only update store if changed 201 86 // and re-init 202 87 if (changed.prefs) { 203 - _store.set('prefs', changed.prefs); 88 + _store.setItem('prefs', JSON.stringify(changed.prefs)); 204 89 } 205 90 206 91 if (changed.items) { 207 - _store.set('items', changed.items); 92 + _store.setItem('items', JSON.stringif(changed.items)); 208 93 } 209 94 }; 210 95 211 - // ui config 212 - const config = { 213 - // allow user to create new items 214 - allowNew: false, 215 - // fields that are view only 216 - disabled: ['keyNum'], 217 - }; 218 - 219 - module.exports = { 220 - init: init, 221 - config, 222 - labels, 223 - schemas, 224 - data: { 225 - get prefs() { return _store.get('prefs'); }, 226 - get items() { return _store.get('items'); }, 227 - }, 228 - onChange 229 - }; 96 + window.addEventListener('load', init); 230 97 231 - 232 - })(); 98 + //})();
+121
features/peeks/config.js
··· 1 + 2 + const labels = { 3 + featureType: 'peeks', 4 + featureDisplay: 'Peeks', 5 + itemType: 'peek', 6 + itemDisplay: 'Peek', 7 + prefs: { 8 + keyPrefix: 'Peek shortcut prefix', 9 + } 10 + }; 11 + 12 + const prefsSchema = { 13 + "$schema": "https://json-schema.org/draft/2020-12/schema", 14 + "$id": "peek.peeks.prefs.schema.json", 15 + "title": "Peeks preferences", 16 + "description": "Peeks user preferences", 17 + "type": "object", 18 + "properties": { 19 + "shortcutKeyPrefix": { 20 + "description": "Global OS hotkey prefix to trigger peeks - will be followed by 0-9", 21 + "type": "string", 22 + "default": "Option+" 23 + }, 24 + }, 25 + "required": [ "shortcutKeyPrefix"] 26 + }; 27 + 28 + const itemSchema = { 29 + "$schema": "https://json-schema.org/draft/2020-12/schema", 30 + "$id": "peek.peeks.peek.schema.json", 31 + "title": "Peek - page peek", 32 + "description": "Peek page peek", 33 + "type": "object", 34 + "properties": { 35 + "keyNum": { 36 + "description": "Number on keyboard to open this peek, 0-9", 37 + "type": "integer", 38 + "minimum": 0, 39 + "maximum": 9, 40 + "default": 0 41 + }, 42 + "title": { 43 + "description": "Name of the peek - user defined label", 44 + "type": "string", 45 + "default": "New Peek" 46 + }, 47 + "address": { 48 + "description": "URL to load", 49 + "type": "string", 50 + "default": "https://example.com" 51 + }, 52 + "persistState": { 53 + "description": "Whether to persist local state or load page into empty container - defaults to false", 54 + "type": "boolean", 55 + "default": false 56 + }, 57 + "keepLive": { 58 + "description": "Whether to keep page alive in background or load fresh when triggered - defaults to false", 59 + "type": "boolean", 60 + "default": false 61 + }, 62 + "allowSound": { 63 + "description": "Whether to allow the page to emit sound or not (eg for background music player peeks - defaults to false", 64 + "type": "boolean", 65 + "default": false 66 + }, 67 + "height": { 68 + "description": "User-defined height of peek page", 69 + "type": "integer", 70 + "default": 600 71 + }, 72 + "width": { 73 + "description": "User-defined width of peek page", 74 + "type": "integer", 75 + "default": 800 76 + }, 77 + }, 78 + "required": [ "keyNum", "title", "address", "persistState", "keepLive", "allowSound", 79 + "height", "width" ] 80 + }; 81 + 82 + const listSchema = { 83 + type: 'array', 84 + items: { "$ref": "#/$defs/peek" } 85 + }; 86 + 87 + // TODO: schemaize 0-9 constraints for peeks 88 + const schemas = { 89 + prefs: prefsSchema, 90 + item: itemSchema, 91 + items: listSchema 92 + }; 93 + 94 + const defaults = { 95 + prefs: { 96 + shortcutKeyPrefix: 'Option+' 97 + }, 98 + items: Array.from(Array(10)), 99 + }; 100 + 101 + for (var i = 0; i != 10; i++) { 102 + defaults.items[i] = { 103 + keyNum: i, 104 + title: `Peek key ${i}`, 105 + address: 'https://example.com/', 106 + persistState: false, 107 + keepLive: false, 108 + allowSound: false, 109 + height: 600, 110 + width: 800, 111 + }; 112 + } 113 + 114 + // ui config 115 + const ui = { 116 + // allow user to create new items 117 + allowNew: false, 118 + // fields that are view only 119 + disabled: ['keyNum'], 120 + }; 121 +
+1
features/settings/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 + <script src="./config.js"></script> 10 11 <script type=module src="./background.js"></script> 11 12 </body> 12 13 </html>
+28 -105
features/settings/background.js
··· 1 - // settings/settings.js 1 + // settings/background.js 2 2 (async () => { 3 3 4 - console.log('settings/settings'); 4 + const log = (...args) => { 5 + window.app.log(labels.featureType, args.join(', ')); 6 + }; 7 + 8 + log('settings/background'); 9 + 10 + //import { labels, schemas, ui, defaults } from './config.js'; 5 11 6 12 //const debug = window.location.search.indexOf('debug') > 0; 7 13 const debug = 1; 8 14 9 15 if (debug) { 10 - console.log('clearing storage') 16 + log('clearing storage') 11 17 localStorage.clear(); 12 18 } 13 19 14 - const labels = { 15 - featureType: 'settings', 16 - featureDisplay: 'Settings', 17 - prefs: { 18 - shortcutKey: 'Settings shortcut', 19 - } 20 - }; 20 + const _store = localStorage; 21 + const _api = window.app; 21 22 22 - let _store = null; 23 - let _data = {}; 24 - 25 - const prefsSchema = { 26 - "$schema": "https://json-schema.org/draft/2020-12/schema", 27 - "$id": "peek.prefs.schema.json", 28 - "title": "Application and Settings preferences", 29 - "description": "Peek user preferences", 30 - "type": "object", 31 - "properties": { 32 - "shortcutKey": { 33 - "description": "App keyboard shortcut to load settings", 34 - "type": "string", 35 - "default": "CommandOrControl+," 36 - }, 37 - "height": { 38 - "description": "User-set or -defined height of Settings page", 39 - "type": "integer", 40 - "default": 600 41 - }, 42 - "width": { 43 - "description": "User-set or -defined width of Settings page", 44 - "type": "integer", 45 - "default": 800 46 - }, 47 - }, 48 - "required": [ "shortcutKey" ] 49 - }; 50 - 51 - // TODO: schemaize 0-9 constraints for peeks 52 - const schemas = { 53 - prefs: prefsSchema, 54 - }; 55 - 56 - const _defaults = { 57 - prefs: { 58 - shortcutKey: 'Option+,', 59 - height: 600, 60 - width: 800, 61 - }, 62 - }; 63 - 64 - const openSettingsWindow = (api, prefs) => { 23 + const openSettingsWindow = (prefs) => { 65 24 const height = prefs.height || 600; 66 25 const width = prefs.width || 800; 67 26 68 27 const params = { 69 28 debug, 70 - type: labels.featureType, 29 + feature: labels.featureType, 71 30 file: 'features/settings/content.html', 72 31 height, 73 32 width 74 33 }; 75 34 76 - api.openWindow(params); 35 + _api.openWindow(params); 77 36 }; 78 37 79 - const initStore = (store, data) => { 80 - const sp = store.getItem('prefs'); 38 + const initStore = (data) => { 39 + const sp = _store.getItem('prefs'); 81 40 if (!sp) { 82 - store.setItem('prefs', JSON.stringify(data.prefs)); 41 + log('first run, initing datastore') 42 + _store.setItem('prefs', JSON.stringify(data.prefs)); 83 43 } 84 44 }; 85 45 86 - const initShortcut = (api, prefs) => { 87 - const shortcut = prefs.shortcutKey; 88 - api.shortcuts.register(shortcut, () => { 89 - openSettingsWindow(api, prefs); 46 + const initShortcut = (shortcut) => { 47 + _api.shortcuts.register(shortcut, () => { 48 + openSettingsWindow(prefs()); 90 49 }); 91 50 }; 92 51 93 - const init = (api, store) => { 94 - console.log('settings: init'); 52 + const prefs = () => JSON.parse(_store.getItem('prefs')); 95 53 96 - _store = store; 97 - 98 - initStore(_store, _defaults); 99 - 100 - _data = { 101 - get prefs() { return JSON.parse(_store.getItem('prefs')); }, 102 - get items() { return JSON.parse(_store.getItem('items')); } 103 - }; 54 + const init = () => { 55 + log('settings: init'); 104 56 105 - console.log('data', _data); 57 + initStore(defaults); 106 58 107 - initShortcut(api, _data.prefs); 59 + initShortcut(prefs().shortcutKey); 108 60 }; 109 61 110 62 const onChange = (changed, old) => { 111 - console.log(labels.featureType, 'onChange', changed); 63 + log(labels.featureType, 'onChange', changed); 112 64 113 65 // TODO only update store if changed 114 66 // and re-init ··· 121 73 } 122 74 }; 123 75 124 - // ui config 125 - const config = { 126 - // allow user to create new items 127 - allowNew: false, 128 - // fields that are view only 129 - disabled: ['keyNum'], 130 - }; 131 76 132 - const open = () => { 133 - openSettingsWindow(_api, _data.prefs); 134 - }; 135 - 136 - /* 137 - module.exports = { 138 - init: init, 139 - config, 140 - labels, 141 - schemas, 142 - data: { 143 - get prefs() { 144 - return _store.get('prefs'); 145 - } 146 - }, 147 - open, 148 - onChange 149 - }; 150 - */ 151 - 152 - window.addEventListener('load', () => { 153 - init(window.app, localStorage); 154 - }); 77 + window.addEventListener('load', init); 155 78 156 79 })();
+66
features/settings/config.js
··· 1 + 2 + const labels = { 3 + featureType: 'settings', 4 + featureDisplay: 'Settings', 5 + prefs: { 6 + shortcutKey: 'Settings shortcut', 7 + } 8 + }; 9 + 10 + const prefsSchema = { 11 + "$schema": "https://json-schema.org/draft/2020-12/schema", 12 + "$id": "peek.prefs.schema.json", 13 + "title": "Application and Settings preferences", 14 + "description": "Peek user preferences", 15 + "type": "object", 16 + "properties": { 17 + "shortcutKey": { 18 + "description": "App keyboard shortcut to load settings", 19 + "type": "string", 20 + "default": "CommandOrControl+," 21 + }, 22 + "height": { 23 + "description": "User-set or -defined height of Settings page", 24 + "type": "integer", 25 + "default": 600 26 + }, 27 + "width": { 28 + "description": "User-set or -defined width of Settings page", 29 + "type": "integer", 30 + "default": 800 31 + }, 32 + }, 33 + "required": [ "shortcutKey" ] 34 + }; 35 + 36 + // TODO: schemaize 0-9 constraints for peeks 37 + const schemas = { 38 + prefs: prefsSchema, 39 + }; 40 + 41 + // ui config for tweakpane filling 42 + const ui = { 43 + // allow user to create new items 44 + allowNew: false, 45 + // fields that are view only 46 + disabled: [], 47 + }; 48 + 49 + // defaults for user-modifiable preferences or data 50 + const defaults = { 51 + prefs: { 52 + shortcutKey: 'Option+,', 53 + height: 600, 54 + width: 800, 55 + } 56 + }; 57 + 58 + /* 59 + export { 60 + labels, 61 + schemas, 62 + ui, 63 + defaults 64 + }; 65 + */ 66 +
+1 -2
features/settings/content.html
··· 1 - <!--index.html--> 2 - 3 1 <!DOCTYPE html> 4 2 <html> 5 3 <head> ··· 24 22 </div> 25 23 26 24 <script type=module src="./../../node_modules/tweakpane/dist/tweakpane.js"></script> 25 + <script src="./config.js"></script> 27 26 <script type=module src="./content.js"></script> 28 27 29 28 </body>
+30 -62
features/settings/content.js
··· 1 1 console.log('settings/content'); 2 2 3 - // TODO: capture and internally navigate out of panes 4 - window.addEventListener('keyup', e => { 5 - //console.log('renderer', 'onkeyup', e); 6 - if (e.key == 'Escape') { 7 - //ipcRenderer.send('esc', ''); 8 - } 9 - }); 10 - 11 - // send changes back to main process 12 - // it will notify us when saved 13 - // and we'll reload entirely 😐 14 - const updateToMain = data => { 15 - console.log('renderer: updating to main', data); 16 - window.app.setConfig(data); 17 - }; 18 - 19 - let panes = []; 20 - 21 - const init = cfg => { 3 + const init = () => { 22 4 console.log('settings: init'); 23 5 24 - let { prefs, features } = cfg; 25 - 26 6 const container = document.querySelector('.houseofpane'); 27 7 28 - // blow away panes if this is an update 29 - if (panes.length > 0) { 30 - panes.forEach(p => { 31 - p.pane.dispose(); 32 - }); 33 - panes = []; 34 - } 8 + const type = labels.featureType; 35 9 36 - cfg.features.forEach((feature, i) => { 37 - const type = feature.labels.featureType; 10 + const paneContainer = document.createElement('div'); 11 + container.appendChild(paneContainer); 38 12 39 - const paneContainer = document.createElement('div'); 40 - container.appendChild(paneContainer); 13 + const allowNew = ui.allowNew || false; 14 + const disabled = ui.disabled || []; 41 15 42 - const allowNew = feature.config.allowNew || false; 43 - const disabled = feature.config.disabled || []; 16 + const onChange = newData => { 17 + console.log('onChange', newData.prefs); 18 + localStorage.setItem('prefs', JSON.stringify(newData.prefs)); 19 + console.log('stored', JSON.parse(localStorage.getItem('prefs'))); 20 + }; 44 21 45 - const onChange = newData => { 46 - const p = {}; 47 - p[type] = newData; 48 - updateToMain(p); 49 - }; 22 + const prefs = JSON.parse(localStorage.getItem('prefs')); 23 + console.log('prefs', prefs) 50 24 51 - const pane = initFeaturePane( 52 - paneContainer, 53 - feature, 54 - onChange 55 - ); 25 + const feature = { 26 + config: ui, 27 + labels, 28 + schemas, 29 + prefs 30 + }; 56 31 57 - panes.push({ 58 - paneContainer, 59 - pane 60 - }); 61 - }); 32 + const pane = initFeaturePane( 33 + paneContainer, 34 + feature, 35 + onChange 36 + ); 37 + console.log('created pane'); 62 38 }; 63 39 64 40 const fillPaneFromSchema = (pane, labels, schema, data, onChange, disabled) => { ··· 151 127 }; 152 128 153 129 // TODO: make this right, ugh 154 - if (data.prefs) { 130 + if (prefs) { 155 131 updated.prefs = paneData.shift(); 156 132 } 157 133 ··· 169 145 }; 170 146 171 147 // prefs pane 172 - if (data.prefs) { 148 + if (prefs) { 173 149 174 150 const prefsFolder = pane.addFolder({ 175 151 title: schemas.prefs.title, ··· 181 157 update(!config.allowNew); 182 158 }; 183 159 184 - fillPaneFromSchema(prefsFolder, labels, schemas.prefs, data.prefs, onPrefChange, []); 160 + fillPaneFromSchema(prefsFolder, labels, schemas.prefs, prefs, onPrefChange, []); 185 161 } 186 162 187 163 // add items 188 - if (data.hasOwnProperty('items')) { 164 + if (data && data.hasOwnProperty('items')) { 189 165 data.items.forEach(item => { 190 166 const folder = pane.addFolder({ 191 167 title: item.title, ··· 233 209 return pane; 234 210 }; 235 211 236 - // listen for data changes 237 - window.app.onConfigChange(() => { 238 - console.log('onconfigchange'); 239 - window.app.getConfig.then(init); 240 - }); 241 - 242 - // initialization: get data and load ui 243 - window.app.getConfig.then(init); 244 - 212 + window.addEventListener('load', init);
+1
features/slides/background.html
··· 7 7 <title>peek:core:background</title> 8 8 </head> 9 9 <body> 10 + <script src="./config.js"></script> 10 11 <script type=module src="./background.js"></script> 11 12 </body> 12 13 </html>
+47 -206
features/slides/background.js
··· 1 1 // slides/slides.js 2 - (async () => { 3 - 4 - console.log('slides/slides'); 2 + //(async () => { 5 3 6 - const labels = { 7 - featureType: 'slides', 8 - featureDisplay: 'Slides', 9 - itemType: 'slide', 10 - itemDisplay: 'Slide', 11 - prefs: { 12 - keyPrefix: 'Slide shortcut prefix', 13 - } 4 + const log = (...args) => { 5 + console.log(labels.featureType, window.app.shortcuts); 6 + window.app.log(labels.featureType, args.join(', ')); 14 7 }; 15 8 16 - const { 17 - BrowserWindow, 18 - globalShortcut, 19 - screen, 20 - } = require('electron'); 9 + //log('slides/background'); 21 10 22 - const path = require('path'); 11 + //import { labels, schemas, ui, defaults } from './config.js'; 23 12 24 - let _store = null; 13 + //const debug = window.location.search.indexOf('debug') > 0; 14 + const debug = 1; 25 15 26 - const prefsSchema = { 27 - "$schema": "https://json-schema.org/draft/2020-12/schema", 28 - "$id": "peek.slides.prefs.schema.json", 29 - "title": "Slides prefs", 30 - "description": "Peek app Slides user preferences", 31 - "type": "object", 32 - "properties": { 33 - "shortcutKeyPrefix": { 34 - "description": "Global OS hotkey prefix to trigger slides - will be followed by up/down/left/right arrows", 35 - "type": "string", 36 - "default": "Option+" 37 - }, 38 - }, 39 - "required": [ "shortcutKeyPrefix"] 40 - }; 16 + if (debug) { 17 + log('clearing storage') 18 + localStorage.clear(); 19 + } 41 20 42 - const itemSchema = { 43 - "$schema": "https://json-schema.org/draft/2020-12/schema", 44 - "$id": "peek.slides.slide.schema.json", 45 - "title": "Peek - page slide", 46 - "description": "Peek page slide", 47 - "type": "object", 48 - "properties": { 49 - "screenEdge": { 50 - "description": "Edge of screen or arrow key to open this slide, up/down/left/right", 51 - "type": "string", 52 - "oneOf": [ 53 - { "format": "Up" }, 54 - { "format": "Down" }, 55 - { "format": "Left" }, 56 - { "format": "Right" } 57 - ], 58 - "default": "Right" 59 - }, 60 - "title": { 61 - "description": "Name of the slide - user defined label", 62 - "type": "string", 63 - "default": "New Slide" 64 - }, 65 - "address": { 66 - "description": "URL to load", 67 - "type": "string", 68 - "default": "https://example.com" 69 - }, 70 - "persistState": { 71 - "description": "Whether to persist local state or load page into empty container - defaults to false", 72 - "type": "boolean", 73 - "default": false 74 - }, 75 - "keepLive": { 76 - "description": "Whether to keep page alive in background or load fresh when triggered - defaults to false", 77 - "type": "boolean", 78 - "default": false 79 - }, 80 - "allowSound": { 81 - "description": "Whether to allow the page to emit sound or not (eg for background music player slides - defaults to false", 82 - "type": "boolean", 83 - "default": false 84 - }, 85 - "height": { 86 - "description": "User-defined height of slide page", 87 - "type": "integer", 88 - "default": 600 89 - }, 90 - "width": { 91 - "description": "User-defined width of slide page", 92 - "type": "integer", 93 - "default": 800 94 - }, 95 - }, 96 - "required": [ "screenEdge", "title", "address", "persistState", "keepLive", "allowSound", 97 - "height", "width" ] 98 - }; 21 + const _store = localStorage; 22 + const _api = window.app; 99 23 100 - const listSchema = { 101 - type: 'array', 102 - items: { "$ref": "#/$defs/slide" } 103 - }; 104 - 105 - // TODO: schemaize 0-9 constraints for peeks 106 - const schemas = { 107 - prefs: prefsSchema, 108 - item: itemSchema, 109 - items: listSchema 110 - }; 111 - 112 - const _defaults = { 113 - prefs: { 114 - shortcutKeyPrefix: 'Option+' 115 - }, 116 - items: [ 117 - { 118 - screenEdge: 'Up', 119 - title: 'Slide from top', 120 - address: 'http://localhost/', 121 - persistState: false, 122 - keepLive: false, 123 - allowSound: false, 124 - height: 600, 125 - width: 800, 126 - }, 127 - { 128 - screenEdge: 'Down', 129 - title: 'Slide from bottom', 130 - address: 'http://localhost/', 131 - persistState: false, 132 - keepLive: false, 133 - allowSound: false, 134 - height: 600, 135 - width: 800, 136 - }, 137 - { 138 - screenEdge: 'Left', 139 - title: 'Slide from left', 140 - address: 'http://localhost/', 141 - persistState: false, 142 - keepLive: false, 143 - allowSound: false, 144 - height: 600, 145 - width: 800, 146 - }, 147 - { 148 - screenEdge: 'Right', 149 - title: 'Slide from right', 150 - address: 'http://localhost/', 151 - persistState: false, 152 - keepLive: false, 153 - allowSound: false, 154 - height: 600, 155 - width: 800, 156 - }, 157 - ] 158 - }; 159 - 160 - let _windows = {}; 161 - 162 - const executeItem = (api, item) => { 24 + const executeItem = (item) => { 163 25 let height = item.height || 600; 164 26 let width = item.width || 800; 165 27 166 - const { size, bounds } = screen.getPrimaryDisplay(); 28 + const size = { 29 + height: window.screen.height, 30 + width: window.screen.width 31 + }; 167 32 168 33 let x, y, center = null; 169 34 ··· 222 87 console.log('waddafa'); 223 88 } 224 89 90 + log(item.screenEdge, x, y); 91 + 225 92 //animateSlide(win, item).then(); 226 93 227 94 const params = { ··· 231 98 width, 232 99 233 100 // peek 234 - type: labels.featureType, 101 + feature: labels.featureType, 235 102 windowKey: `${labels.featureType}:${item.screenEdge}`, 236 103 keepLive: item.keepLive || false, 237 104 persistData: item.persistData || false, ··· 241 108 y, 242 109 }; 243 110 244 - api.openWindow(params); 111 + _api.openWindow(params); 245 112 }; 246 113 247 - const initStore = (store, data) => { 248 - const sp = store.get('prefs'); 114 + const initStore = (data) => { 115 + const sp = _store.getItem('prefs'); 249 116 if (!sp) { 250 - store.set('prefs', data.prefs); 117 + _store.setItem('prefs', JSON.stringify(data.prefs)); 251 118 } 252 119 253 - const items = store.get('items'); 120 + const items = _store.getItem('items'); 254 121 if (!items) { 255 - store.set('items', data.items); 122 + _store.setItem('items', JSON.stringify(data.items)); 256 123 } 257 124 }; 258 125 259 - const initItems = (api, prefs, items) => { 126 + const initItems = (prefs, items) => { 127 + log('initItems'); 260 128 const cmdPrefix = prefs.shortcutKeyPrefix; 261 129 262 130 items.forEach(item => { 263 131 const shortcut = `${cmdPrefix}${item.screenEdge}`; 264 132 265 - if (globalShortcut.isRegistered(shortcut)) { 266 - globalShortcut.unregister(shortcut); 267 - } 268 - 269 - const ret = globalShortcut.register(shortcut, () => { 270 - executeItem(api, item); 133 + _api.shortcuts.register(shortcut, () => { 134 + executeItem(item); 271 135 }); 272 - 273 - if (!ret) { 274 - console.error('Unable to register shortcut', shortcut); 275 - } 276 136 }); 277 137 }; 278 138 279 - const init = (api, store) => { 280 - _store = store; 281 - _api = api; 139 + const init = () => { 140 + log('init'); 282 141 283 - initStore(_store, _defaults); 142 + initStore(defaults); 284 143 285 - _data = { 286 - get prefs() { return _store.get('prefs'); }, 287 - get items() { return _store.get('items'); }, 288 - }; 144 + const prefs = () => JSON.parse(_store.getItem('prefs')); 145 + const items = () => JSON.parse(_store.getItem('items')); 289 146 290 - // initialize peeks 291 - if (_data.items.length > 0) { 292 - initItems(api, _data.prefs, _data.items); 147 + // initialize slides 148 + if (items().length > 0) { 149 + initItems(prefs(), items()); 293 150 } 294 151 }; 295 152 296 153 const onChange = (changed, old) => { 297 - console.log(labels.featureType, 'onChange', changed); 154 + log('onChange', changed); 298 155 299 156 // TODO only update store if changed 300 157 // and re-init 301 158 if (changed.prefs) { 302 - _store.set('prefs', changed.prefs); 159 + _store.setItem('prefs', JSON.stringify(changed.prefs)); 303 160 } 304 161 305 162 if (changed.items) { 306 - _store.set('items', changed.items); 163 + _store.setItem('items', JSON.stringif(changed.items)); 307 164 } 308 165 }; 309 166 310 - // ui config 311 - const config = { 312 - // allow user to create new items 313 - allowNew: false, 314 - // fields that are view only 315 - disabled: ['screenEdge'], 316 - }; 317 - 318 - module.exports = { 319 - init: init, 320 - config, 321 - labels, 322 - schemas, 323 - data: { 324 - get prefs() { return _store.get('prefs'); }, 325 - get items() { return _store.get('items'); }, 326 - }, 327 - onChange 328 - }; 167 + window.addEventListener('load', init); 329 168 169 + /* 330 170 const animateSlide = (win, slide) => { 331 171 return new Promise((res, rej) => { 332 172 const { size, bounds } = screen.getPrimaryDisplay(); ··· 381 221 }, timerInterval); 382 222 }); 383 223 }; 224 + */ 384 225 385 - })(); 226 + //})();
+151
features/slides/config.js
··· 1 + 2 + const labels = { 3 + featureType: 'slides', 4 + featureDisplay: 'Slides', 5 + itemType: 'slide', 6 + itemDisplay: 'Slide', 7 + prefs: { 8 + keyPrefix: 'Slide shortcut prefix', 9 + } 10 + }; 11 + 12 + const prefsSchema = { 13 + "$schema": "https://json-schema.org/draft/2020-12/schema", 14 + "$id": "peek.slides.prefs.schema.json", 15 + "title": "Slides prefs", 16 + "description": "Peek app Slides user preferences", 17 + "type": "object", 18 + "properties": { 19 + "shortcutKeyPrefix": { 20 + "description": "Global OS hotkey prefix to trigger slides - will be followed by up/down/left/right arrows", 21 + "type": "string", 22 + "default": "Option+" 23 + }, 24 + }, 25 + "required": [ "shortcutKeyPrefix"] 26 + }; 27 + 28 + const itemSchema = { 29 + "$schema": "https://json-schema.org/draft/2020-12/schema", 30 + "$id": "peek.slides.slide.schema.json", 31 + "title": "Peek - page slide", 32 + "description": "Peek page slide", 33 + "type": "object", 34 + "properties": { 35 + "screenEdge": { 36 + "description": "Edge of screen or arrow key to open this slide, up/down/left/right", 37 + "type": "string", 38 + "oneOf": [ 39 + { "format": "Up" }, 40 + { "format": "Down" }, 41 + { "format": "Left" }, 42 + { "format": "Right" } 43 + ], 44 + "default": "Right" 45 + }, 46 + "title": { 47 + "description": "Name of the slide - user defined label", 48 + "type": "string", 49 + "default": "New Slide" 50 + }, 51 + "address": { 52 + "description": "URL to load", 53 + "type": "string", 54 + "default": "https://example.com" 55 + }, 56 + "persistState": { 57 + "description": "Whether to persist local state or load page into empty container - defaults to false", 58 + "type": "boolean", 59 + "default": false 60 + }, 61 + "keepLive": { 62 + "description": "Whether to keep page alive in background or load fresh when triggered - defaults to false", 63 + "type": "boolean", 64 + "default": false 65 + }, 66 + "allowSound": { 67 + "description": "Whether to allow the page to emit sound or not (eg for background music player slides - defaults to false", 68 + "type": "boolean", 69 + "default": false 70 + }, 71 + "height": { 72 + "description": "User-defined height of slide page", 73 + "type": "integer", 74 + "default": 600 75 + }, 76 + "width": { 77 + "description": "User-defined width of slide page", 78 + "type": "integer", 79 + "default": 800 80 + }, 81 + }, 82 + "required": [ "screenEdge", "title", "address", "persistState", "keepLive", "allowSound", 83 + "height", "width" ] 84 + }; 85 + 86 + const listSchema = { 87 + type: 'array', 88 + items: { "$ref": "#/$defs/slide" } 89 + }; 90 + 91 + const schemas = { 92 + prefs: prefsSchema, 93 + item: itemSchema, 94 + items: listSchema 95 + }; 96 + 97 + // ui config 98 + const ui = { 99 + // allow user to create new items 100 + allowNew: false, 101 + // fields that are view only 102 + disabled: ['screenEdge'], 103 + }; 104 + 105 + const defaults = { 106 + prefs: { 107 + shortcutKeyPrefix: 'Option+' 108 + }, 109 + items: [ 110 + { 111 + screenEdge: 'Up', 112 + title: 'Slide from top', 113 + address: 'http://localhost/', 114 + persistState: false, 115 + keepLive: false, 116 + allowSound: false, 117 + height: 600, 118 + width: 800, 119 + }, 120 + { 121 + screenEdge: 'Down', 122 + title: 'Slide from bottom', 123 + address: 'http://localhost/', 124 + persistState: false, 125 + keepLive: false, 126 + allowSound: false, 127 + height: 600, 128 + width: 800, 129 + }, 130 + { 131 + screenEdge: 'Left', 132 + title: 'Slide from left', 133 + address: 'http://localhost/', 134 + persistState: false, 135 + keepLive: false, 136 + allowSound: false, 137 + height: 600, 138 + width: 800, 139 + }, 140 + { 141 + screenEdge: 'Right', 142 + title: 'Slide from right', 143 + address: 'http://localhost/', 144 + persistState: false, 145 + keepLive: false, 146 + allowSound: false, 147 + height: 600, 148 + width: 800, 149 + }, 150 + ] 151 + };
+51 -102
index.js
··· 98 98 99 99 // ***** Caches ***** 100 100 101 - let _windows = new Set(); 101 + const _windows = new Set(); 102 102 103 103 const windowCache = { 104 104 cache: [], ··· 107 107 byKey: key => windowCache.cache.find(w => w.key == key) 108 108 }; 109 109 110 - // ***** Data ***** 111 - 112 - const getData = () => { 113 - let rollup = { 114 - features: [] 115 - }; 116 - 117 - Object.keys(features).forEach(k => { 118 - const feature = features[k]; 119 - 120 - //console.log('feature', feature); 121 - 122 - rollup.features.push({ 123 - config: feature.config, 124 - labels: feature.labels, 125 - schemas: feature.schemas, 126 - data: feature.data 127 - }) 128 - }); 129 - 130 - return rollup; 131 - }; 132 - 133 - const updateData = newData => { 134 - console.log('updateData', newData); 135 - 136 - Object.keys(newData).forEach(k => { 137 - console.log('updateData: key exists?', k); 138 - if (features[k]) { 139 - console.log('updateData: yes, updating with', newData[k]); 140 - features[k].onChange(newData[k]); 141 - } 142 - }); 143 - }; 144 - 145 - // initialized all bits which need updating if the data changes 146 - // can be called repeatedly to refresh on changes 147 - const initFeatures = () => { 148 - console.log('initFeatures'); 149 - // TODO: allow features to register 150 - // as app level prefs for enable/disable 151 - 152 - // inject into features 153 - // eventually get to less tight coupling 154 - const api = { 155 - debug: DEBUG, 156 - preloadPath, 157 - openWindow 158 - }; 159 - 160 - const datastorePrefix = 'peekFeature'; 161 - 162 - Object.keys(features).forEach(k => { 163 - console.log('main:initFeatures()', k); 164 - const feature = features[k]; 165 - 166 - if (!feature.labels) { 167 - console.error('feature?', feature) 168 - } 169 - const storeName = `${datastorePrefix}${feature.labels.featureType}`; 170 - 171 - // have to make per feature stores for now, pfftt 172 - // maybe fine, better isolation 173 - const featureStore = new Store({ 174 - name: storeName, 175 - // TODO: figure out schema approach here 176 - //schema: fullSchema, 177 - watch: true 178 - }); 179 - 180 - if (DEBUG) { 181 - console.log('main: clearing datastore', k) 182 - featureStore.clear(); 183 - } 184 - 185 - feature.init(api, featureStore); 186 - 187 - featureStore.onDidAnyChange(initFeatures); 188 - }); 189 - }; 110 + const _shortcuts = {}; 190 111 191 112 // Electron app load 192 113 const onReady = () => { ··· 201 122 // mostly just useful to know if the app is running or not 202 123 initTray(); 203 124 204 - //initFeatures(features); 205 - 125 + // init web core 206 126 openWindow({ 127 + feature: 'Core', 207 128 file: webCoreAddress, 208 - show: false, 129 + show: true, 209 130 debug: DEBUG 210 131 }) 211 132 ··· 224 145 // ***** API ***** 225 146 226 147 ipcMain.on('registershortcut', (ev, msg) => { 148 + //_shortcuts[msg.shortcut] = msg.replyTopic; 227 149 registerShortcut(msg.shortcut, () => { 228 - console.log('shorcut executed', msg.shortcut, msg.replyTopic) 150 + console.log('on(registershortcut): shorcut executed', msg.shortcut, msg.replyTopic) 229 151 ev.reply(msg.replyTopic, {}); 230 152 }); 231 153 }); ··· 264 186 }); 265 187 266 188 ipcMain.on('console', (ev, msg) => { 267 - console.log('renderer:', msg); 189 + console.log('r:', msg.source, msg.text); 268 190 }); 269 191 270 192 // ***** Helpers ***** ··· 276 198 globalShortcut.unregister(shortcut); 277 199 } 278 200 279 - const ret = globalShortcut.register(shortcut, callback); 201 + const ret = globalShortcut.register(shortcut, () => { 202 + console.log('shortcut executed', shortcut); 203 + callback(); 204 + }); 280 205 281 206 if (!ret) { 282 207 console.error('Unable to register shortcut', shortcut); ··· 286 211 287 212 // window opener 288 213 const openWindow = (params) => { 214 + console.log('openWindow', params); 215 + 216 + // if no source identifier, barf 217 + if (!params.hasOwnProperty('feature') || params.feature == undefined) { 218 + throw new Error('openWindow: no identifying source for openWindow request!'); 219 + } 220 + 221 + // TODO: need to figure out a better approach 222 + const show = params.hasOwnProperty('show') ? params.show : true; 223 + 224 + // cache key 225 + // TODO: need to figure out a better approach 226 + const key = params.feature + (params.address || params.file); 227 + 289 228 if (params.keepLive == true) { 290 - const entry = windowCache.byKey(params.windowKey); 229 + const entry = windowCache.byKey(key); 291 230 if (entry != undefined) { 292 231 const win = BrowserWindow.fromId(entry.id); 293 232 if (win) { 294 - console.log('openWindow(): opening persistent window for', params.windowKey) 295 - win.show(); 233 + console.log('openWindow: opening persistent window for', key) 234 + if (show) { 235 + win.show(); 236 + } 296 237 return; 297 238 } 298 239 } 299 240 } 300 241 301 - console.log('openWindow(): creating new window', params); 242 + console.log('openWindow(): creating new window'); 302 243 303 244 const height = params.height || 600; 304 245 const width = params.width || 800; 305 - const show = params.hasOwnProperty('show') ? params.show : true; 246 + 247 + let webPreferences = {}; 306 248 307 - let webPreferences = { 308 - preload: preloadPath, 309 - }; 249 + if (params.file) { 250 + console.log('FILE', params.file); 251 + params.address = `file://${path.join(__dirname)}/${params.file}`; 252 + webPreferences.preload = preloadPath; 253 + } 310 254 311 255 if (!params.persistData) { 312 256 // TODO: hack. this just isolates. ··· 317 261 height, 318 262 width, 319 263 show, 320 - center: true, 321 264 skipTaskbar: true, 322 265 autoHideMenuBar: true, 323 266 titleBarStyle: 'hidden', ··· 330 273 } 331 274 }); 332 275 276 + if (winPreferences.x == undefined && winPreferences.y == undefined) { 277 + winPreferences.center = true; 278 + } 279 + 280 + console.log('final params', winPreferences.x, winPreferences.y); 281 + 333 282 let win = new BrowserWindow(winPreferences); 334 283 335 284 // if persisting window, cache the caller's key and window id 336 285 if (params.keepLive == true) { 337 286 windowCache.add({ 338 287 id: win.id, 339 - key: params.windowKey 288 + key: key 340 289 }); 341 290 } 342 291 343 292 // TODO: make configurable 344 293 const onGoAway = () => { 345 294 if (params.keepLive) { 295 + //console.log('win.onGoAway(): hiding ', params.address); 346 296 win.hide(); 347 297 } 348 298 else { 299 + //console.log('win.onGoAway(): destroying ', params.address); 349 300 win.destroy(); 350 301 } 351 302 } ··· 353 304 win.on('close', onGoAway); 354 305 355 306 win.on('closed', () => { 307 + //console.log('win.on(closed): deleting ', key, ' for ', params.address); 356 308 _windows.delete(win); 357 309 win = null; 358 310 }); ··· 364 316 if (params.address) { 365 317 win.loadURL(params.address); 366 318 } 367 - else if (params.file) { 368 - win.loadFile(params.file); 369 - } 370 319 else { 371 320 console.error('openWindow: neither address nor file!'); 372 321 } 373 322 374 323 //win.webContents.send('window', { type: labels.featureType, id: win.id}); 375 - broadcastToWindows('window', { type: labels.featureType, id: win.id}); 324 + //broadcastToWindows('window', { type: labels.featureType, id: win.id}); 376 325 377 326 if (params.script) { 378 327 const script = params.script; ··· 412 361 } 413 362 }); 414 363 415 - 416 364 const onQuit = () => { 365 + console.log('onQuit'); 417 366 // Close all persisent windows? 418 367 419 368 app.quit();
+28 -21
preload.js
··· 1 - const d = msg => { 2 - ipcRenderer.send('console', msg); 3 - }; 4 - 5 1 const { 6 2 contextBridge, 7 - globalShortcut, 8 3 ipcRenderer 9 - } = require('electron') 4 + } = require('electron'); 5 + 6 + const src = 'preload'; 10 7 11 - /* 12 - const EventEmitter = require('node:events'); 13 - class LocalEmitter extends EventEmitter {}; 14 - const pubsub = new LocalEmitter(); 15 - */ 8 + const log = (source, text) => { 9 + ipcRenderer.send('console', { 10 + source, 11 + text 12 + }); 13 + }; 16 14 17 15 /* 18 16 ipcRenderer.on('window', (ev, msg) => { 19 - d('preload: onwindow', msg); 17 + console.log('preload: onwindow', msg); 20 18 const { type, id, data } = msg; 21 19 if (type == 'main') { 22 20 handleMainWindow(); ··· 24 22 }); 25 23 */ 26 24 27 - 28 25 // all visible window types close on escape 29 26 window.addEventListener('keyup', e => { 30 27 if (e.key == 'Escape') { ··· 36 33 37 34 api.shortcuts = { 38 35 register: (shortcut, cb) => { 39 - console.log('registering', shortcut, 'for', window.location) 40 - const replyTopic = `${shortcut}` 36 + //log(source, 'registering ' + shortcut + ' for ' + window.location) 37 + 38 + const replyTopic = `${shortcut}${window.location}`; 39 + 41 40 ipcRenderer.send('registershortcut', { 42 41 shortcut, 43 42 replyTopic 44 43 }); 45 - ipcRenderer.on(replyTopic, cb); 44 + 45 + ipcRenderer.on(replyTopic, () => { 46 + log(src, 'shortcut execution reply'); 47 + cb(); 48 + }); 46 49 }, 47 50 unregister: shortcut => { 48 51 console.log('unregistering', shortcut, 'for', window.location) ··· 53 56 }; 54 57 55 58 api.openWindow = (params, callback) => { 56 - console.log('openwindow', params, 'for', window.location) 57 - const replyTopic = 'huh'; 59 + log(src, ['openwindow', JSON.stringify(params), 'for', window.location].join(', ')); 60 + // TODO: won't work for features that open multiple windows 61 + const replyTopic = params.feature; 58 62 ipcRenderer.send('openwindow', { 59 63 params, 60 64 replyTopic ··· 63 67 ipcRenderer.on(replyTopic, params.callback); 64 68 } 65 69 }; 70 + 71 + api.log = log; 66 72 67 73 /* 68 74 api.onConfigChange = callback => { ··· 114 120 115 121 contextBridge.exposeInMainWorld('app', api); 116 122 123 + /* 117 124 window.addEventListener('load', () => { 118 - console.log('preloaded'); 119 - d('preload loaded'); 125 + console.log('preload loaded'); 126 + log(src, 'preload loaded'); 120 127 }); 121 - 128 + */ 122 129 123 130 /* 124 131 const handleMainWindow = () => {