Feature selector (#4074)
* WIP * ParityBar verification * import from index.js * i18n expansion & tests * Features component * Adapt language selector to use features * Add features to settings view * typo * Convert logging * Fix earlier merge issues resulting in test failures * Lint failure fixes (new rules) * Fix additional listing rules * Re-add FormattedMessage (missing after merge), fix tests * Fix loader overrides * grumble: split item rendering (& test) * grumble: allow enable/disable while testing (default on) * grumble: move LanguageSelector below Features * grumble: don't pass visiblity prop (& update tests) * grumble: missing observable (onClick misbehaving) * grumble: don't reset to defaults per session * Fix to single store instance
This commit is contained in:
parent
5b2dd8deb2
commit
155bbc328f
27
js/src/i18n/constants.js
Normal file
27
js/src/i18n/constants.js
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
const DEFAULT_LOCALE = 'en';
|
||||
const DEFAULT_LOCALES = process.env.NODE_ENV === 'production'
|
||||
? ['en']
|
||||
: ['en', 'de'];
|
||||
const LS_STORE_KEY = '_parity::locale';
|
||||
|
||||
export {
|
||||
DEFAULT_LOCALE,
|
||||
DEFAULT_LOCALES,
|
||||
LS_STORE_KEY
|
||||
};
|
30
js/src/i18n/languages.spec.js
Normal file
30
js/src/i18n/languages.spec.js
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { DEFAULT_LOCALE } from './constants';
|
||||
import languages from './languages';
|
||||
|
||||
const keys = Object.keys(languages);
|
||||
|
||||
describe('i18n/languages', () => {
|
||||
it('has a language list', () => {
|
||||
expect(keys.length > 1).to.be.true;
|
||||
});
|
||||
|
||||
it('includes DEFAULT_LOCALE as a language', () => {
|
||||
expect(keys.includes(DEFAULT_LOCALE)).to.be.true;
|
||||
});
|
||||
});
|
@ -21,39 +21,32 @@ import de from 'react-intl/locale-data/de';
|
||||
import en from 'react-intl/locale-data/en';
|
||||
import store from 'store';
|
||||
|
||||
import { DEFAULT_LOCALE, DEFAULT_LOCALES, LS_STORE_KEY } from './constants';
|
||||
import languages from './languages';
|
||||
import deMessages from './de';
|
||||
import enMessages from './en';
|
||||
|
||||
const LS_STORE_KEY = '_parity::locale';
|
||||
|
||||
let instance = null;
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
const DEFAULT = 'en';
|
||||
const LANGUAGES = flatten({ languages });
|
||||
const MESSAGES = {
|
||||
de: Object.assign(flatten(deMessages), LANGUAGES),
|
||||
en: Object.assign(flatten(enMessages), LANGUAGES)
|
||||
};
|
||||
const LOCALES = isProduction
|
||||
? ['en']
|
||||
: ['en', 'de'];
|
||||
|
||||
addLocaleData([...de, ...en]);
|
||||
|
||||
export default class Store {
|
||||
@observable locale = DEFAULT;
|
||||
@observable locales = LOCALES;
|
||||
@observable messages = MESSAGES[DEFAULT];
|
||||
@observable isDevelopment = !isProduction;
|
||||
@observable locale = DEFAULT_LOCALE;
|
||||
@observable locales = DEFAULT_LOCALES;
|
||||
@observable messages = MESSAGES[DEFAULT_LOCALE];
|
||||
|
||||
constructor () {
|
||||
const savedLocale = store.get(LS_STORE_KEY);
|
||||
|
||||
this.locale = (savedLocale && LOCALES.includes(savedLocale))
|
||||
this.locale = (savedLocale && DEFAULT_LOCALES.includes(savedLocale))
|
||||
? savedLocale
|
||||
: DEFAULT;
|
||||
: DEFAULT_LOCALE;
|
||||
this.messages = MESSAGES[this.locale];
|
||||
}
|
||||
|
||||
|
73
js/src/i18n/store.spec.js
Normal file
73
js/src/i18n/store.spec.js
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import { DEFAULT_LOCALE, DEFAULT_LOCALES, LS_STORE_KEY } from './constants';
|
||||
import { LocaleStore } from './';
|
||||
|
||||
let localeStore;
|
||||
|
||||
describe('i18n/Store', () => {
|
||||
before(() => {
|
||||
localeStore = LocaleStore.get();
|
||||
store.set(LS_STORE_KEY, 'testing');
|
||||
});
|
||||
|
||||
it('creates a default instance', () => {
|
||||
expect(localeStore).to.be.ok;
|
||||
});
|
||||
|
||||
it('sets the default locale to default (invalid localStorage)', () => {
|
||||
expect(localeStore.locale).to.equal(DEFAULT_LOCALE);
|
||||
});
|
||||
|
||||
it('loads the locale from localStorage (valid localStorage)', () => {
|
||||
const testLocale = DEFAULT_LOCALES[DEFAULT_LOCALES.length - 1];
|
||||
|
||||
store.set(LS_STORE_KEY, testLocale);
|
||||
|
||||
const testStore = new LocaleStore();
|
||||
|
||||
expect(testStore.locale).to.equal(testLocale);
|
||||
});
|
||||
|
||||
it('lists the locales', () => {
|
||||
expect(localeStore.locales.length > 1).to.be.true;
|
||||
});
|
||||
|
||||
it('lists locals including default', () => {
|
||||
expect(localeStore.locales.includes(DEFAULT_LOCALE)).to.be.true;
|
||||
});
|
||||
|
||||
describe('@action', () => {
|
||||
describe('setLocale', () => {
|
||||
const testLocale = DEFAULT_LOCALES[DEFAULT_LOCALES.length - 1];
|
||||
|
||||
beforeEach(() => {
|
||||
localeStore.setLocale(testLocale);
|
||||
});
|
||||
|
||||
it('sets the locale as passed', () => {
|
||||
expect(localeStore.locale).to.equal(testLocale);
|
||||
});
|
||||
|
||||
it('sets the locale in localStorage', () => {
|
||||
expect(store.get(LS_STORE_KEY)).to.equal(testLocale);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
21
js/src/ui/Features/constants.js
Normal file
21
js/src/ui/Features/constants.js
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
const LS_STORE_KEY = '_parity::features';
|
||||
|
||||
export {
|
||||
LS_STORE_KEY
|
||||
};
|
61
js/src/ui/Features/defaults.js
Normal file
61
js/src/ui/Features/defaults.js
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
const MODES = {
|
||||
DEVELOPMENT: 1000, // only in dev mode, disabled by default, can be toggled
|
||||
TESTING: 1011, // feature is available in dev mode
|
||||
PRODUCTION: 1022 // feature is available
|
||||
};
|
||||
|
||||
const FEATURES = {
|
||||
LANGUAGE: 'LANGUAGE',
|
||||
LOGLEVELS: 'LOGLEVELS'
|
||||
};
|
||||
|
||||
const DEFAULTS = {
|
||||
[FEATURES.LANGUAGE]: {
|
||||
mode: MODES.TESTING,
|
||||
name: 'Language Selection',
|
||||
description: 'Allows changing the default interface language'
|
||||
},
|
||||
[FEATURES.LOGLEVELS]: {
|
||||
mode: MODES.TESTING,
|
||||
name: 'Logging Level Selection',
|
||||
description: 'Allows changing of the log levels for various components'
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
Object
|
||||
.keys(MODES)
|
||||
.forEach((mode) => {
|
||||
const key = `.${mode}`;
|
||||
|
||||
FEATURES[key] = key;
|
||||
DEFAULTS[key] = {
|
||||
mode: MODES[mode],
|
||||
name: key,
|
||||
description: key
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default DEFAULTS;
|
||||
|
||||
export {
|
||||
FEATURES,
|
||||
MODES
|
||||
};
|
67
js/src/ui/Features/defaults.spec.js
Normal file
67
js/src/ui/Features/defaults.spec.js
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import defaults, { FEATURES, MODES } from './defaults';
|
||||
|
||||
const features = Object.values(FEATURES);
|
||||
const modes = Object.values(MODES);
|
||||
|
||||
describe('ui/Features/Defaults', () => {
|
||||
describe('feature codes', () => {
|
||||
Object.keys(FEATURES).forEach((key) => {
|
||||
describe(key, () => {
|
||||
let value;
|
||||
|
||||
beforeEach(() => {
|
||||
value = FEATURES[key];
|
||||
});
|
||||
|
||||
it('exists as an default', () => {
|
||||
expect(defaults[value]).to.be.ok;
|
||||
});
|
||||
|
||||
it('has a single unique code', () => {
|
||||
expect(features.filter((code) => code === value).length).to.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaults', () => {
|
||||
Object.keys(defaults).forEach((key) => {
|
||||
describe(key, () => {
|
||||
let value;
|
||||
|
||||
beforeEach(() => {
|
||||
value = defaults[key];
|
||||
});
|
||||
|
||||
it('exists as an exposed feature', () => {
|
||||
expect(features.includes(key)).to.be.ok;
|
||||
});
|
||||
|
||||
it('has a valid mode', () => {
|
||||
expect(modes.includes(value.mode)).to.be.true;
|
||||
});
|
||||
|
||||
it('has a name and description', () => {
|
||||
expect(value.description).to.be.ok;
|
||||
expect(value.name).to.be.ok;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
20
js/src/ui/Features/features.css
Normal file
20
js/src/ui/Features/features.css
Normal file
@ -0,0 +1,20 @@
|
||||
/* Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.description {
|
||||
opacity: 0.75;
|
||||
}
|
69
js/src/ui/Features/features.js
Normal file
69
js/src/ui/Features/features.js
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Checkbox } from 'material-ui';
|
||||
import { observer } from 'mobx-react';
|
||||
import { List, ListItem } from 'material-ui/List';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import defaults, { MODES } from './defaults';
|
||||
import Store from './store';
|
||||
import styles from './features.css';
|
||||
|
||||
@observer
|
||||
export default class Features extends Component {
|
||||
store = Store.get();
|
||||
|
||||
render () {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<List>
|
||||
{
|
||||
Object
|
||||
.keys(defaults)
|
||||
.filter((key) => defaults[key].mode !== MODES.PRODUCTION)
|
||||
.map(this.renderItem)
|
||||
}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
renderItem = (key) => {
|
||||
const feature = defaults[key];
|
||||
const onCheck = () => this.store.toggleActive(key);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={ `feature_${key}` }
|
||||
leftCheckbox={
|
||||
<Checkbox
|
||||
checked={ this.store.active[key] }
|
||||
onCheck={ onCheck }
|
||||
/>
|
||||
}
|
||||
primaryText={ feature.name }
|
||||
secondaryText={
|
||||
<div className={ styles.description }>
|
||||
{ feature.description }
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
89
js/src/ui/Features/features.spec.js
Normal file
89
js/src/ui/Features/features.spec.js
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import defaults, { MODES } from './defaults';
|
||||
|
||||
import Features from './';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
function render (props = { visible: true }) {
|
||||
component = shallow(
|
||||
<Features { ...props } />
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Settings/Features', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('visibility', () => {
|
||||
let oldEnv;
|
||||
|
||||
beforeEach(() => {
|
||||
oldEnv = process.env.NODE_ENV;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env.NODE_ENV = oldEnv;
|
||||
});
|
||||
|
||||
it('renders null when NODE_ENV === production', () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
render();
|
||||
expect(component.get(0)).to.be.null;
|
||||
});
|
||||
|
||||
it('renders component when NODE_ENV !== production', () => {
|
||||
process.env.NODE_ENV = 'development';
|
||||
render();
|
||||
expect(component.get(0)).not.to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance methods', () => {
|
||||
describe('renderItem', () => {
|
||||
const keys = Object.keys(defaults).filter((key) => defaults[key].mode !== MODES.PRODUCTION);
|
||||
const key = keys[0];
|
||||
|
||||
let item;
|
||||
|
||||
beforeEach(() => {
|
||||
item = instance.renderItem(key);
|
||||
});
|
||||
|
||||
it('renders an item', () => {
|
||||
expect(item).not.to.be.null;
|
||||
});
|
||||
|
||||
it('displays the correct name', () => {
|
||||
expect(item.props.primaryText).to.equal(defaults[key].name);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
25
js/src/ui/Features/index.js
Normal file
25
js/src/ui/Features/index.js
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { FEATURES } from './defaults';
|
||||
import FeaturesStore from './store';
|
||||
|
||||
export default from './features';
|
||||
|
||||
export {
|
||||
FEATURES,
|
||||
FeaturesStore
|
||||
};
|
73
js/src/ui/Features/store.js
Normal file
73
js/src/ui/Features/store.js
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { action, observable } from 'mobx';
|
||||
import store from 'store';
|
||||
|
||||
import { LS_STORE_KEY } from './constants';
|
||||
import defaults, { FEATURES, MODES } from './defaults';
|
||||
|
||||
const isProductionMode = process.env.NODE_ENV === 'production';
|
||||
|
||||
let instance = null;
|
||||
|
||||
export default class Store {
|
||||
@observable active = {};
|
||||
|
||||
constructor () {
|
||||
this.loadActiveFeatures();
|
||||
}
|
||||
|
||||
@action setActiveFeatures = (features = {}, isProduction) => {
|
||||
this.active = Object.assign({}, this.getDefaultActive(isProduction), features);
|
||||
}
|
||||
|
||||
@action toggleActive = (featureKey) => {
|
||||
this.active = Object.assign({}, this.active, { [featureKey]: !this.active[featureKey] });
|
||||
this.saveActiveFeatures();
|
||||
}
|
||||
|
||||
getDefaultActive (isProduction = isProductionMode) {
|
||||
const modesTest = [MODES.PRODUCTION];
|
||||
|
||||
if (!isProduction) {
|
||||
modesTest.push(MODES.TESTING);
|
||||
}
|
||||
|
||||
return Object
|
||||
.keys(FEATURES)
|
||||
.reduce((visibility, feature) => {
|
||||
visibility[feature] = modesTest.includes(defaults[feature].mode);
|
||||
return visibility;
|
||||
}, {});
|
||||
}
|
||||
|
||||
loadActiveFeatures () {
|
||||
this.setActiveFeatures(store.get(LS_STORE_KEY));
|
||||
}
|
||||
|
||||
saveActiveFeatures () {
|
||||
store.set(LS_STORE_KEY, this.active);
|
||||
}
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new Store();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
96
js/src/ui/Features/store.spec.js
Normal file
96
js/src/ui/Features/store.spec.js
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import lstore from 'store';
|
||||
|
||||
import { LS_STORE_KEY } from './constants';
|
||||
import defaults, { MODES } from './defaults';
|
||||
|
||||
import Store from './store';
|
||||
|
||||
let store;
|
||||
|
||||
function createStore () {
|
||||
store = new Store();
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
describe('ui/Features/Store', () => {
|
||||
beforeEach(() => {
|
||||
lstore.set(LS_STORE_KEY, { 'testingFromStorage': true });
|
||||
createStore();
|
||||
});
|
||||
|
||||
it('loads with values from localStorage', () => {
|
||||
expect(store.active.testingFromStorage).to.be.true;
|
||||
});
|
||||
|
||||
describe('@action', () => {
|
||||
describe('setActiveFeatures', () => {
|
||||
it('sets the active features', () => {
|
||||
store.setActiveFeatures({ 'testing': true });
|
||||
expect(store.active.testing).to.be.true;
|
||||
});
|
||||
|
||||
it('overrides the defaults', () => {
|
||||
store.setActiveFeatures({ '.PRODUCTION': false });
|
||||
expect(store.active['.PRODUCTION']).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleActive', () => {
|
||||
it('changes the state of a feature', () => {
|
||||
expect(store.active['.PRODUCTION']).to.be.true;
|
||||
store.toggleActive('.PRODUCTION');
|
||||
expect(store.active['.PRODUCTION']).to.be.false;
|
||||
});
|
||||
|
||||
it('saves the updated state to localStorage', () => {
|
||||
store.toggleActive('.PRODUCTION');
|
||||
expect(lstore.get(LS_STORE_KEY)).to.deep.equal(store.active);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
describe('getDefaultActive', () => {
|
||||
it('returns features where mode === TESTING|PRODUCTION (non-production)', () => {
|
||||
const visibility = store.getDefaultActive(false);
|
||||
|
||||
expect(
|
||||
Object
|
||||
.keys(visibility)
|
||||
.filter((key) => visibility[key])
|
||||
.filter((key) => ![MODES.TESTING, MODES.PRODUCTION].includes(defaults[key].mode))
|
||||
.length
|
||||
).to.equal(0);
|
||||
});
|
||||
|
||||
it('returns features where mode === PRODUCTION (production)', () => {
|
||||
const visibility = store.getDefaultActive(true);
|
||||
|
||||
expect(
|
||||
Object
|
||||
.keys(visibility)
|
||||
.filter((key) => visibility[key])
|
||||
.filter((key) => ![MODES.PRODUCTION].includes(defaults[key].mode))
|
||||
.length
|
||||
).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -14,20 +14,23 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { MenuItem } from 'material-ui';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { LocaleStore } from '~/i18n';
|
||||
import { FeaturesStore, FEATURES } from '../Features';
|
||||
|
||||
import Select from '../Form/Select';
|
||||
import { LocaleStore } from '../../i18n';
|
||||
|
||||
@observer
|
||||
export default class LanguageSelector extends Component {
|
||||
features = FeaturesStore.get();
|
||||
store = LocaleStore.get();
|
||||
|
||||
render () {
|
||||
if (!this.store.isDevelopment) {
|
||||
if (!this.features.active[FEATURES.LANGUAGE]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -70,6 +73,6 @@ export default class LanguageSelector extends Component {
|
||||
}
|
||||
|
||||
onChange = (event, index, locale) => {
|
||||
this.store.setLocale(locale);
|
||||
this.store.setLocale(locale || event.target.value);
|
||||
}
|
||||
}
|
||||
|
69
js/src/ui/LanguageSelector/langugeSelector.spec.js
Normal file
69
js/src/ui/LanguageSelector/langugeSelector.spec.js
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { LocaleStore } from '~/i18n';
|
||||
|
||||
import LanguageSelector from './';
|
||||
|
||||
let component;
|
||||
|
||||
function render (props = {}) {
|
||||
component = shallow(
|
||||
<LanguageSelector { ...props } />
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('LanguageSelector', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
describe('Select', () => {
|
||||
let select;
|
||||
let localeStore;
|
||||
|
||||
beforeEach(() => {
|
||||
localeStore = LocaleStore.get();
|
||||
sinon.stub(localeStore, 'setLocale');
|
||||
|
||||
render();
|
||||
select = component.find('Select');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localeStore.setLocale.restore();
|
||||
});
|
||||
|
||||
it('renders the Select', () => {
|
||||
expect(select).to.have.length(1);
|
||||
});
|
||||
|
||||
it('has locale items', () => {
|
||||
expect(select.find('MenuItem').length > 0).to.be.true;
|
||||
});
|
||||
|
||||
it('calls localeStore.setLocale when changed', () => {
|
||||
select.simulate('change', { target: { value: 'de' } });
|
||||
expect(localeStore.setLocale).to.have.been.calledWith('de');
|
||||
});
|
||||
});
|
||||
});
|
@ -31,6 +31,7 @@ import CopyToClipboard from './CopyToClipboard';
|
||||
import CurrencySymbol from './CurrencySymbol';
|
||||
import Editor from './Editor';
|
||||
import Errors from './Errors';
|
||||
import Features, { FEATURES, FeaturesStore } from './Features';
|
||||
import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form';
|
||||
import GasPriceEditor from './GasPriceEditor';
|
||||
import GasPriceSelector from './GasPriceSelector';
|
||||
@ -74,6 +75,9 @@ export {
|
||||
CurrencySymbol,
|
||||
Editor,
|
||||
Errors,
|
||||
FEATURES,
|
||||
Features,
|
||||
FeaturesStore,
|
||||
Form,
|
||||
FormWrap,
|
||||
GasPriceEditor,
|
||||
|
@ -16,17 +16,18 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import { throttle } from 'lodash';
|
||||
import store from 'store';
|
||||
|
||||
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
||||
import { CancelIcon, FingerprintIcon } from '~/ui/Icons';
|
||||
import { Badge, Button, ContainerTitle, ParityBackground } from '~/ui';
|
||||
import { Embedded as Signer } from '../Signer';
|
||||
import DappsStore from '~/views/Dapps/dappsStore';
|
||||
|
||||
import imagesEthcoreBlock from '!url-loader!../../../assets/images/parity-logo-white-no-text.svg';
|
||||
import styles from './parityBar.css';
|
||||
|
||||
const LS_STORE_KEY = '_parity::parityBar';
|
||||
@ -112,10 +113,6 @@ class ParityBar extends Component {
|
||||
render () {
|
||||
const { moving, opened, position } = this.state;
|
||||
|
||||
const content = opened
|
||||
? this.renderExpanded()
|
||||
: this.renderBar();
|
||||
|
||||
const containerClassNames = opened
|
||||
? [ styles.overlay ]
|
||||
: [ styles.bar ];
|
||||
@ -124,11 +121,12 @@ class ParityBar extends Component {
|
||||
containerClassNames.push(styles.moving);
|
||||
}
|
||||
|
||||
const parityBgClassName = opened
|
||||
? styles.expanded
|
||||
: styles.corner;
|
||||
|
||||
const parityBgClassNames = [ parityBgClassName, styles.parityBg ];
|
||||
const parityBgClassNames = [
|
||||
opened
|
||||
? styles.expanded
|
||||
: styles.corner,
|
||||
styles.parityBg
|
||||
];
|
||||
|
||||
if (moving) {
|
||||
parityBgClassNames.push(styles.moving);
|
||||
@ -169,7 +167,11 @@ class ParityBar extends Component {
|
||||
ref='container'
|
||||
style={ parityBgStyle }
|
||||
>
|
||||
{ content }
|
||||
{
|
||||
opened
|
||||
? this.renderExpanded()
|
||||
: this.renderBar()
|
||||
}
|
||||
</ParityBackground>
|
||||
</div>
|
||||
);
|
||||
@ -182,34 +184,38 @@ class ParityBar extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parityIcon = (
|
||||
<img
|
||||
src={ imagesEthcoreBlock }
|
||||
className={ styles.parityIcon }
|
||||
/>
|
||||
);
|
||||
|
||||
const parityButton = (
|
||||
<Button
|
||||
className={ styles.parityButton }
|
||||
icon={ parityIcon }
|
||||
label={ this.renderLabel('Parity') }
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ styles.cornercolor }
|
||||
ref={ this.onRef }
|
||||
>
|
||||
{ this.renderLink(parityButton) }
|
||||
{
|
||||
this.renderLink(
|
||||
<Button
|
||||
className={ styles.parityButton }
|
||||
icon={
|
||||
<img
|
||||
className={ styles.parityIcon }
|
||||
src={ imagesEthcoreBlock }
|
||||
/>
|
||||
}
|
||||
label={
|
||||
this.renderLabel(
|
||||
<FormattedMessage
|
||||
id='parityBar.label.parity'
|
||||
defaultMessage='Parity'
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
className={ styles.button }
|
||||
icon={ <FingerprintIcon /> }
|
||||
label={ this.renderSignerLabel() }
|
||||
onClick={ this.toggleDisplay }
|
||||
/>
|
||||
|
||||
{ this.renderDrag() }
|
||||
</div>
|
||||
);
|
||||
@ -307,7 +313,13 @@ class ParityBar extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
return this.renderLabel('Signer', bubble);
|
||||
return this.renderLabel(
|
||||
<FormattedMessage
|
||||
id='parityBar.label.signer'
|
||||
defaultMessage='Signer'
|
||||
/>,
|
||||
bubble
|
||||
);
|
||||
}
|
||||
|
||||
getHorizontal (x) {
|
||||
|
157
js/src/views/ParityBar/parityBar.spec.js
Normal file
157
js/src/views/ParityBar/parityBar.spec.js
Normal file
@ -0,0 +1,157 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import ParityBar from './';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let store;
|
||||
|
||||
function createRedux (state = {}) {
|
||||
store = {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => Object.assign({ signer: { pending: [] } }, state)
|
||||
};
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
function render (props = {}, state = {}) {
|
||||
component = shallow(
|
||||
<ParityBar { ...props } />,
|
||||
{
|
||||
context: {
|
||||
store: createRedux(state)
|
||||
}
|
||||
}
|
||||
).find('ParityBar').shallow({ context: { api: {} } });
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/ParityBar', () => {
|
||||
beforeEach(() => {
|
||||
render({ dapp: true });
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('includes the ParityBackground', () => {
|
||||
expect(component.find('Connect(ParityBackground)')).to.have.length(1);
|
||||
});
|
||||
|
||||
describe('renderBar', () => {
|
||||
let bar;
|
||||
|
||||
beforeEach(() => {
|
||||
bar = shallow(instance.renderBar());
|
||||
});
|
||||
|
||||
it('renders nothing when not overlaying a dapp', () => {
|
||||
render({ dapp: false });
|
||||
expect(instance.renderBar()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders when overlaying a dapp', () => {
|
||||
expect(bar.find('div')).not.to.have.length(0);
|
||||
});
|
||||
|
||||
it('renders the Parity button', () => {
|
||||
const label = shallow(bar.find('Button').first().props().label);
|
||||
|
||||
expect(label.find('FormattedMessage').props().id).to.equal('parityBar.label.parity');
|
||||
});
|
||||
|
||||
it('renders the Signer button', () => {
|
||||
const label = shallow(bar.find('Button').last().props().label);
|
||||
|
||||
expect(label.find('FormattedMessage').props().id).to.equal('parityBar.label.signer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderExpanded', () => {
|
||||
let expanded;
|
||||
|
||||
beforeEach(() => {
|
||||
expanded = shallow(instance.renderExpanded());
|
||||
});
|
||||
|
||||
it('includes the Signer', () => {
|
||||
expect(expanded.find('Connect(Embedded)')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderLabel', () => {
|
||||
it('renders the label name', () => {
|
||||
expect(shallow(instance.renderLabel('testing', null)).text()).to.equal('testing');
|
||||
});
|
||||
|
||||
it('renders name and bubble', () => {
|
||||
expect(shallow(instance.renderLabel('testing', '(bubble)')).text()).to.equal('testing(bubble)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderSignerLabel', () => {
|
||||
let label;
|
||||
|
||||
beforeEach(() => {
|
||||
label = shallow(instance.renderSignerLabel());
|
||||
});
|
||||
|
||||
it('renders the signer label', () => {
|
||||
expect(label.find('FormattedMessage').props().id).to.equal('parityBar.label.signer');
|
||||
});
|
||||
|
||||
it('does not render a badge when no pending requests', () => {
|
||||
expect(label.find('Badge')).to.have.length(0);
|
||||
});
|
||||
|
||||
it('renders a badge when pending requests', () => {
|
||||
render({}, { signer: { pending: ['123', '456'] } });
|
||||
expect(shallow(instance.renderSignerLabel()).find('Badge').props().value).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('opened state', () => {
|
||||
beforeEach(() => {
|
||||
sinon.spy(instance, 'renderBar');
|
||||
sinon.spy(instance, 'renderExpanded');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.renderBar.restore();
|
||||
instance.renderExpanded.restore();
|
||||
});
|
||||
|
||||
it('renders the bar on with opened === false', () => {
|
||||
expect(component.find('Link[to="/apps"]')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders expanded with opened === true', () => {
|
||||
expect(instance.renderExpanded).not.to.have.been.called;
|
||||
instance.setState({ opened: true });
|
||||
expect(instance.renderExpanded).to.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
@ -14,67 +14,28 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { MenuItem } from 'material-ui';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { MenuItem } from 'material-ui';
|
||||
import LogLevel from 'loglevel';
|
||||
|
||||
import { LOG_KEYS } from '~/config';
|
||||
import { Select, Container, LanguageSelector } from '~/ui';
|
||||
import Features, { FeaturesStore, FEATURES } from '~/ui/Features';
|
||||
|
||||
import Store, { LOGLEVEL_OPTIONS } from './store';
|
||||
import layout from '../layout.css';
|
||||
|
||||
@observer
|
||||
export default class Parity extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
loglevels: {},
|
||||
mode: 'active',
|
||||
selectValues: []
|
||||
};
|
||||
store = new Store(this.context.api);
|
||||
features = FeaturesStore.get();
|
||||
|
||||
componentWillMount () {
|
||||
this.loadMode();
|
||||
this.loadLogLevels();
|
||||
this.setSelectValues();
|
||||
}
|
||||
|
||||
loadLogLevels () {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextState = { ...this.state.logLevels };
|
||||
|
||||
Object.keys(LOG_KEYS).map((logKey) => {
|
||||
const log = LOG_KEYS[logKey];
|
||||
|
||||
const logger = LogLevel.getLogger(log.key);
|
||||
const level = logger.getLevel();
|
||||
|
||||
nextState[logKey] = { level, log };
|
||||
});
|
||||
|
||||
this.setState({ logLevels: nextState });
|
||||
}
|
||||
|
||||
setSelectValues () {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectValues = Object.keys(LogLevel.levels).map((levelName) => {
|
||||
const value = LogLevel.levels[levelName];
|
||||
|
||||
return {
|
||||
name: levelName,
|
||||
value
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({ selectValues });
|
||||
return this.store.loadMode();
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -94,18 +55,30 @@ export default class Parity extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className={ layout.details }>
|
||||
<LanguageSelector />
|
||||
{ this.renderModes() }
|
||||
<Features />
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ this.renderLogsConfig() }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
renderItem (mode, label) {
|
||||
return (
|
||||
<MenuItem
|
||||
key={ mode }
|
||||
label={ label }
|
||||
value={ mode }
|
||||
>
|
||||
{ label }
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
renderLogsConfig () {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (!this.features.active[FEATURES.LOGLEVELS]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -127,129 +100,89 @@ export default class Parity extends Component {
|
||||
}
|
||||
|
||||
renderLogsLevels () {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return null;
|
||||
}
|
||||
const { logLevels } = this.store;
|
||||
|
||||
const { logLevels, selectValues } = this.state;
|
||||
return Object
|
||||
.keys(logLevels)
|
||||
.map((key) => {
|
||||
const { level, log } = logLevels[key];
|
||||
const { path, desc } = log;
|
||||
|
||||
return Object.keys(logLevels).map((logKey) => {
|
||||
const { level, log } = logLevels[logKey];
|
||||
const { key, desc } = log;
|
||||
const onChange = (_, index) => {
|
||||
this.store.updateLoggerLevel(path, Object.values(LOGLEVEL_OPTIONS)[index].value);
|
||||
};
|
||||
|
||||
const onChange = (_, index) => {
|
||||
const nextLevel = Object.values(selectValues)[index].value;
|
||||
|
||||
LogLevel.getLogger(key).setLevel(nextLevel);
|
||||
this.loadLogLevels();
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={ logKey }>
|
||||
<p>{ desc }</p>
|
||||
<Select
|
||||
onChange={ onChange }
|
||||
value={ level }
|
||||
values={ selectValues }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div key={ key }>
|
||||
<p>{ desc }</p>
|
||||
<Select
|
||||
onChange={ onChange }
|
||||
value={ level }
|
||||
values={ LOGLEVEL_OPTIONS }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderModes () {
|
||||
const { mode } = this.state;
|
||||
|
||||
const renderItem = (mode, label) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={ mode }
|
||||
value={ mode }
|
||||
label={ label }
|
||||
>
|
||||
{ label }
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
const { mode } = this.store;
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.label'
|
||||
defaultMessage='mode of operation'
|
||||
/>
|
||||
}
|
||||
id='parityModeSelect'
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.hint'
|
||||
defaultMessage='the syning mode for the Parity node'
|
||||
/>
|
||||
}
|
||||
value={ mode }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.label'
|
||||
defaultMessage='mode of operation'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onChangeMode }
|
||||
value={ mode }
|
||||
>
|
||||
{
|
||||
renderItem('active',
|
||||
this.renderItem('active', (
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.mode_active'
|
||||
defaultMessage='Parity continuously syncs the chain'
|
||||
/>
|
||||
)
|
||||
))
|
||||
}
|
||||
{
|
||||
renderItem('passive',
|
||||
this.renderItem('passive', (
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.mode_passive'
|
||||
defaultMessage='Parity syncs initially, then sleeps and wakes regularly to resync'
|
||||
/>
|
||||
)
|
||||
))
|
||||
}
|
||||
{
|
||||
renderItem('dark',
|
||||
this.renderItem('dark', (
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.mode_dark'
|
||||
defaultMessage='Parity syncs only when the RPC is active'
|
||||
/>
|
||||
)
|
||||
))
|
||||
}
|
||||
{
|
||||
renderItem('offline',
|
||||
this.renderItem('offline', (
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.mode_offline'
|
||||
defaultMessage="Parity doesn't sync"
|
||||
/>
|
||||
)
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
onChangeMode = (event, index, mode) => {
|
||||
const { api } = this.context;
|
||||
|
||||
api.parity
|
||||
.setMode(mode)
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.setState({ mode });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('onChangeMode', error);
|
||||
});
|
||||
}
|
||||
|
||||
loadMode () {
|
||||
const { api } = this.context;
|
||||
|
||||
api.parity
|
||||
.mode()
|
||||
.then((mode) => {
|
||||
this.setState({ mode });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('loadMode', error);
|
||||
});
|
||||
this.store.changeMode(mode || event.target.value);
|
||||
}
|
||||
}
|
||||
|
98
js/src/views/Settings/Parity/parity.spec.js
Normal file
98
js/src/views/Settings/Parity/parity.spec.js
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { createApi } from './parity.test.js';
|
||||
import Parity from './';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
function render (props = {}) {
|
||||
component = shallow(
|
||||
<Parity { ...props } />,
|
||||
{ context: { api: createApi() } }
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Settings/Parity', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
sinon.spy(instance.store, 'loadMode');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.store.loadMode.restore();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('componentWillMount', () => {
|
||||
beforeEach(() => {
|
||||
return instance.componentWillMount();
|
||||
});
|
||||
|
||||
it('loads the mode in the store', () => {
|
||||
expect(instance.store.loadMode).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('components', () => {
|
||||
it('renders a Container component', () => {
|
||||
expect(component.find('Container')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders a LanguageSelector component', () => {
|
||||
expect(component.find('LanguageSelector')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders a Features component', () => {
|
||||
expect(component.find('LanguageSelector')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Parity features', () => {
|
||||
describe('mode selector', () => {
|
||||
let select;
|
||||
|
||||
beforeEach(() => {
|
||||
select = component.find('Select[id="parityModeSelect"]');
|
||||
sinon.spy(instance.store, 'changeMode');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.store.changeMode.restore();
|
||||
});
|
||||
|
||||
it('renders a mode selector', () => {
|
||||
expect(select).to.have.length(1);
|
||||
});
|
||||
|
||||
it('changes the mode on the store when changed', () => {
|
||||
select.simulate('change', { target: { value: 'dark' } });
|
||||
expect(instance.store.changeMode).to.have.been.calledWith('dark');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
30
js/src/views/Settings/Parity/parity.test.js
Normal file
30
js/src/views/Settings/Parity/parity.test.js
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
function createApi () {
|
||||
return {
|
||||
parity: {
|
||||
mode: sinon.stub().resolves('passive'),
|
||||
setMode: sinon.stub().resolves(true)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
createApi
|
||||
};
|
105
js/src/views/Settings/Parity/store.js
Normal file
105
js/src/views/Settings/Parity/store.js
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import LogLevel from 'loglevel';
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
import { LOG_KEYS } from '~/config';
|
||||
|
||||
const DEFAULT_MODE = 'active';
|
||||
const LOGLEVEL_OPTIONS = Object
|
||||
.keys(LogLevel.levels)
|
||||
.map((name) => {
|
||||
return {
|
||||
name,
|
||||
value: LogLevel.levels[name]
|
||||
};
|
||||
});
|
||||
|
||||
export default class Store {
|
||||
@observable logLevels = {};
|
||||
@observable mode = DEFAULT_MODE;
|
||||
|
||||
constructor (api) {
|
||||
this._api = api;
|
||||
|
||||
this.loadLogLevels();
|
||||
}
|
||||
|
||||
@action setLogLevels = (logLevels) => {
|
||||
this.logLevels = logLevels;
|
||||
}
|
||||
|
||||
@action setLogLevelsSelect = (logLevelsSelect) => {
|
||||
this.logLevelsSelect = logLevelsSelect;
|
||||
}
|
||||
|
||||
@action setMode = (mode) => {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
changeMode (mode) {
|
||||
return this._api.parity
|
||||
.setMode(mode)
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.setMode(mode);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('changeMode', error);
|
||||
});
|
||||
}
|
||||
|
||||
loadLogLevels () {
|
||||
this.setLogLevels(
|
||||
Object
|
||||
.keys(LOG_KEYS)
|
||||
.reduce((state, logKey) => {
|
||||
const log = LOG_KEYS[logKey];
|
||||
const logger = LogLevel.getLogger(log.key);
|
||||
const level = logger.getLevel();
|
||||
|
||||
state[logKey] = {
|
||||
level,
|
||||
log
|
||||
};
|
||||
|
||||
return state;
|
||||
}, this.logLevels)
|
||||
);
|
||||
}
|
||||
|
||||
updateLoggerLevel (path, level) {
|
||||
LogLevel.getLogger(path).setLevel(level);
|
||||
this.loadLogLevels();
|
||||
}
|
||||
|
||||
loadMode () {
|
||||
return this._api.parity
|
||||
.mode()
|
||||
.then((mode) => {
|
||||
this.setMode(mode);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('loadMode', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
LOGLEVEL_OPTIONS
|
||||
};
|
84
js/src/views/Settings/Parity/store.spec.js
Normal file
84
js/src/views/Settings/Parity/store.spec.js
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { createApi } from './parity.test.js';
|
||||
import Store from './store';
|
||||
|
||||
let api;
|
||||
let store;
|
||||
|
||||
function createStore () {
|
||||
api = createApi();
|
||||
store = new Store(api);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
describe('views/Settings/Parity/Store', () => {
|
||||
beforeEach(() => {
|
||||
createStore();
|
||||
sinon.spy(store, 'setMode');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.setMode.restore();
|
||||
});
|
||||
|
||||
it('defaults to mode === active', () => {
|
||||
expect(store.mode).to.equal('active');
|
||||
});
|
||||
|
||||
describe('@action', () => {
|
||||
describe('setMode', () => {
|
||||
it('sets the mode', () => {
|
||||
store.setMode('offline');
|
||||
expect(store.mode).to.equal('offline');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
describe('changeMode', () => {
|
||||
beforeEach(() => {
|
||||
return store.changeMode('offline');
|
||||
});
|
||||
|
||||
it('calls parity.setMode', () => {
|
||||
expect(api.parity.setMode).to.have.been.calledWith('offline');
|
||||
});
|
||||
|
||||
it('sets the mode as provided', () => {
|
||||
expect(store.setMode).to.have.been.calledWith('offline');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadMode', () => {
|
||||
beforeEach(() => {
|
||||
return store.loadMode();
|
||||
});
|
||||
|
||||
it('calls parity.mode', () => {
|
||||
expect(api.parity.mode).to.have.been.called;
|
||||
});
|
||||
|
||||
it('sets the mode as retrieved', () => {
|
||||
expect(store.setMode).to.have.been.calledWith('passive');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -117,8 +117,13 @@ module.exports = {
|
||||
test: /\.(woff(2)|ttf|eot|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [ 'file-loader?name=fonts/[name][hash:10].[ext]' ]
|
||||
},
|
||||
{
|
||||
test: /parity-logo-white-no-text\.svg/,
|
||||
use: [ 'url-loader' ]
|
||||
},
|
||||
{
|
||||
test: /\.svg(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
exclude: [ /parity-logo-white-no-text\.svg/ ],
|
||||
use: [ 'file-loader?name=assets/[name].[hash:10].[ext]' ]
|
||||
}
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user