mirror of
https://github.com/chylex/Firefox-SCsCC.git
synced 2025-04-09 07:15:43 +02:00
rewrite options as hyperapp
This commit is contained in:
parent
6320ff5eed
commit
8689bc2f4a
@ -19,5 +19,8 @@ module.exports = {
|
||||
'no-param-reassign': ['error', { props: false }],
|
||||
|
||||
'react/no-unknown-property': 'off',
|
||||
'react/prop-types': 'off',
|
||||
|
||||
'jsx-a11y/label-has-for': 'off',
|
||||
},
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ export default [
|
||||
{
|
||||
title: 'Convert to',
|
||||
name: 'toCurr',
|
||||
type: 'menulist',
|
||||
type: 'select',
|
||||
value: '',
|
||||
options: [
|
||||
{ value: '', label: 'Please select a currency' },
|
||||
@ -175,13 +175,13 @@ export default [
|
||||
{
|
||||
title: 'Round price',
|
||||
name: 'round',
|
||||
type: 'bool',
|
||||
type: 'checkbox',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
title: 'Currency symbol',
|
||||
name: 'symbol',
|
||||
type: 'string',
|
||||
type: 'text',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
@ -203,13 +203,13 @@ export default [
|
||||
{
|
||||
title: 'Separate symbol from price',
|
||||
name: 'symbSep',
|
||||
type: 'bool',
|
||||
type: 'checkbox',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
title: 'Thousand separator',
|
||||
name: 'sepTho',
|
||||
type: 'menulist',
|
||||
type: 'select',
|
||||
value: ' ',
|
||||
options: [
|
||||
{
|
||||
@ -233,7 +233,7 @@ export default [
|
||||
{
|
||||
title: 'Decimal separator',
|
||||
name: 'sepDec',
|
||||
type: 'menulist',
|
||||
type: 'select',
|
||||
value: ',',
|
||||
options: [
|
||||
{
|
||||
@ -249,13 +249,13 @@ export default [
|
||||
{
|
||||
title: 'Add custom style to converted prices',
|
||||
name: 'style',
|
||||
type: 'bool',
|
||||
type: 'checkbox',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
title: 'Show exchange rate update notifications',
|
||||
name: 'noti',
|
||||
type: 'bool',
|
||||
type: 'checkbox',
|
||||
value: true,
|
||||
},
|
||||
];
|
||||
|
13
src/options/App.jsx
Normal file
13
src/options/App.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { h } from 'hyperapp';
|
||||
|
||||
import OptionsContainer from './components/OptionsContainer';
|
||||
|
||||
const App = ({ options, preferences }, { preferences: { handleChange } }) => (
|
||||
<div class="container">
|
||||
<h1>SCs Currency Converter - Options</h1>
|
||||
|
||||
<OptionsContainer options={options} preferences={preferences} onOptionChange={handleChange} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
35
src/options/actions.js
Normal file
35
src/options/actions.js
Normal file
@ -0,0 +1,35 @@
|
||||
const actions = {
|
||||
preferences: {
|
||||
handleChange: ({ type, name, value }) => (preferences) => {
|
||||
const changedPrefs = {
|
||||
[name]: type === 'text' ? value.trim() : value,
|
||||
};
|
||||
|
||||
// change the symbol on currency change
|
||||
if (name === 'toCurr') {
|
||||
changedPrefs.symbol = value;
|
||||
}
|
||||
|
||||
// if a space inserted before or after the symbol, remove it, and set symbSep to true
|
||||
if (name === 'symbol' && value !== value.trim() && !preferences.symbSep) {
|
||||
changedPrefs.symbSep = true;
|
||||
}
|
||||
|
||||
// don't let to set the same thousand and decimal separator
|
||||
if (name === 'sepTho' && preferences.sepDec === value) {
|
||||
changedPrefs.sepDec = value === ',' ? '.' : ',';
|
||||
}
|
||||
if (name === 'sepDec' && preferences.sepTho === value) {
|
||||
changedPrefs.sepTho = value === ',' ? '.' : ',';
|
||||
}
|
||||
|
||||
const newPrefs = { ...preferences, ...changedPrefs };
|
||||
|
||||
browser.storage.local.set({ preferences: newPrefs });
|
||||
},
|
||||
|
||||
set: (newPrefs) => () => newPrefs,
|
||||
},
|
||||
};
|
||||
|
||||
export default actions;
|
49
src/options/components/OptionField.jsx
Normal file
49
src/options/components/OptionField.jsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { h } from 'hyperapp';
|
||||
|
||||
const OptionField = ({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
value,
|
||||
options,
|
||||
onChange,
|
||||
}) => {
|
||||
const handleChange = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { target } = e;
|
||||
const newValue = type === 'checkbox' ? target.checked : target.value;
|
||||
|
||||
onChange({ type, name, value: newValue });
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'select':
|
||||
return (
|
||||
<select id={id} name={name} value={value} onchange={handleChange}>
|
||||
{options.map(({ value: val, label }) => (
|
||||
<option key={val} value={val}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
case 'text':
|
||||
return (
|
||||
<input type="text" id={id} name={name} value={value} onchange={handleChange} />
|
||||
);
|
||||
case 'checkbox':
|
||||
return (
|
||||
<input type="checkbox" id={id} name={name} checked={value} onchange={handleChange} />
|
||||
);
|
||||
case 'radio':
|
||||
return options.map(({ value: val, label }) => (
|
||||
<label key={val}>
|
||||
<input type="radio" name={name} value={val} checked={val === value} onchange={handleChange} />
|
||||
{label}
|
||||
</label>
|
||||
));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default OptionField;
|
35
src/options/components/OptionsContainer.jsx
Normal file
35
src/options/components/OptionsContainer.jsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { h } from 'hyperapp';
|
||||
|
||||
import OptionField from './OptionField';
|
||||
import ResetButton from './ResetButton';
|
||||
|
||||
const OptionsContainer = ({ options, preferences, onOptionChange }) => (
|
||||
<div class="options">
|
||||
{options.map(({
|
||||
name,
|
||||
title,
|
||||
type,
|
||||
options: opts,
|
||||
}) => (
|
||||
<div key={name} class="row">
|
||||
<div class="col">
|
||||
<label for={`option-${name}`}>{title}</label>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<OptionField id={`option-${name}`} type={type} name={name} value={preferences[name]} options={opts} onChange={onOptionChange} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div class="row">
|
||||
<div class="col" />
|
||||
|
||||
<div class="col">
|
||||
<ResetButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default OptionsContainer;
|
15
src/options/components/ResetButton.jsx
Normal file
15
src/options/components/ResetButton.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { h } from 'hyperapp';
|
||||
|
||||
const ResetButton = () => {
|
||||
const handleClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
browser.storage.local.set({ currRates: {} });
|
||||
};
|
||||
|
||||
return (
|
||||
<button onclick={handleClick}>Reset exchange rates</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetButton;
|
@ -1,70 +1,18 @@
|
||||
import getOptionRowElem from './utils/getOptionRowElem';
|
||||
import getButtonRowElem from './utils/getButtonRowElem';
|
||||
import getChangedPrefs from './utils/getChangedPrefs';
|
||||
import { app } from 'hyperapp';
|
||||
|
||||
|
||||
const onChange = ({ target }) => {
|
||||
const changedPrefs = getChangedPrefs(target);
|
||||
|
||||
if (Object.keys(changedPrefs).length) {
|
||||
browser.storage.local.get('preferences')
|
||||
.then((storage) => {
|
||||
browser.storage.local.set({ preferences: Object.assign({}, storage.preferences, changedPrefs) });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
browser.storage.local.set({ currRates: {} });
|
||||
};
|
||||
|
||||
const buildOptionsForm = (options, preferences) => {
|
||||
const optionsElem = document.getElementById('options');
|
||||
|
||||
options.forEach((option) => {
|
||||
const value = preferences[option.name];
|
||||
const optionRowElem = getOptionRowElem(option, value, onChange);
|
||||
|
||||
optionsElem.appendChild(optionRowElem);
|
||||
});
|
||||
|
||||
const buttonRowElem = getButtonRowElem('Reset exchange rates', onReset);
|
||||
|
||||
optionsElem.appendChild(buttonRowElem);
|
||||
};
|
||||
|
||||
const refreshOptionsForm = (changedPrefs) => {
|
||||
Object.keys(changedPrefs).forEach((prefName) => {
|
||||
const optionElem = document.querySelector(`[name="${prefName}"]`);
|
||||
|
||||
if (optionElem) {
|
||||
if (optionElem.type === 'radio') {
|
||||
const radioElem = document.querySelector(`[name="${prefName}"][value="${changedPrefs[prefName]}"]`);
|
||||
if (radioElem) radioElem.checked = true;
|
||||
} else if (optionElem.type === 'checkbox') {
|
||||
optionElem.checked = changedPrefs[prefName];
|
||||
} else {
|
||||
optionElem.value = changedPrefs[prefName];
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
import actions from './actions';
|
||||
import App from './App';
|
||||
|
||||
Promise.all([browser.runtime.getBackgroundPage(), browser.storage.local.get('preferences')])
|
||||
.then(([bgWindow, storage]) => {
|
||||
buildOptionsForm(bgWindow.OPTIONS, storage.preferences);
|
||||
const state = {
|
||||
options: bgWindow.OPTIONS,
|
||||
preferences: storage.preferences,
|
||||
};
|
||||
|
||||
browser.storage.onChanged.addListener((changes) => {
|
||||
if (changes.preferences) {
|
||||
const oldPrefs = changes.preferences.oldValue;
|
||||
const newPrefs = changes.preferences.newValue;
|
||||
const changedPrefs = {};
|
||||
const main = app(state, actions, App, document.getElementById('app'));
|
||||
|
||||
Object.keys(changes.preferences.newValue).forEach((prefKey) => {
|
||||
if (oldPrefs[prefKey] !== newPrefs[prefKey]) changedPrefs[prefKey] = newPrefs[prefKey];
|
||||
});
|
||||
|
||||
if (Object.keys(changedPrefs).length) refreshOptionsForm(changedPrefs);
|
||||
}
|
||||
browser.storage.onChanged.addListener(({ preferences }) => {
|
||||
if (preferences) main.preferences.set(preferences.newValue);
|
||||
});
|
||||
});
|
||||
|
@ -42,12 +42,12 @@ body {
|
||||
padding: 0 5px 5px;
|
||||
}
|
||||
|
||||
#options .col:first-child {
|
||||
.options .col:first-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
#options .col:first-child {
|
||||
.options .col:first-child {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,8 @@
|
||||
<link rel="stylesheet" type="text/css" href="options.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>SCs Currency Converter - Options</h1>
|
||||
<div id="app"></div>
|
||||
|
||||
<div id="options"></div>
|
||||
</div>
|
||||
<script type="text/javascript" src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,20 +0,0 @@
|
||||
export default (text, onClick) => {
|
||||
const rowElem = document.createElement('div');
|
||||
rowElem.className = 'row';
|
||||
|
||||
const emplyColElem = document.createElement('div');
|
||||
emplyColElem.className = 'col';
|
||||
rowElem.appendChild(emplyColElem);
|
||||
|
||||
const buttonColElem = document.createElement('div');
|
||||
buttonColElem.className = 'col';
|
||||
|
||||
const buttonElem = document.createElement('button');
|
||||
buttonElem.textContent = text;
|
||||
buttonElem.addEventListener('click', onClick);
|
||||
|
||||
buttonColElem.appendChild(buttonElem);
|
||||
rowElem.appendChild(buttonColElem);
|
||||
|
||||
return rowElem;
|
||||
};
|
@ -1,35 +0,0 @@
|
||||
export default (target) => {
|
||||
const prefName = target.name;
|
||||
const prefValue = target.type === 'checkbox' ? target.checked : target.value;
|
||||
const changedPrefs = {};
|
||||
changedPrefs[prefName] = prefValue;
|
||||
|
||||
// change the symbol on currency change
|
||||
if (prefName === 'toCurr') {
|
||||
changedPrefs.symbol = prefValue;
|
||||
}
|
||||
|
||||
// if a space inserted before the symbol, remove it, and set symbSep to true
|
||||
if (prefName === 'symbol' && (prefValue.charAt(0) === ' ' || prefValue.charAt(prefValue.length - 1) === ' ')) {
|
||||
const symbSepElem = document.getElementById('option-symbSep');
|
||||
if (!symbSepElem.checked) changedPrefs.symbSep = true;
|
||||
|
||||
target.value = prefValue.trim();
|
||||
delete changedPrefs.symbol;
|
||||
}
|
||||
|
||||
// don't const to set the same thousand and decimal separator
|
||||
if (prefName === 'sepTho' && [',', '.'].indexOf(prefValue) !== -1) {
|
||||
const sepDecElem = document.getElementById('option-sepDec');
|
||||
if (sepDecElem.value === prefValue) {
|
||||
changedPrefs.sepDec = prefValue === ',' ? '.' : ',';
|
||||
}
|
||||
} else if (prefName === 'sepDec') {
|
||||
const sepThoElem = document.getElementById('option-sepTho');
|
||||
if (sepThoElem.value === prefValue) {
|
||||
changedPrefs.sepTho = prefValue === ',' ? '.' : ',';
|
||||
}
|
||||
}
|
||||
|
||||
return changedPrefs;
|
||||
};
|
@ -1,74 +0,0 @@
|
||||
export default (option, value, onChange) => {
|
||||
const rowElem = document.createElement('div');
|
||||
rowElem.className = 'row';
|
||||
|
||||
const labelColElem = document.createElement('div');
|
||||
labelColElem.className = 'col';
|
||||
const labelElem = document.createElement('label');
|
||||
labelElem.htmlFor = `option-${option.name}`;
|
||||
labelElem.textContent = option.title;
|
||||
|
||||
labelColElem.appendChild(labelElem);
|
||||
rowElem.appendChild(labelColElem);
|
||||
|
||||
const optionColElem = document.createElement('div');
|
||||
optionColElem.className = 'col';
|
||||
|
||||
let optionElem;
|
||||
if (option.type === 'string') {
|
||||
optionElem = document.createElement('input');
|
||||
optionElem.type = 'text';
|
||||
optionElem.name = option.name;
|
||||
optionElem.value = value || '';
|
||||
|
||||
optionElem.addEventListener('input', onChange);
|
||||
} else if (option.type === 'bool') {
|
||||
optionElem = document.createElement('input');
|
||||
optionElem.type = 'checkbox';
|
||||
optionElem.name = option.name;
|
||||
if (value === true) optionElem.checked = true;
|
||||
|
||||
optionElem.addEventListener('change', onChange);
|
||||
} else if (option.type === 'radio') {
|
||||
optionElem = document.createElement('div');
|
||||
|
||||
option.options.forEach((opt) => {
|
||||
const optLabelElem = document.createElement('label');
|
||||
|
||||
const radioElem = document.createElement('input');
|
||||
radioElem.type = 'radio';
|
||||
radioElem.name = option.name;
|
||||
radioElem.value = opt.value;
|
||||
if (value === opt.value) radioElem.setAttribute('checked', '');
|
||||
|
||||
radioElem.addEventListener('change', onChange);
|
||||
|
||||
optLabelElem.appendChild(radioElem);
|
||||
optLabelElem.appendChild(document.createTextNode(opt.label));
|
||||
optionElem.appendChild(optLabelElem);
|
||||
});
|
||||
} else if (option.type === 'menulist') {
|
||||
optionElem = document.createElement('select');
|
||||
optionElem.name = option.name;
|
||||
|
||||
option.options.forEach((opt) => {
|
||||
const optElem = document.createElement('option');
|
||||
optElem.value = opt.value;
|
||||
optElem.textContent = opt.label;
|
||||
if (value === opt.value) optElem.setAttribute('selected', '');
|
||||
|
||||
optionElem.appendChild(optElem);
|
||||
});
|
||||
|
||||
optionElem.addEventListener('change', onChange);
|
||||
}
|
||||
|
||||
if (optionElem) {
|
||||
optionElem.id = `option-${option.name}`;
|
||||
|
||||
optionColElem.appendChild(optionElem);
|
||||
rowElem.appendChild(optionColElem);
|
||||
}
|
||||
|
||||
return rowElem;
|
||||
};
|
Loading…
Reference in New Issue
Block a user