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 #22 from autonome/switch

Switch

authored by

Dietrich Ayala and committed by
GitHub
42bcdcd8 eed1b63c

+260 -589
+5 -8
features/cmd/background.js
··· 1 1 // cmd/background.js 2 2 3 - import { id, labels, schemas, ui, defaults } from './config.js'; 3 + import { id, labels, schemas, storageKeys, defaults } from './config.js'; 4 4 import { log as l, openStore } from "../utils.js"; 5 5 6 6 const log = function(...args) { l(id, args); }; 7 7 8 - log('background'); 8 + log('background', id); 9 9 10 10 const debug = window.app.debug; 11 - const store = openStore(id, defaults); 11 + const clear = false; 12 + 13 + const store = openStore(id, defaults, clear /* clear storage */); 12 14 const api = window.app; 13 - 14 - const storageKeys = { 15 - PREFS: 'prefs', 16 - ITEMS: 'items', 17 - }; 18 15 19 16 const openInputWindow = prefs => { 20 17 const height = prefs.height || 50;
+7 -73
features/cmd/config.js
··· 1 1 const id = 'features/cmd'; 2 + const guid = 'cee1225d-40ac-41e5-a34c-e2edba69d599'; 2 3 3 4 const labels = { 4 5 featureType: 'cmd', ··· 24 25 "required": [ "shortcutKey"] 25 26 }; 26 27 27 - /* 28 - const itemSchema = { 29 - "$schema": "https://json-schema.org/draft/2020-12/schema", 30 - "$id": "peek.cmd.entry.schema.json", 31 - "title": "Peek - command entry", 32 - "description": "Peek command entry", 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/entry" } 85 - }; 86 - */ 87 - 88 28 const schemas = { 89 29 prefs: prefsSchema, 90 - //item: itemSchema, 91 - //items: listSchema 30 + }; 31 + 32 + const storageKeys = { 33 + PREFS: 'prefs', 92 34 }; 93 35 94 36 const defaults = { ··· 97 39 }, 98 40 }; 99 41 100 - // ui config for tweakpane filling 101 - const ui = { 102 - // allow user to create new items 103 - allowNew: false, 104 - // fields that are view only 105 - disabled: [], 106 - }; 107 - 108 42 export { 109 43 id, 44 + guid, 110 45 labels, 111 46 schemas, 112 - ui, 47 + storageKeys, 113 48 defaults 114 49 }; 115 -
+20 -14
features/core/background.js
··· 1 - import { id, labels, schemas, ui, defaults } from './config.js'; 1 + import { id, labels, schemas, storageKeys, defaults } from './config.js'; 2 2 import { log as l, openStore } from "../utils.js"; 3 3 4 4 const log = function(...args) { l(id, args); }; 5 5 6 - log('background'); 6 + log('background', id); 7 7 8 8 const debug = window.app.debug; 9 + const clear = false; 9 10 10 - const _store = openStore(id, defaults); 11 + const _store = openStore(id, defaults, clear /* clear storage */); 11 12 const _api = window.app; 12 - 13 - const storageKeys = { 14 - PREFS: 'prefs', 15 - FEATURES: 'items', 16 - }; 17 13 18 14 const openSettingsWindow = (prefs) => { 19 15 const height = prefs.height || 600; ··· 44 40 log('initializing feature ' + f); 45 41 46 42 const params = { 47 - feature: f.title, 43 + feature: f.name, 48 44 debug, 49 - file: f.address, 45 + file: f.start_url, 50 46 keepLive: true, 51 47 show: debug 52 48 }; ··· 55 51 //window.app.openWindow(params, () => window.app.log('win opened')); 56 52 }; 57 53 54 + // unused, worth testing more tho 58 55 const initIframeFeature = file => { 59 56 const pathPrefix = 'file:///Users/dietrich/misc/peek/'; 60 57 log('initiframe'); ··· 70 67 }; 71 68 72 69 const prefs = () => _store.get(storageKeys.PREFS); 73 - const items = () => _store.get(storageKeys.FEATURES); 70 + const features = () => _store.get(storageKeys.FEATURES); 74 71 75 72 const init = () => { 76 73 log('init'); 77 74 78 75 const p = prefs(); 79 76 77 + console.log('prefs', p); 78 + 80 79 initShortcut(p); 81 80 82 - items().forEach(initFeature); 81 + features().forEach(initFeature); 83 82 //features.forEach(initIframeFeature); 84 83 85 84 const startupFeatureTitle = p.startupFeature; 86 85 87 - const startupFeature = items().find(f => f.title = startupFeatureTitle); 86 + const startupFeature = features().find(f => f.name = startupFeatureTitle); 88 87 88 + // Listen for system- or feature-level requests to open windows. 89 + // 90 + // In this case, for opening up global settings 91 + // on app start (if configured) and from the tray icon. 89 92 window.app.subscribe('open', msg => { 90 93 if (msg.feature && msg.feature == 'feature/core/settings') { 91 94 openSettingsWindow(p); 92 95 } 93 96 }); 94 97 98 + // main process uses these for initi 95 99 window.app.publish('prefs', { 96 100 feature: id, 97 101 prefs: p ··· 100 104 101 105 window.addEventListener('load', init); 102 106 107 + /* 103 108 const odiff = (a, b) => Object.entries(b).reduce((c, [k, v]) => Object.assign(c, a[k] ? {} : { [k]: v }), {}); 104 109 105 110 const onStorageChange = (e) => { ··· 110 115 //log('onStorageChane', e.key, featureKey) 111 116 if (e.key == featureKey) { 112 117 //log('STORAGE CHANGE', e.key, old[0].enabled, now[0].enabled); 113 - items().forEach((feat, i) => { 118 + features().forEach((feat, i) => { 114 119 log(feat.title, i, feat.enabled, old[i].enabled, now[i].enabled); 115 120 // disabled, so unload 116 121 if (old[i].enabled == true && now[i].enabled == false) { ··· 127 132 }; 128 133 129 134 window.addEventListener('storage', onStorageChange); 135 + */
+57 -88
features/core/config.js
··· 1 1 const id = 'features/core'; 2 + const guid = '8aadaae5-2594-4968-aba0-707f0d371cfb'; 2 3 3 4 const labels = { 4 5 featureType: 'settings', ··· 51 52 "required": [ "shortcutKey", "startupFeature", "enableTrayIcon", "showInDockAndSwitcher" ] 52 53 }; 53 54 54 - const itemSchema = { 55 + const featureSchema = { 55 56 "$schema": "https://json-schema.org/draft/2020-12/schema", 56 57 "$id": "peek.settings.feature.schema.json", 57 58 "title": "Feature", 58 59 "description": "Application feature", 59 60 "type": "object", 60 61 "properties": { 61 - "title": { 62 + "id": { 63 + "description": "Unique id of the feature", 64 + "type": "string" 65 + }, 66 + "name": { 62 67 "description": "Name of the feature", 63 68 "type": "string" 64 69 }, 65 - "address": { 66 - "description": "URL to load", 70 + "description": { 71 + "description": "Description of the feature", 72 + "type": "string" 73 + }, 74 + "start_url": { 75 + "description": "Address to load the feature", 76 + "type": "string", 77 + "format": "uri" 78 + }, 79 + "settings_url": { 80 + "description": "Address to load the feature's settings", 67 81 "type": "string", 68 82 "format": "uri" 69 83 }, 70 84 "enabled": { 71 - "description": "Whether the feature is enabled or not - defaults to true", 85 + "description": "Whether the feature is enabled or not.", 72 86 "type": "boolean", 73 87 "default": true 74 88 }, 75 89 }, 76 - "required": [ "title", "address", "enabled" ] 90 + "required": [ "id", "name", "description", "start_url", "enabled" ] 77 91 }; 78 92 79 - const listSchema = { 93 + const featureListSchema = { 80 94 "title": "Features", 81 95 "type": 'array', 82 96 "features": { "$ref": "#/$defs/feature" } ··· 84 98 85 99 const schemas = { 86 100 prefs: prefsSchema, 87 - item: itemSchema, 88 - items: listSchema 101 + feature: featureSchema, 102 + featureList: featureListSchema 103 + }; 104 + 105 + const storageKeys = { 106 + PREFS: 'prefs', 107 + FEATURES: 'features', 89 108 }; 90 109 91 110 // defaults for user-modifiable preferences or data 92 111 const defaults = { 93 112 prefs: { 94 113 shortcutKey: 'Option+,', 95 - height: 600, 114 + height: 850, 96 115 width: 800, 97 116 startupFeature: 'feature/core/settings', 98 117 showTrayIcon: true, 99 118 showInTrayAndSwitcher: true 100 119 }, 101 - items: [ 102 - { title: 'Cmd', 103 - address: 'features/cmd/background.html', 120 + features: [ 121 + { id: 'cee1225d-40ac-41e5-a34c-e2edba69d599', 122 + name: 'Cmd', 123 + description: 'Command bar', 124 + start_url: 'features/cmd/background.html', 104 125 enabled: false, 105 - settingsAddress: 'features/cmd/settings.html', 126 + settings_url: 'features/cmd/settings.html', 106 127 }, 107 - { title: 'Groups', 108 - address: 'features/groups/background.html', 128 + { id: '82de735f-a4b7-4fe6-a458-ec29939ae00d', 129 + name: 'Groups', 130 + description: 'View your web in groups', 131 + start_url: 'features/groups/background.html', 109 132 enabled: false, 110 - settingsAddress: 'features/groups/settings.html', 133 + settings_url: 'features/groups/settings.html', 111 134 }, 112 - { title: 'Peeks', 113 - address: 'features/peeks/background.html', 135 + { id: 'ef3bd271-d408-421f-9338-47b615571e43', 136 + name: 'Peeks', 137 + description: 'Peek at pages in a transient popup using keyboard shortcuts', 138 + start_url: 'features/peeks/background.html', 114 139 enabled: false, 115 - settingsAddress: 'features/peeks/settings.html', 140 + settings_url: 'features/peeks/settings.html', 116 141 }, 117 - { title: 'Scripts', 118 - address: 'features/scripts/background.html', 142 + { id: '30c25027-d367-4595-b37f-9db3de853c37', 143 + name: 'Scripts', 144 + description: 'Create, manage and run content scripts', 145 + start_url: 'features/scripts/background.html', 119 146 enabled: false, 120 - settingsAddress: 'features/scripts/settings.html', 147 + settings_url: 'features/scripts/settings.html', 121 148 }, 122 - { title: 'Slides', 123 - address: 'features/slides/background.html', 149 + { id: '434108f3-18a6-437a-b507-2f998f693bb2', 150 + name: 'Slides', 151 + description: 'Open specific pages as side/top/bottom bars', 152 + start_url: 'features/slides/background.html', 124 153 enabled: false, 125 - settingsAddress: 'features/slides/settings.html', 154 + settings_url: 'features/slides/settings.html', 126 155 } 127 156 ] 128 157 }; 129 158 130 - // ui config for tweakpane filling 131 - // TODO: this needs to be per section 132 - // or integrated some other way entirely, kind of a mess 133 - // 134 - // gotta think about much more complex objects 135 - // and also multiple types of items/lists 136 - const ui = { 137 - // allow user to create new items 138 - allowNew: false, 139 - 140 - // fields that are view only 141 - disabled: ['title', 'address' ], 142 - 143 - // fields to make links 144 - linkify: [ 145 - { field: 'settingsAddress', 146 - title: 'Settings' 147 - } 148 - ], 149 - }; 150 - 151 - 152 - /* 153 - const paneData = { 154 - label: labels.featureDisplay, 155 - children: [], 156 - }; 157 - 158 - settings.sections.push({ 159 - }); 160 - 161 - { 162 - "disabled": false, 163 - "expanded": true, 164 - "hidden": false, 165 - "children": [ 166 - { 167 - "disabled": false, 168 - "expanded": true, 169 - "hidden": false, 170 - "label": "param1", 171 - "binding": { 172 - "key": "param1", 173 - "value": 1 174 - }, 175 - "tag": "foo" 176 - }, 177 - { 178 - "disabled": false, 179 - "hidden": false, 180 - "label": "param2", 181 - "binding": { 182 - "key": "param2", 183 - "value": 2 184 - }, 185 - "tag": "bar" 186 - } 187 - ], 188 - } 189 - */ 190 - 191 159 export { 192 160 id, 161 + guid, 193 162 labels, 194 163 schemas, 195 - ui, 164 + storageKeys, 196 165 defaults 197 166 };
+1 -1
features/core/settings.css
··· 25 25 } 26 26 27 27 /* tweakpanes */ 28 - .houseofpane > div > div { 28 + .houseofpane > div { 29 29 min-width: 380px; 30 30 max-width: 380px; 31 31 margin-bottom: 10px;
+2
features/core/settings.html
··· 8 8 <meta name="description" content=""> 9 9 <meta name="viewport" content="width=device-width, initial-scale=1"> 10 10 <link rel="stylesheet" href="settings.css"> 11 + <link rel="stylesheet" href="settings.css"> 12 + <link rel="stylesheet" href="../../node_modules/lil-gui/dist/lil-gui.min.css"> 11 13 </head> 12 14 <body> 13 15 <div>
+83 -70
features/core/settings.js
··· 1 - import { id, labels, schemas, ui, defaults } from './config.js'; 2 - import { log as l, openStore, settingsPane } from "../utils.js"; 1 + import { id, labels, schemas, storageKeys, defaults } from './config.js'; 2 + import { log as l, openStore } from "../utils.js"; 3 + import GUI from './../../node_modules/lil-gui/dist/lil-gui.esm.min.js'; 3 4 4 5 const log = function(...args) { l(id, args); }; 5 6 const DEBUG = window.app.debug; 6 7 7 8 log('loading', id); 8 - 9 - const storageKeys = { 10 - PREFS: 'prefs', 11 - FEATURES: 'items', 12 - }; 13 9 14 10 const store = openStore(id); 15 11 const container = document.querySelector('.houseofpane'); 16 - const prefs = store.get(storageKeys.PREFS); 17 - const items = store.get(storageKeys.FEATURES); 18 - 19 - const onChange = newData => { 20 - log('onChange', JSON.stringify(newData)); 12 + let prefs = store.get(storageKeys.PREFS); 13 + let features = store.get(storageKeys.FEATURES); 21 14 22 - if (newData.prefs) { 23 - const key = 'prefs'; 24 - store.set(storageKeys.PREFS, newData[key]); 25 - log('stored', key, store.get(storageKeys.PREFS)); 26 - } 27 - 28 - if (newData.items) { 29 - const key = 'items'; 30 - store.set(storageKeys.FEATURES, newData[key]); 31 - log('stored', key, store.get(storageKeys.FEATURES)); 32 - } 15 + const persistToStorage = () => { 16 + store.set(storageKeys.PREFS, prefs); 17 + store.set(storageKeys.FEATURES, features); 33 18 }; 34 19 35 - const paneConfig = { 36 - 37 - label: labels.featureDisplay, 20 + const init = () => { 38 21 39 - children: [ 40 - { 41 - label: schemas.prefs.title, 22 + /* 23 + pubsub.publish('open', { 24 + feature: msg.prefs.startupFeature 25 + }); 26 + */ 42 27 43 - // json schema to validate against 44 - schema: schemas.prefs, 28 + // Initialize settings UI 29 + const container = document.querySelector('.houseofpane'); 45 30 46 - // data to populate 47 - data: defaults.prefs, 48 - }, 31 + const gui = new GUI({ 32 + touchStyles: false, 33 + container: container, 34 + title: labels.featureDisplay 35 + }); 49 36 50 - { 51 - label: schemas.items.title, 37 + // anytime anything changes, persist to storage 38 + gui.onFinishChange(persistToStorage); 52 39 53 - // json schema to validate against 54 - schema: schemas.items, 40 + // Add prefs 41 + const prefsFolder = gui.addFolder( 42 + schemas.prefs.title 43 + ); 55 44 56 - // data to populate 57 - data: defaults.items, 45 + const pProps = schemas.prefs.properties; 46 + const data = prefs; 58 47 59 - // allow user to create new entries 60 - // adds an "add" button 61 - canAdd: true, 48 + Object.keys(pProps).forEach(k => { 49 + // schema for property 50 + const s = pProps[k]; 62 51 63 - // allow user to delete entries 64 - canDelete: true, 52 + // value (or default) 53 + const v = 54 + (data && data.hasOwnProperty(k)) 55 + ? data[k] 56 + : pProps[k].default; 65 57 66 - // fields that are view only 67 - disabled: [ 68 - 'title', 'address' 69 - ], 58 + const disabled = false; 59 + const step = pProps[k].type == 'integer' ? 1 : null; 70 60 71 - // field callbacks: function(pane, data) 72 - callbacks: [ 73 - 'settingsAddress', 74 - ], 61 + addToGUI(prefsFolder, k, v, disabled, step).onChange(e => { 62 + // TODO: validate new value against schema 63 + prefs[k] = e; 64 + });; 65 + }); 75 66 76 - // non-schema fields to add 77 - addlFields: [ 78 - ], 79 - }, 80 - ] 67 + // Add features 68 + features.forEach((feature, i) => { 69 + const folder = gui.addFolder(feature.name); 70 + addToGUI(folder, 'Description', feature.description).disable(); 71 + addToGUI(folder, 'Enabled', feature.enabled).onChange(e => { 72 + // TODO: validate new value against schema 73 + features[i].enabled = e; 74 + }); 75 + addToGUI(folder, 'Settings', () => { 76 + const title = `${feature.name} - Settings`; 77 + openSettingsAddress(title, feature.settings_url); 78 + }).disable(!feature.enabled); 79 + }); 81 80 }; 82 81 83 - const init = () => { 82 + const openSettingsAddress = (title, address) => { 83 + const params = { 84 + feature: title, 85 + file: address, 86 + }; 87 + 88 + window.app.openWindow(params, () => window.app.log(title, 'settings win opened', address)); 89 + } 90 + 91 + const addToGUI = (gui, label, value, disabled = false, step = null, max = null) => { 92 + const params = {}; 93 + params[label] = value; 84 94 85 - /* 86 - pubsub.publish('open', { 87 - feature: msg.prefs.startupFeature 88 - }); 89 - */ 95 + const ctr = gui.add(params, label); 90 96 91 97 /* 92 - items.forEach(item => { 93 - item.settings = 'button'; 94 - }); 98 + if (disabled == true) { 99 + ctr.disable(); 100 + } 101 + 102 + if (max != null) { 103 + ctr.max(max); 104 + } 105 + 106 + if (step != null) { 107 + ctr.step(step); 108 + } 95 109 */ 96 110 97 - settingsPane(container, ui, labels, schemas, prefs, items, onChange); 98 - 99 - }; 111 + return ctr; 112 + } 100 113 101 114 window.addEventListener('load', init);
+5 -9
features/groups/background.js
··· 1 1 // groups/background.js 2 2 3 - import { id, labels, schemas, ui, defaults } from './config.js'; 3 + import { id, labels, schemas, storageKeys, defaults } from './config.js'; 4 4 import { log as l, openStore } from "../utils.js"; 5 5 6 6 const log = function(...args) { l(id, args); }; 7 + 8 + log('background', id); 7 9 8 10 const debug = window.app.debug; 9 - 10 - log('background'); 11 + const clear = false; 11 12 12 - const store = openStore(id, defaults); 13 + const store = openStore(id, defaults, clear /* clear storage */); 13 14 const api = window.app; 14 - 15 - const storageKeys = { 16 - PREFS: 'prefs', 17 - ITEMS: 'items', 18 - }; 19 15 20 16 const openGroupsWindow = () => { 21 17 const height = 600;
+7 -72
features/groups/config.js
··· 1 1 const id = 'features/groups'; 2 + const guid = '82de735f-a4b7-4fe6-a458-ec29939ae00d'; 2 3 3 4 const labels = { 4 5 featureType: 'groups', ··· 24 25 "required": [ "shortcutKey"] 25 26 }; 26 27 27 - /* 28 - const itemSchema = { 29 - "$schema": "https://json-schema.org/draft/2020-12/schema", 30 - "$id": "peek.groups.entry.schema.json", 31 - "title": "Peek - command entry", 32 - "description": "Peek command entry", 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" ] 28 + const schemas = { 29 + prefs: prefsSchema, 80 30 }; 81 31 82 - const listSchema = { 83 - type: 'array', 84 - items: { "$ref": "#/$defs/entry" } 85 - }; 86 - */ 87 - 88 - const schemas = { 89 - prefs: prefsSchema, 90 - //item: itemSchema, 91 - //items: listSchema 32 + const storageKeys = { 33 + PREFS: 'prefs', 92 34 }; 93 35 94 36 const defaults = { ··· 97 39 }, 98 40 }; 99 41 100 - // ui config 101 - const ui = { 102 - // allow user to create new items 103 - allowNew: false, 104 - // fields that are view only 105 - disabled: ['keyNum'], 106 - }; 107 - 108 42 export { 109 43 id, 44 + guid, 110 45 labels, 111 46 schemas, 112 - ui, 47 + storageKeys, 113 48 defaults 114 49 };
+7 -10
features/peeks/background.js
··· 1 1 // peeks/background.js 2 2 3 - import { id, labels, schemas, ui, defaults } from './config.js'; 3 + import { id, labels, schemas, storageKeys, defaults } from './config.js'; 4 4 import { log as l, openStore } from "../utils.js"; 5 5 6 6 const log = function(...args) { l(id, args); }; 7 7 8 - log('background'); 8 + log('background', id); 9 9 10 10 const debug = window.app.debug; 11 + const clear = false; 11 12 12 - const store = openStore(id, defaults); 13 + const store = openStore(id, defaults, clear /* clear storage */); 13 14 const api = window.app; 14 15 15 - const storageKeys = { 16 - PREFS: 'prefs', 17 - ITEMS: 'items', 18 - }; 19 - 20 16 const executeItem = (item) => { 21 17 const height = item.height || 600; 22 18 const width = item.width || 800; ··· 39 35 40 36 const initItems = (prefs, items) => { 41 37 const cmdPrefix = prefs.shortcutKeyPrefix; 38 + console.log('inititems', items); 42 39 43 40 items.forEach(item => { 44 - if (item.enabled == true) { 41 + if (item.enabled == true && item.address.length > 0) { 45 42 const shortcut = `${cmdPrefix}${item.keyNum}`; 46 43 47 44 api.shortcuts.register(shortcut, () => { ··· 57 54 const prefs = () => store.get(storageKeys.PREFS); 58 55 const items = () => store.get(storageKeys.ITEMS); 59 56 60 - // initialize slides 57 + // initialize peeks 61 58 if (items().length > 0) { 62 59 initItems(prefs(), items()); 63 60 }
+12 -11
features/peeks/config.js
··· 1 1 const id = 'features/peeks'; 2 + const guid = 'ef3bd271-d408-421f-9338-47b615571e43'; 2 3 3 4 const labels = { 4 5 featureType: 'peeks', ··· 97 98 items: listSchema 98 99 }; 99 100 101 + const storageKeys = { 102 + PREFS: 'prefs', 103 + ITEMS: 'items', 104 + }; 105 + 100 106 const defaults = { 101 107 prefs: { 102 108 shortcutKeyPrefix: 'Option+' ··· 105 111 }; 106 112 107 113 for (var i = 0; i != 10; i++) { 114 + const address = i == 0 ? 'https://example.com/' : ''; 115 + const enabled = i == 0 ? true : false; 108 116 defaults.items[i] = { 109 117 keyNum: i, 110 118 title: `Peek key ${i}`, 111 - address: 'https://example.com/', 119 + address: address, 112 120 persistState: false, 113 121 keepLive: false, 114 122 allowSound: false, 115 123 height: 600, 116 124 width: 800, 117 - enabled: false, 125 + enabled: enabled, 118 126 }; 119 127 } 120 128 121 - // ui config 122 - const ui = { 123 - // allow user to create new items 124 - allowNew: false, 125 - // fields that are view only 126 - disabled: ['keyNum'], 127 - }; 128 - 129 129 export { 130 130 id, 131 + guid, 131 132 labels, 132 133 schemas, 133 - ui, 134 + storageKeys, 134 135 defaults 135 136 };
+9 -12
features/scripts/background.js
··· 1 1 // scripts/background.js 2 2 3 - import { id, labels, schemas, ui, defaults } from './config.js'; 3 + import { id, labels, schemas, storageKeys, defaults } from './config.js'; 4 4 import { log as l, openStore } from "../utils.js"; 5 5 6 6 const log = function(...args) { l(id, args); }; 7 7 8 - log('background'); 8 + log('background', id); 9 9 10 10 const debug = window.app.debug; 11 + const clear = false; 11 12 12 - const store = openStore(id, defaults); 13 + const store = openStore(id, defaults, clear /* clear storage */); 13 14 const api = window.app; 14 15 15 - const storageKeys = { 16 - PREFS: 'prefs', 17 - ITEMS: 'items', 18 - }; 19 - 20 16 let _intervals = []; 21 17 22 18 const executeItem = (script, cb) => { ··· 43 39 }; 44 40 45 41 const initItems = (prefs, items) => { 46 - // blow it all away for now 47 - // someday make it right proper just cancel/update changed and add new 42 + // blow it all away for now at module start 43 + // someday make it right proper 44 + // just cancel/update changed and add new 48 45 _intervals.forEach(clearInterval); 49 46 50 47 // debounce me somehow so not shooting em all off ··· 54 51 const interval = setInterval(() => { 55 52 const r = executeItem(item, res => { 56 53 57 - //log('script result for', item.title, JSON.stringify(res)); 58 - //log('script prev val', item.previousValue); 54 + log('script result for', item.title, JSON.stringify(res)); 55 + log('script prev val', item.previousValue); 59 56 60 57 if (item.previousValue != res) { 61 58
+8 -10
features/scripts/config.js
··· 1 1 const id = 'features/scripts'; 2 + const guid = '30c25027-d367-4595-b37f-9db3de853c37'; 2 3 3 4 const labels = { 4 5 featureType: 'scripts', ··· 93 94 items: { "$ref": "#/$defs/script" } 94 95 }; 95 96 96 - // TODO: schemaize 0-9 constraints for peeks 97 97 const schemas = { 98 98 prefs: prefsSchema, 99 99 item: itemSchema, 100 100 items: listSchema 101 + }; 102 + 103 + const storageKeys = { 104 + PREFS: 'prefs', 105 + ITEMS: 'items', 101 106 }; 102 107 103 108 const defaults = { ··· 119 124 ] 120 125 }; 121 126 122 - // ui config 123 - const ui = { 124 - // allow user to create new items 125 - allowNew: false, 126 - // fields that are view only 127 - disabled: ['screenEdge'], 128 - }; 129 - 130 127 export { 131 128 id, 129 + guid, 132 130 labels, 133 131 schemas, 134 - ui, 132 + storageKeys, 135 133 defaults 136 134 };
+6 -10
features/slides/background.js
··· 1 1 // slides/slides.js 2 2 3 - import { id, labels, schemas, ui, defaults } from './config.js'; 3 + import { id, labels, schemas, storageKeys, defaults } from './config.js'; 4 4 import { log as l, openStore } from "../utils.js"; 5 5 6 6 const log = function(...args) { l(id, args); }; 7 7 8 - log('background'); 8 + log('background', id); 9 9 10 10 const debug = window.app.debug; 11 + const clear = false; 11 12 12 - const store = openStore(id, defaults); 13 + const store = openStore(id, defaults, clear /* clear storage */); 13 14 const api = window.app; 14 - 15 - const storageKeys = { 16 - PREFS: 'prefs', 17 - ITEMS: 'items', 18 - }; 19 15 20 16 const executeItem = (item) => { 21 17 let height = item.height || 600; ··· 115 111 const cmdPrefix = prefs.shortcutKeyPrefix; 116 112 117 113 items.forEach(item => { 118 - //if (item.enabled == true) { 114 + if (item.enabled == true && item.address.length > 0) { 119 115 const shortcut = `${cmdPrefix}${item.screenEdge}`; 120 116 121 117 api.shortcuts.register(shortcut, () => { 122 118 executeItem(item); 123 119 }); 124 - //} 120 + } 125 121 }); 126 122 }; 127 123
+12 -13
features/slides/config.js
··· 1 1 const id = 'features/slides'; 2 + const guid = '434108f3-18a6-437a-b507-2f998f693bb2'; 2 3 3 4 const labels = { 4 5 featureType: 'slides', ··· 100 101 items: listSchema 101 102 }; 102 103 103 - // ui config 104 - const ui = { 105 - // allow user to create new items 106 - allowNew: false, 107 - // fields that are view only 108 - disabled: ['screenEdge'], 104 + const storageKeys = { 105 + PREFS: 'prefs', 106 + ITEMS: 'items', 109 107 }; 110 108 111 109 const defaults = { ··· 127 125 { 128 126 screenEdge: 'Down', 129 127 title: 'Slide from bottom', 130 - address: 'http://localhost/', 128 + address: '', 131 129 persistState: false, 132 130 keepLive: false, 133 131 allowSound: false, 134 132 height: 600, 135 133 width: 800, 136 - enabled: true, 134 + enabled: false, 137 135 }, 138 136 { 139 137 screenEdge: 'Left', 140 138 title: 'Slide from left', 141 - address: 'http://localhost/', 139 + address: '', 142 140 persistState: false, 143 141 keepLive: false, 144 142 allowSound: false, 145 143 height: 600, 146 144 width: 800, 147 - enabled: true, 145 + enabled: false, 148 146 }, 149 147 { 150 148 screenEdge: 'Right', 151 149 title: 'Slide from right', 152 - address: 'http://localhost/', 150 + address: '', 153 151 persistState: false, 154 152 keepLive: false, 155 153 allowSound: false, 156 154 height: 600, 157 155 width: 800, 158 - enabled: true, 156 + enabled: false, 159 157 }, 160 158 ] 161 159 }; 162 160 163 161 export { 164 162 id, 163 + guid, 165 164 labels, 166 165 schemas, 167 - ui, 166 + storageKeys, 168 167 defaults 169 168 };
+9 -182
features/utils.js
··· 15 15 window.app.log(source, str); 16 16 }; 17 17 18 - const openStore = (prefix, defaults) => { 18 + const openStore = (prefix, defaults, clear = false) => { 19 19 20 20 //log(id, 'openStore', prefix, (defaults ? Object.keys(defaults) : '')); 21 + 22 + // multiple contexts 23 + const keyify = k => `${prefix}+${k}`; 21 24 22 25 // Simple localStorage abstraction/wrapper 23 26 const store = { ··· 42 45 store.clear(); 43 46 } 44 47 45 - //store.clear(); 46 - 47 - // multiple contexts 48 - const keyify = k => `${prefix}+${k}`; 48 + if (clear) { 49 + store.clear(); 50 + } 49 51 50 52 const initStore = (store, data) => { 51 53 Object.keys(data).forEach(k => { ··· 57 59 }); 58 60 }; 59 61 60 - if (defaults) { 62 + if (defaults != null) { 61 63 //log('UTILS/openStore()', 'initing'); 62 64 initStore(store, defaults); 63 65 } ··· 65 67 return store; 66 68 }; 67 69 68 - const settingsPane = (container, config, labels, schemas, prefs, items, onChange) => { 69 - log('settingsPane()'); 70 - 71 - const paneContainer = document.createElement('div'); 72 - container.appendChild(paneContainer); 73 - 74 - const allowNew = config.allowNew || false; 75 - const disabled = config.disabled || []; 76 - 77 - const pane = new Pane({ 78 - container: paneContainer, 79 - title: labels.featureDisplay 80 - }); 81 - 82 - const update = (all) => { 83 - const paneData = exportPaneData(pane); 84 - 85 - log('folder level update for', labels.featureDisplay); 86 - log('pane data', paneData); 87 - 88 - let updated = {}; 89 - 90 - // TODO: make this right, ugh 91 - if (prefs) { 92 - updated.prefs = paneData.shift(); 93 - } 94 - 95 - // remove "new item" entry if not editable feature 96 - // TODO: make this right 97 - if (!all) { 98 - newData.pop(); 99 - } 100 - 101 - if (paneData.length > 0) { 102 - updated.items = paneData; 103 - } 104 - 105 - onChange(updated); 106 - }; 107 - 108 - // prefs pane 109 - if (prefs) { 110 - const prefsFolder = pane.addFolder({ 111 - title: schemas.prefs.title, 112 - expanded: true 113 - }); 114 - 115 - /* 116 - const btn = pane.addButton({ 117 - title: 'clickme' 118 - }); 119 - btn.on('click', () => { 120 - alert('clickd'); 121 - }); 122 - */ 123 - 124 - const onPrefChange = changed => { 125 - log('initFeaturePane::onPrefChange', changed) 126 - update(!config.allowNew); 127 - }; 128 - 129 - fillPaneFromSchema(prefsFolder, labels, schemas.prefs, prefs, onPrefChange, []); 130 - } 131 - 132 - // add items 133 - if (items) { 134 - //log('adding items panes'); 135 - items.forEach(item => { 136 - const folder = pane.addFolder({ 137 - title: item.title, 138 - expanded: false 139 - }); 140 - 141 - fillPaneFromSchema(folder, labels, schemas.item, item, update, config.disabled); 142 - 143 - // TODO: implement 144 - //folder.addButton({title: labels.testBtn}); 145 - 146 - if (config.allowNew) { 147 - const delBtn = folder.addButton({title: labels.delBtn}); 148 - delBtn.on('click', () => { 149 - pane.remove(folder); 150 - // TODO: https://github.com/cocopon/tweakpane/issues/533 151 - update(); 152 - }); 153 - } 154 - 155 - //folder.on('change', () => update(!config.allowNew)); 156 - }); 157 - } 158 - 159 - /* 160 - if (config.allowNew) { 161 - // add new item entry 162 - const folder = pane.addFolder({ 163 - title: labels.newFolder, 164 - expanded: false 165 - }); 166 - 167 - //fillPaneFromSchema(folder, labels, schema); 168 - fillPaneFromSchema(folder, labels, schema, {}, onChange, disabled); 169 - 170 - const btn = pane.addButton({title: labels.addBtn}); 171 - 172 - // handle adds of new entries 173 - btn.on('click', () => { 174 - update(true); 175 - }); 176 - } 177 - */ 178 - }; 179 - 180 - const fillPaneFromSchema = (pane, labels, schema, data, onChange, disabled) => { 181 - const props = schema.properties; 182 - 183 - Object.keys(props).forEach(k => { 184 - // TODO: unhack 185 - if (k == 'settingsAddress') { 186 - log('sa', data[k], data); 187 - //log('settingsAddress', k, 'v', data[k]); 188 - const btn = pane.addButton({title: k}); 189 - 190 - btn.on('click', () => { 191 - console.log('settings click!') 192 - const address = data[k]; 193 - 194 - const params = { 195 - debug: window.app.debug, 196 - feature: labels.featureType, 197 - file: address, 198 - }; 199 - 200 - window.app.publish('open', { 201 - feature: 'feature/cmd/settings' 202 - }); 203 - }); 204 - } 205 - else { 206 - // schema for property 207 - const s = props[k]; 208 - 209 - // value (or default) 210 - const v = 211 - (data && data.hasOwnProperty(k)) 212 - ? data[k] 213 - : props[k].default; 214 - 215 - const params = {}; 216 - const opts = {}; 217 - 218 - // dedecimalize 219 - if (s.type == 'integer') { 220 - opts.step = 1; 221 - } 222 - 223 - // disabled fields 224 - if (disabled.includes(k)) { 225 - opts.disabled = true; 226 - } 227 - 228 - params[k] = v; 229 - 230 - const input = pane.addBinding(params, k, opts); 231 - 232 - // TODO: consider inline state management 233 - input.on('change', ev => { 234 - // TODO: validate against schema 235 - log('inline field change', k, ev.value) 236 - data[k] = ev.value; 237 - onChange(data) 238 - }); 239 - } 240 - }); 241 - }; 242 - 243 70 /* 244 71 const paneGenerator = (pane, labels, schema, data, onChange, disabled) => { 245 72 const schemaKeys = Object.keys(schema.properties); ··· 349 176 export { 350 177 log, 351 178 openStore, 352 - settingsPane 179 + //settingsPane 353 180 };
+9 -6
index.js
··· 355 355 // 356 356 // TODO: need to figure out a better approach 357 357 const key = params.key ? params.key : (params.feature + (params.address || params.file)); 358 + console.log('openWindow', 'cache key', key); 358 359 359 360 if (windowCache.hasKey(key)) { 360 361 console.log('REUSING WINDOW for ', key) ··· 369 370 return; 370 371 } 371 372 } 373 + } 374 + else { 375 + console.log('KEY NOT IN CACHE'); 372 376 } 373 377 374 378 console.log('openWindow(): creating new window'); ··· 414 418 let win = new BrowserWindow(winPreferences); 415 419 416 420 // if persisting window, cache the caller's key and window id 417 - if (params.keepLive == true) { 421 + if (params.keepLive == true || DEBUG) { 418 422 windowCache.add({ 419 423 id: win.id, 420 424 key, ··· 426 430 const onGoAway = () => { 427 431 if (params.keepLive == true) { 428 432 if (params.keepVisible == false) { 429 - console.log('win.onGoAway(): hiding ', params.address); 433 + console.log('main.onGoAway(): hiding ', params.address); 430 434 win.hide(); 431 435 } 436 + // else keep window alive and visible! 432 437 } 433 438 else { 434 439 console.log('win.onGoAway(): destroying ', params.address); ··· 436 441 } 437 442 } 438 443 439 - // don't do this in debug mode, devtools steals focus 444 + // don't do this in detached debug mode, devtools steals focus 440 445 // and closes everything 😐 441 446 // TODO: fix 442 447 // TODO: should be configurable behavior 443 - if (!DEBUG) { 444 - win.on('blur', onGoAway); 445 - } 448 + win.on('blur', onGoAway); 446 449 447 450 win.on('close', onGoAway); 448 451
+1
package.json
··· 21 21 "electron-squirrel-startup": "1.0.0", 22 22 "electron-store": "8.1.0", 23 23 "electron-unhandled": "4.0.1", 24 + "lil-gui": "^0.19.2", 24 25 "tweakpane": "4.0.1" 25 26 }, 26 27 "scripts": {