diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ApplyMods.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ApplyMods.js index 27189f2..244e214 100644 --- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ApplyMods.js +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ApplyMods.js @@ -26,7 +26,7 @@ export default function getApplyMods(theState) { return (
- + К изначальным!
); diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/InfoLi.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/InfoLi.js index 4bd4884..0cdccfc 100644 --- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/InfoLi.js +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/InfoLi.js @@ -122,6 +122,7 @@ export default function getInfoLi() { checked={props.checked} id={iddy} onClick={props.onClick} + onChange={props.onChange} disabled={props.disabled} />
diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js index 4f8258f..daafc8f 100644 --- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js @@ -21,6 +21,8 @@ export default function getMain(theState) { const ApplyMods = getApplyMods(theState); const Notifications = getNotifications(theState); + //const addChecks = (arr) => arr.map( (conf) => Object.assign(conf, {ifChecked: Boolean(conf.value)}) ); + const checksName = 'pacMods'; return class Main extends Component { @@ -35,6 +37,7 @@ export default function getMain(theState) { 'ownProxies': props.apis.pacKitchen.getOrderedConfigs('ownProxies'), }, }; + this.handleModChange = this.handleModChange.bind(this); } @@ -64,33 +67,34 @@ export default function getMain(theState) { } - handleModCheck(that, {targetConf, targetIndex, targetChildren}) { + handleModChange({targetConf, targetIndex, newValue}) { - const oldCats = that.state.catToOrderedMods; - const newCats = Object.keys(that.state.catToOrderedMods).reduce((acc, cat) => { + const oldCats = this.state.catToOrderedMods; + const newCats = Object.keys(this.state.catToOrderedMods).reduce((acc, cat) => { if (cat !== targetConf.category) { acc[cat] = oldCats[cat]; } else { - acc[cat] = oldCats[cat].map( - (conf, index) => targetIndex === index - ? Object.assign({}, conf, {value: !targetConf.value}) - : conf - ); + acc[cat] = oldCats[cat].map((conf, index) => { + + if (targetIndex !== index) { + return conf; + } + console.log(`${conf.key} := ${newValue}`); + return Object.assign({}, conf, { + value: newValue + }); + + }); } return acc; }, {}); - that.setState({ catToOrderedMods: newCats }); - - } - - handleModChange(that, event) { - - if (event.target.name === checksName) { - that.setState({ifModsChangesStashed: true}); - } + this.setState({ + catToOrderedMods: newCats, + ifModsChangesStashed: true, + }); } @@ -98,14 +102,13 @@ export default function getMain(theState) { const applyModsEl = createElement(ApplyMods, Object.assign({}, props, { - disabled: !this.state.ifModsChangesStashed || props.ifInputsDisabled, + ifInputsDisabled: !this.state.ifModsChangesStashed || props.ifInputsDisabled, onClick: linkEvent(this, this.handleModApply), } )); const modsHandlers = { - onChange: linkEvent(this, this.handleModChange), - onClick: (...args) => this.handleModCheck(this, ...args), + onConfChanged: this.handleModChange, }; return createElement(TabPanel, { @@ -127,7 +130,7 @@ export default function getMain(theState) { Object.assign({}, props, { orderedConfigs: this.state.catToOrderedMods['ownProxies'], childrenOfMod: { - customProxyStringRaw: createElement(ProxyEditor, props), + customProxyStringRaw: ProxyEditor, }, name: checksName, }, modsHandlers) diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js index 3a801b9..1bfb365 100644 --- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js @@ -1,31 +1,86 @@ -import Inferno from 'inferno'; +import Inferno, {linkEvent} from 'inferno'; +import Component from 'inferno-component'; +import createElement from 'inferno-create-element'; import getInfoLi from './InfoLi'; export default function getModList(theState) { const InfoLi = getInfoLi(theState); - return function ModList(props) { + return class ModList extends Component { - return ( -
    - { - props.orderedConfigs.map((conf, index) => ( - ( props.onClick({targetConf: conf, targetIndex: index, targetChildren: props.childrenOfMod})} - > - {Boolean(conf.value) && props.childrenOfMod && props.childrenOfMod[conf.key]} - ) - )) + constructor(props) { + + super(props); + this.state= { + checks: props.orderedConfigs.map((mod) => Boolean(mod.value)), + }; + + } + + handleCheck(confMeta, ifChecked) { + + console.log('handle CHECK:', ifChecked); + this.state.checks[confMeta.index] = ifChecked; + if (ifChecked === false || !confMeta.ifChild) { + console.log('NO CHILD OR FALSE', confMeta); + this.handleNewValue(confMeta, ifChecked); + } else { + this.setState({ + checks: this.state.checks.map( + (ch, i) => i === confMeta.index ? ifChecked : ch + ) + }); } -
- ); - }; + } + + handleNewValue({ conf, index }, newValue) { + + console.log('handle NEW VALUE', conf.key, newValue); + this.props.onConfChanged({ + targetConf: conf, + targetIndex: index, + newValue: newValue, + }); + + } + + render(props) { + + return ( +
    + { + props.orderedConfigs.map((conf, index) => { + + const ifMayHaveChild = props.childrenOfMod && props.childrenOfMod[conf.key]; + const confMeta = { conf, index, ifChild: ifMayHaveChild }; + + const child = ifMayHaveChild && this.state.checks[index] + && createElement( + props.childrenOfMod[conf.key], + Object.assign({}, props, {conf, onNewValue: (newValue) => this.handleNewValue(confMeta, newValue)}) + ); + + console.log('CHIIIIILD', child); + return ( this.handleCheck(confMeta, event.target.checked)} + > + {child} + ); + + }) + } +
+ ); + + } + + } }; diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js index c9ee179..3026378 100644 --- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js @@ -134,7 +134,7 @@ export default function getProxyEditor(theState) { const SwitchButton = (props) => ( {type}{hostname}{port} - @@ -421,7 +421,7 @@ export default function getProxyEditor(theState) { { this.state.stashedExports === false - ? 'Жду изменений...' + ? 'Комментарии не поддерживаются!' : (this.state.ifHasErrors ? (Сбросьте изменения или поправьте) : (Сбросить изменения) @@ -462,20 +462,30 @@ PROXY foobar.com:8080; # Not HTTP!`.trim()} return class ProxyEditor extends Component { - constructor(props) { + constructor(props/*{ conf, onNewValue, ifInputsDisabled }*/) { super(props); + console.log('CONSTRUCTOR') + const oldValue = typeof props.conf.value === 'string' && props.conf.value; + const newValue = oldValue || localStorage.getItem(UI_RAW) || ''; this.state = { - proxyStringRaw: localStorage.getItem(UI_RAW) || '', + proxyStringRaw: newValue, ifExportsMode: false, }; this.handleSwitch = () => this.setState({ifExportsMode: !this.state.ifExportsMode}); + this.mayEmitNewValue(oldValue, newValue); } - preventLostOfChanges() { + mayEmitNewValue(oldValue, newValue) { - window.onbeforeunload = () => true; // TODO + console.log('emit?', oldValue, 'vs', newValue); + if ( // Reject: 1) both `false` OR 2) both `===`. + ( Boolean(oldValue) || Boolean(newValue) ) && oldValue !== newValue + ) { + console.log('EMIT'); + this.props.onNewValue(newValue); + } } @@ -484,9 +494,17 @@ PROXY foobar.com:8080; # Not HTTP!`.trim()} const props = Object.assign({ proxyStringRaw: this.state.proxyStringRaw, onSwitch: this.handleSwitch, - setProxyStringRaw: (newVal) => this.setState({proxyStringRaw: newVal}), + setProxyStringRaw: (newValue) => { + + const oldValue = this.state.proxyStringRaw; + localStorage.setItem(UI_RAW, newValue); + this.setState({proxyStringRaw: newValue}); + this.mayEmitNewValue(oldValue, newValue); + + }, }, originalProps); + return createElement(ExportsEditor, props); return this.state.ifExportsMode ? createElement(ExportsEditor, props) : createElement(TabledEditor, props); diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js_replace b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js_replace new file mode 100644 index 0000000..85ca06d --- /dev/null +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js_replace @@ -0,0 +1,508 @@ +import Inferno, {linkEvent} from 'inferno'; +import Component from 'inferno-component'; +import createElement from 'inferno-create-element'; +import css from 'csjs-inject'; + +export default function getProxyEditor(theState) { + + const scopedCss = css` + + table.editor { + border-collapse: collapse; + /*border-style: hidden;*/ + width: 100%; + margin: 0.5em 0; + background-color: #f3f5f6; + } + + table.editor ::-webkit-input-placeholder { + color: #c9c9c9; + } + + table.editor td, table.editor th { + border: 1px solid #ccc; + text-align: left; + height: 100%; + } + + /* ADD PANEL */ + table.editor tr.addPanel td { + padding: 0; + } + /* PROXY ROW */ + table.editor tr.proxyRow td:nth-child(2) { + text-align: center; + } + + table.editor th:not(:last-child) { + padding: 0 0.6em; + } + + table.editor input:not([type="submit"]), + table.editor select, + table.editor select:hover { + border: none; + background: inherit !important; + } + table.editor select, + table.editor select:hover { + -webkit-appearance: menulist !important; + box-shadow: none !important; + } + table.editor input { + width: 100%; + } + + /* BUTTONS */ + table.editor input[type="submit"], + table.editor button { + min-width: 0; + min-height: 0; + width: 100%; + padding: 0; + border: none; + } + .only { + /*height: 100%;*/ + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } + table.editor .add { + font-weight: 900; + } + table.editor .export { + padding-right: 2px; + } + + /* LAST COLUMN: BUTTONS */ + table.editor tr > *:nth-last-child(1), + table.editor tr.proxyRow > td:first-child { + text-align: center; + padding: 0; + position: relative; + min-width: 1em; + } + /* LAST-2 COLUMN: HOSTNAME + table.editor td:nth-last-child(3) { + padding-left: 2px; + }*/ + .noPad { + padding: 0; + } + .padLeft { + padding-left: 2px; + } + + textarea.textarea { + width: 100% !important; + min-height: 100%; + height: 6em; + border-width: 1px 0 0 0; + /*border: none;*/ + } + + table.editor input:invalid { + color: red !important; + border-radius: 0; + border-bottom: 1px dotted red !important; + } + + `; + + const UI_RAW = 'ui-proxy-string-raw'; + const MAX_PORT = 65535; + const onlyPort = function onlyPort(event) { + + if (!event.ctrlKey && (/^\D$/.test(event.key) || /^\d$/.test(event.key) && parseInt(`${this.value}${event.key}`) > MAX_PORT)) { + event.preventDefault(); + return false; + } + // Digits, Alt, Tab, Enter, etc. + return true; + + }; + const splitBySemi = (proxyString) => proxyString.replace(/#.*$/mg, '').trim().split(/\s*;\s*/g).filter((s) => s); + const joinBySemi = (strs) => strs.join(';\n') + ';'; + const normilizeProxyString = (str) => joinBySemi(splitBySemi(str)); + + const PROXY_TYPE_LABEL_PAIRS = [['PROXY', 'PROXY/HTTP'],['HTTPS'],['SOCKS4'],['SOCKS5'],['SOCKS']]; + + + const SwitchButton = (props) => + ( + + ); + + class TabledEditor extends Component { + + constructor(props) { + + super(props); + this.state = { + selectedNewType: 'HTTPS', + }; + + } + + handleTypeSelect(that, event) { + + that.setState({ + selectedNewType: event.target.value, + }); + + } + + showInvalidMessage(that, event) { + + that.props.funs.showErrors({message: event.target.validationMessage}); + + } + + handleModeSwitch(that) { + + that.props.onSwitch(); + + } + + handleAdd(that, event) { + + const form = event.target; + const elements = Array.from(form.elements).reduce((acc, el, index) => { + + acc[el.name || index] = el.value; + el.value = ''; + return acc; + + }, {}); + const type = that.state.selectedNewType; + const hostname = elements.hostname; + const port = elements.port; + + that.props.setProxyStringRaw( + `${that.props.proxyStringRaw.trim()}\n;${type} ${hostname}:${port};`.trim() + ); + + } + + handleDelete(that, {proxyAsString, index}) { + + event.preventDefault(); + /* + const proxyStrings = splitBySemi(that.props.proxyStringRaw); + proxyStrings.splice(index, 1); + */ + const newVal = that.props.proxyStringRaw.replace( + new RegExp(`${proxyAsString}(?:\s*;\s*)*`, 'g'), + '' + ); + that.props.setProxyStringRaw( newVal ); + + } + + raisePriority(that, {proxyAsString, index}) { + + event.preventDefault(); + if (index < 1) { + return; + } + + const proxyStrs = splitBySemi(that.props.proxyStringRaw); + const target = proxyStrs[index - 1]; + const newVal = that.props.proxyStringRaw + .replace(proxyAsString, target) + .replace(target, proxyAsString); + + //proxyStrings.splice(index - 1, 2, proxyStrings[index], proxyStrings[index-1]); + + that.props.setProxyStringRaw( newVal ); + + } + + handleSubmit(that, event) { + + event.preventDefault(); + that.handleAdd(that, event); + + } + + render(props) { + + return ( +
+ + + + + + + + {/* ADD NEW PROXY STARTS. */} + + + + + + + {/* ADD NEW PROXY ENDS. */} + { + splitBySemi(this.props.proxyStringRaw).map((proxyAsString, index) => { + + const [type, addr] = proxyAsString.trim().split(/\s+/); + const [hostname, port] = addr.split(':'); + return ( + + + + + ); + + }) + } + +
протокол домен / IP порт + +
+ + + {/* LAST-2: HOSTNAME */} + + + {/* LAST-1: PORT */} + + + {/* LAST: ADD BUTTON */} + +
+ + {type}{hostname}{port} + +
+
+ ); + } + } + + const getInitState = () => ({ + ifHasErrors: false, + stashedExports: false, + }); + + class ExportsEditor extends Component { + + constructor(props) { + + super(props); + this.state = getInitState(); + + } + + resetState(that, event) { + + that.setState(getInitState()); + event.preventDefault(); + + } + + getErrorsInStashedExports() { + + if(this.state.stashedExports === false) { + return; + } + const errors = splitBySemi(this.state.stashedExports) + .map((proxyAsString) => { + + const [rawType, addr, ...rest] = proxyAsString.split(/\s+/); + if (rest && rest.length) { + return new Error( + `"${rest.join(', ')}" кажется мне лишним. Вы забыли ";"?` + ); + } + const knownTypes = PROXY_TYPE_LABEL_PAIRS.map(([type, label]) => type); + if( !knownTypes.includes(rawType.toUpperCase()) ) { + return new Error( + `Неверный тип ${rawType}. Известные типы: ${knownTypes.join(', ')}.` + ); + } + if (!(addr && /^[^:]+:\d+$/.test(addr))) { + return new Error( + `Адрес прокси "${addr || ''}" не соответствует формату "<домен_или_IP>:<порт_из_цифр>".` + ); + } + const [hostname, rawPort] = addr.split(':'); + const port = parseInt(rawPort); + if (port < 0 || port > 65535) { + return new Error( + `Порт "${rawPort}" должен быть целым числом от 0 до 65535.` + ); + } + return false; + + }).filter((e) => e); + return errors && errors.length && errors; + + } + + handleModeSwitch(that, event) { + + if (that.state.stashedExports !== false) { + const errors = that.getErrorsInStashedExports(); + if (errors) { + that.setState({ifHasErrors: true}); + that.props.funs.showErrors(...errors); + return; + } + that.props.setProxyStringRaw(that.state.stashedExports); + } + that.setState({ + stashedExports: false, + ifHasErrors: false, + }); + that.props.onSwitch(); + + } + + handleTextareaChange(that, event) { + + that.setState({ + stashedExports: event.target.value, + }); + + } + + handleSubmit(that, event) { + + event.preventDefault(); + this.handleModeSwitch(this, event); + + } + + render(props) { + + const reset = linkEvent(this, this.resetState); + + return ( +
+ + + + + + + + + +
+ { + this.state.stashedExports === false + ? 'Жду изменений...' + : (this.state.ifHasErrors + ? (Сбросьте изменения или поправьте) + : (Сбросить изменения) + ) + } + + +