From b0230386481fc980baa99ada5ace49908a1dabe5 Mon Sep 17 00:00:00 2001 From: Gordon Stein <7331488+gsteinLTU@users.noreply.github.com> Date: Wed, 16 Nov 2022 17:53:53 -0600 Subject: [PATCH 1/6] Add options menu --- src/extensions.js | 4 ++++ src/gui-ext.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/extensions.js b/src/extensions.js index a599e4dc92..504e2c7b2b 100644 --- a/src/extensions.js +++ b/src/extensions.js @@ -162,6 +162,10 @@ return null; }; + Extension.prototype.getPreferences = function() { + return null; + }; + Extension.prototype.getCategories = function() { return []; }; diff --git a/src/gui-ext.js b/src/gui-ext.js index 322ddb229b..632e95572d 100644 --- a/src/gui-ext.js +++ b/src/gui-ext.js @@ -482,7 +482,61 @@ IDE_Morph.prototype.extensionsMenu = function() { return menu; }; - return menuFromDict(dict); + let menu = menuFromDict(dict); + + const on = new SymbolMorph( + 'checkedBox', + MorphicPreferences.menuFontSize * 0.75 + ), + off = new SymbolMorph( + 'rectangle', + MorphicPreferences.menuFontSize * 0.75 + ); + + // Add preferences + this.extensions.registry + .filter(ext => ext.getPreferences()) + .forEach(ext => { + const name = ext.name || ext.constructor.name; + let thisExtMenu = menu.items.find(item => item[0] == name); + + let prefs = ext.getPreferences(); + + if(thisExtMenu){ + thisExtMenu = thisExtMenu[1]; + + // Only show menu if there is a non-hidden option available + if(prefs.find(pref => !pref.hide || world.currentKey == 16) !== undefined){ + let newOptionsMenu = new MenuMorph(this); + thisExtMenu.addMenu('Options', newOptionsMenu); + + // Add each preference as a toggle + prefs.forEach(pref => { + + let test = pref.test; + + // Allow test to be boolean or function + if(typeof(test) == 'function'){ + test = test(); + } + + if (!pref.hide || world.currentKey == 16) { + newOptionsMenu.addItem( + [ + (test? on : off), + pref.label + ], + pref.toggle, + test ? pref.onHint : pref.offHint, + pref.hide ? new Color(100, 0, 0) : null + ); + } + }); + } + } + }); + + return menu; }; IDE_Morph.prototype.requestProjectReload = async function (reason) { From b4b202b92143dc3a5940da55c3559b9039f0d111 Mon Sep 17 00:00:00 2001 From: Gordon Stein <7331488+gsteinLTU@users.noreply.github.com> Date: Tue, 13 Dec 2022 17:29:08 -0600 Subject: [PATCH 2/6] Default to empty list --- src/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions.js b/src/extensions.js index 504e2c7b2b..e83f0d2bd8 100644 --- a/src/extensions.js +++ b/src/extensions.js @@ -163,7 +163,7 @@ }; Extension.prototype.getPreferences = function() { - return null; + return []; }; Extension.prototype.getCategories = function() { From bcf5af0edf1170af385d1f6df786017eb6e8774f Mon Sep 17 00:00:00 2001 From: Gordon Stein <7331488+gsteinLTU@users.noreply.github.com> Date: Tue, 13 Dec 2022 17:29:28 -0600 Subject: [PATCH 3/6] Expect test to be function --- src/gui-ext.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/gui-ext.js b/src/gui-ext.js index 632e95572d..6bee4ff6ba 100644 --- a/src/gui-ext.js +++ b/src/gui-ext.js @@ -514,20 +514,15 @@ IDE_Morph.prototype.extensionsMenu = function() { prefs.forEach(pref => { let test = pref.test; - - // Allow test to be boolean or function - if(typeof(test) == 'function'){ - test = test(); - } if (!pref.hide || world.currentKey == 16) { newOptionsMenu.addItem( [ - (test? on : off), + (test() ? on : off), pref.label ], pref.toggle, - test ? pref.onHint : pref.offHint, + test() ? pref.onHint : pref.offHint, pref.hide ? new Color(100, 0, 0) : null ); } From 112208fdfc7cae09952cdfdd9f3f6f014a02806e Mon Sep 17 00:00:00 2001 From: Gordon Stein <7331488+gsteinLTU@users.noreply.github.com> Date: Tue, 13 Dec 2022 17:47:39 -0600 Subject: [PATCH 4/6] Rename to settings and add helpers --- src/extensions.js | 25 ++++++++++++++++++++++++- src/gui-ext.js | 6 +++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/extensions.js b/src/extensions.js index e83f0d2bd8..07f5ce7a70 100644 --- a/src/extensions.js +++ b/src/extensions.js @@ -162,7 +162,7 @@ return null; }; - Extension.prototype.getPreferences = function() { + Extension.prototype.getSettings = function() { return []; }; @@ -186,6 +186,29 @@ Extension.prototype.onOpenRole = function() { }; + class ExtensionSetting { + constructor(label, toggle, test, onHint = '', offHint = '', hide = false) { + this.label = label; + this.toggle = toggle, + this.test = test, + this.onHint = onHint, + this.offHint = offHint, + this.hide = hide + } + } + + ExtensionSetting.createFromLocalStorage = function(label, id, defaultValue = false, onHint = '', offHint = '', hide = false){ + return new ExtensionSetting( + label, + () => { + window.localStorage.setItem(id, !(window.localStorage.getItem(id) ?? defaultValue)); + }, + () => window.localStorage.getItem(id) ?? defaultValue, + onHint, offHint, hide); + } + + Extension.ExtensionSetting = ExtensionSetting; + class LabelPart { constructor(spec, fn) { if (spec[0] !== '%') { diff --git a/src/gui-ext.js b/src/gui-ext.js index 6bee4ff6ba..8414e13e77 100644 --- a/src/gui-ext.js +++ b/src/gui-ext.js @@ -495,12 +495,12 @@ IDE_Morph.prototype.extensionsMenu = function() { // Add preferences this.extensions.registry - .filter(ext => ext.getPreferences()) + .filter(ext => ext.getSettings()) .forEach(ext => { const name = ext.name || ext.constructor.name; let thisExtMenu = menu.items.find(item => item[0] == name); - let prefs = ext.getPreferences(); + let prefs = ext.getSettings(); if(thisExtMenu){ thisExtMenu = thisExtMenu[1]; @@ -510,7 +510,7 @@ IDE_Morph.prototype.extensionsMenu = function() { let newOptionsMenu = new MenuMorph(this); thisExtMenu.addMenu('Options', newOptionsMenu); - // Add each preference as a toggle + // Add each setting as a toggle prefs.forEach(pref => { let test = pref.test; From 5e542a8698de9f7c621e25a865bfcf2533138811 Mon Sep 17 00:00:00 2001 From: Gordon Stein <7331488+gsteinLTU@users.noreply.github.com> Date: Tue, 13 Dec 2022 18:24:41 -0600 Subject: [PATCH 5/6] Add test for menu item and settings --- test/extensions.spec.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test/extensions.spec.js b/test/extensions.spec.js index 5a97e3cc53..a9abaeb9d8 100644 --- a/test/extensions.spec.js +++ b/test/extensions.spec.js @@ -8,9 +8,16 @@ describe('extensions', function() { TestExtension.prototype = new Extension('TestExt'); TestExtension.prototype.getMenu = () => { return { - 'hello!': function() {}, + 'TestMenuItem': function() {}, }; }; + TestExtension.prototype.getSettings = () => { + return [new Extension.ExtensionSetting( + 'Test Setting', + () => {}, + () => false + )]; + }; TestExtension.prototype.getCategories = () => [ new Extension.Category( 'TEST!', @@ -107,6 +114,27 @@ describe('extensions', function() { driver.dialog().destroy(); }); + it('should create menu item', function() { + driver.click(driver.ide().controlBar.extensionsButton); + driver.click(driver.dialog().children[1]); + const subMenuItems = driver.dialog().children[2].children.map(c => c.labelString); + assert(subMenuItems.includes('TestMenuItem')); + driver.dialog().destroy(); + }); + + it('should create settings', function() { + driver.click(driver.ide().controlBar.extensionsButton); + driver.click(driver.dialog().children[1]); + const subMenuItems = driver.dialog().children[2].children.map(c => c.labelString); + assert(subMenuItems.includes('Options')); + driver.click(driver.dialog().children[2].children[2]); + + const optionMenuItems = driver.dialog().children[2].children[3].children.map(c => c.labelString); + assert(optionMenuItems.find(item => item && item[1] == 'Test Setting')); + + driver.dialog().destroy(); + }); + it('should not load an extension twice', function() { const {NetsBloxExtensions} = driver.globals(); const extCount = NetsBloxExtensions.registry.length; From eace72b9bb73fd22338a7d553e6ae47e3e60f653 Mon Sep 17 00:00:00 2001 From: Gordon Stein <7331488+gsteinLTU@users.noreply.github.com> Date: Tue, 13 Dec 2022 18:29:15 -0600 Subject: [PATCH 6/6] Fix other failing tests --- test/extensions.spec.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/extensions.spec.js b/test/extensions.spec.js index a9abaeb9d8..60cc62d37d 100644 --- a/test/extensions.spec.js +++ b/test/extensions.spec.js @@ -171,15 +171,16 @@ describe('extensions', function() { driver.selectCategory('TEST!'); assert.equal( driver.palette().contents.children.length, - 2 + 5 ); }); it('should show new blocks on the stage', function() { driver.selectStage(); driver.selectCategory('TEST!'); - assert( - driver.palette().contents.children.length > 1 + assert.equal( + driver.palette().contents.children.length, + 4 ); }); @@ -211,7 +212,7 @@ describe('extensions', function() { driver.selectCategory('TEST!'); const block = driver.palette().contents.children.find(child => child.selector === 'spriteBlock'); const [inputSlot] = block.inputs(); - assert.equal(inputSlot.evaluate(), 'this is a second test'); + assert(Object.keys(inputSlot.choices).includes('this is a second test')); }); it('should hide sprite block on stage', function() {