Skip to content

Commit

Permalink
customToggles: Refactor, add multicasting, add CLI (honestbleeps#4730)
Browse files Browse the repository at this point in the history
  • Loading branch information
larsjohnsen committed Apr 8, 2018
1 parent caf8790 commit 7b6ad4a
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 65 deletions.
142 changes: 91 additions & 51 deletions lib/modules/customToggles.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/* @flow */

import _ from 'lodash';
import { $ } from '../vendor';
import { Module } from '../core/module';
import * as Modules from '../core/modules';
import * as Options from '../core/options';
import { CreateElement, indexOptionTable } from '../utils';
import { CreateElement } from '../utils';
import { multicast } from '../environment';
import * as CommandLine from './commandLine';
import * as Menu from './menu';

export const module: Module<*> = new Module('customToggles');
Expand All @@ -20,83 +20,123 @@ module.options = {
title: 'customTogglesToggleTitle',
type: 'table',
fields: [{
key: 'name',
name: 'name',
key: 'key',
name: 'key',
type: 'text',
}, {
key: 'enabled',
name: 'enabled',
type: 'boolean',
value: true,
}, {
key: 'menuItem',
name: 'menuItem',
key: 'text',
name: 'text',
type: 'text',
}],
value: ([]: Array<[string, boolean, string]>),
},
};

const toggles: Map<string, Toggle> = new Map();

module.beforeLoad = () => {
for (const instance of module.options.toggle.value) {
const [key, initialEnabled, text] = instance;

if (toggles.has(key)) {
console.error(`A toggle with key ${key} already exists`, instance);
}

const toggle = new Toggle(key, text, initialEnabled);

toggle.onStateChange(() => {
// Keep the settings data current to avoid overwriting modifications from other tabs
instance[1] = toggle.enabled;
});

toggle.onToggle(() => {
Options.set(module, 'toggle', module.options.toggle.value);
});
}
};

module.go = () => {
createToggleMenuItems();
registerCommandLine();

for (const toggle of toggles.values()) {
toggle.addMenuItem();
}
};

const togglesByName = _.once(() => indexOptionTable(module.options.toggle, 0));
const togglesByMenuItem = _.once(() => indexOptionTable(module.options.toggle, 2));
export class Toggle {
text: string;
enabled: boolean;

function anyTogglesEnabled(toggles) {
return toggles
.map(([, enabled]) => enabled)
.some(enabled => enabled);
}
stateChangeCallbacks: Array<() => void> = []; // Invoked on all tabs
toggleCallbacks: Array<() => void> = []; // Invoked on the tab which caused the change

export function toggleActive(name?: string): boolean {
if (!name || !Modules.isRunning(module)) return false;
constructor(key: string, text: *, enabled: *) {
this.text = text;
this.enabled = enabled;

const toggles = togglesByName()[name];
return !!toggles && anyTogglesEnabled(toggles);
}
const setGlobalState = multicast(enabled => {
this.setLocalState(enabled);
}, { name: `toggle.${key}` });

function createToggleMenuItems() {
for (const [menuItemId, toggles] of Object.entries(togglesByMenuItem())) {
const $toggle = $('<div>', { text: menuItemId || '\u00A0' /* nbsp */, title: `Toggle ${menuItemId}` })
.append(CreateElement.toggleButton(undefined, menuItemId, anyTogglesEnabled(toggles)));
this.onToggle(() => { setGlobalState(this.enabled); });

Menu.addMenuItem($toggle, function() { onClickToggleMenuItem(this, menuItemId); });
toggles.set(key, this);
}
}

function onClickToggleMenuItem(menuItem, menuItemID) {
const enabled = toggleMenuItem(menuItemID);
$(menuItem).find('.toggleButton').toggleClass('enabled', enabled);
}
toggle() {
this.setLocalState(!this.enabled);

function toggleMenuItem(menuItemID) {
const toggles = togglesByMenuItem()[menuItemID];
return toggleToggle(toggles);
}
for (const callback of this.toggleCallbacks) callback();
}

setLocalState(enabled: boolean) {
this.enabled = enabled;

// For modules which update dynamically
$(module).trigger($.Event('toggle')); // eslint-disable-line new-cap

function toggleToggle(toggles) {
const newEnabled = !anyTogglesEnabled(toggles);
for (const callback of this.stateChangeCallbacks) callback();
}

// Update cached settings
toggles.forEach(toggle => { toggle[1] = newEnabled; });
onStateChange(callback: *) {
this.stateChangeCallbacks.push(callback);
}

// Update settings in storage
module.options.toggle.value
.filter(toggle => toggles.some(t => t[0] === toggle[0]))
.forEach(toggle => { toggle[1] = newEnabled; });
onToggle(callback: *) {
this.toggleCallbacks.push(callback);
}

Options.set(module, 'toggle', module.options.toggle.value);
addMenuItem(text: string = this.text, title: string = `Toggle ${this.text}`, on?: string, off?: string) {
const $toggle = $('<div>', { text: text || '\u00A0' /* nbsp */, title })
.append(CreateElement.toggleButton(undefined, this.text, this.enabled, on, off));
this.onStateChange(() => { $toggle.find('.toggleButton').toggleClass('enabled', this.enabled); });
Menu.addMenuItem($toggle, () => { this.toggle(); });
}
}

// Notify listeners
toggles.forEach(([toggle]) => {
if (newEnabled) {
$(module).trigger($.Event('activated', { target: toggle })); // eslint-disable-line new-cap
} else {
$(module).trigger($.Event('deactivated', { target: toggle })); // eslint-disable-line new-cap
function registerCommandLine() {
const getToggles = val => Array.from(toggles.values())
.filter(({ text }) => text.startsWith(val))
.sort(({ text: a }, { text: b }) => a.localeCompare(b));

CommandLine.registerCommand('toggle', 'toggle - toggle any custom toggle',
(command, val) => getToggles(val).length ?
`Toggle ${getToggles(val).map((toggle, i) => i === 0 ? `<b>${toggle.text}</b>` : toggle.text).join('|')}` :
`No toggles matching <i>${val}</i>`,
(command, val) => {
const match = getToggles(val)[0];
if (match) match.toggle();
else return `${val} does not match a valid toggle`;
}
});
);
}

return newEnabled;
export function toggleActive(key: string): boolean {
const toggle = toggles.get(key);
return !!toggle && toggle.enabled;
}
12 changes: 5 additions & 7 deletions lib/modules/filteReddit/browseCases/Toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@
import { Case } from '../Case';
import * as CustomToggles from '../../customToggles';

function getToggles() {
return CustomToggles.module.options.toggle.value
.map(([name, , menuItem]) => [menuItem, name]);
}
const getOptions = () => CustomToggles.module.options.toggle.value.map(([key, , text]) => [text, key]);

export class Toggle extends Case {
static text = 'Custom toggle';

static defaultConditions = { toggleName: getToggles()[0] || '' };
static defaultConditions = { toggleName: getOptions()[0] || '' }; // TODO Migrate `toggleName` to `key`
static fields = [
'custom toggle ',
{ type: 'select', id: 'toggleName', get options() { return getToggles(); } },
{ type: 'select', id: 'toggleName', get options() { return getOptions(); } },
' is enabled',
];

evaluate() {
return CustomToggles.toggleActive(this.value.toggleName);
const key = this.value.toggleName;
return CustomToggles.toggleActive(key);
}
}
14 changes: 7 additions & 7 deletions lib/modules/stylesheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ module.options = {
type: 'list',
listType: 'subreddits',
}, {
key: 'toggleName',
name: 'toggleName',
key: 'toggleKey',
name: 'toggleKey',
type: 'text',
}],
},
Expand Down Expand Up @@ -103,8 +103,8 @@ module.options = {
type: 'list',
listType: 'subreddits',
}, {
key: 'toggleName',
name: 'toggleName',
key: 'toggleKey',
name: 'toggleKey',
type: 'text',
}],
},
Expand Down Expand Up @@ -138,8 +138,8 @@ module.options = {
type: 'list',
listType: 'subreddits',
}, {
key: 'toggleName',
name: 'toggleName',
key: 'toggleKey',
name: 'toggleKey',
type: 'text',
}],
},
Expand Down Expand Up @@ -180,7 +180,7 @@ module.beforeLoad = () => {
applyMultiredditClass();
}

$(CustomToggles.module).on('activated deactivated', applyStyles);
$(CustomToggles.module).on('toggle', applyStyles);
applyStyles();

function applyStyles() {
Expand Down

0 comments on commit 7b6ad4a

Please sign in to comment.