From 260174511ee0a528e918112e7556aecea3505fcb Mon Sep 17 00:00:00 2001 From: Leonardo Santos Date: Sun, 16 Sep 2018 01:39:51 -0300 Subject: [PATCH 1/3] rework StringEntry to work with the replace feature- make the input controlled --- src/components/StringList/StringEntry.js | 97 ++++++++++++------------ 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/src/components/StringList/StringEntry.js b/src/components/StringList/StringEntry.js index 2f3650f..29585bf 100644 --- a/src/components/StringList/StringEntry.js +++ b/src/components/StringList/StringEntry.js @@ -15,6 +15,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + import React, { Component } from 'react'; import HighlightWords from 'react-highlight-words'; @@ -26,77 +27,73 @@ import './StringEntry.css'; import { translate } from '../../i18n/i18n'; export default class StringEntry extends Component { - state = { focused: false }; + inputRef = React.createRef(); + + constructor(props) { + super(props); + + this.state = { + focused: false, + value: props.string.value, + }; + } - onDivClick = ({ target }) => { + onDivClick = () => { this.setState({ focused: true }); }; - onInput = ({ target }) => { - const { string } = this.props; + onChange = ({ target }) => { + this.setState({ value: target.value }); + }; - // If highlightWords is not undefined it means that the string object was cloned - // so we need to update it's value here. - if (string.highlightWords) { - string.value = target.value; + componentWillReceiveProps(nextProps) { + if (this.state.value !== nextProps.string.value) { + // We don't care if we erase the state. + this.setState({ value: nextProps.string.value }); } - - this.props.onChanged(target.value, string.id); - }; + } componentDidUpdate() { + // We use this to focus the input when it changes from the + // higlighted div. If we don't do this, the user will need to + // click twice to focus the input. if (this.state.focused) { - const { input } = this; - - input.focus(); - - // Place the caret at end - setImmediate(() => { - input.selectionStart = input.selectionEnd = input.value.length; - }); + this.inputRef.current.focus(); } } onInputBlur = () => { this.setState({ focused: false }); + this.props.onChanged(this.state.value, this.props.string.id); }; render() { const { string } = this.props; - const { value, highlightWords } = string; - - let element; - - if (!highlightWords || this.state.focused) { - element = ( - { - this.input = input; - }} - onBlur={this.onInputBlur} - type="text" - className="string-input" - defaultValue={value} - /> - ); - } else { - // TODO: tab select? focus & allow to use enter to open the file selector - element = ( -
- -
- ); - } + + const useFakeHighlightedInput = + string.highlightWords && !this.state.focused; return (
- {element} + {useFakeHighlightedInput ? ( +
+ +
+ ) : ( + + )}
From d9aca8bd79488633a825bfc2eb3ee6eddf08908c Mon Sep 17 00:00:00 2001 From: Leonardo Santos Date: Mon, 8 Oct 2018 11:43:52 -0300 Subject: [PATCH 2/3] replace feature: basic implementation --- src/App.css | 13 +++++++ src/App.js | 78 +++++++++++++++++++++++++++++++++++++++-- src/icons/replace.js | 13 +++++++ src/util/string-util.js | 5 ++- 4 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 src/icons/replace.js diff --git a/src/App.css b/src/App.css index 3742bef..18fe2d5 100644 --- a/src/App.css +++ b/src/App.css @@ -41,6 +41,19 @@ float: right; } +.replace-inputs { + display: flex; + flex-direction: column; + margin-top: 4px; +} +.replace-inputs input { + height: 20px; + margin-bottom: 5px; +} +.replace .button { + float: right; +} + .load-info-box { margin: auto; diff --git a/src/App.js b/src/App.js index d8dccf4..0d0de8e 100644 --- a/src/App.js +++ b/src/App.js @@ -33,10 +33,12 @@ import Settings from './settings'; import GearSvg from './icons/gear'; import CoffeIcon from './icons/coffee'; +import ReplaceIcon from './icons/replace'; import './App.css'; class App extends Component { + // TODO: merge the context property directly in the state? static INITIAL_CONTEXT = Object.freeze({ loadedJar: undefined, selectedFileName: undefined, @@ -134,6 +136,7 @@ class App extends Component { method, ...rest } = string; + const value = getUtf8String(classFile.constant_pool, constantIndex); const location = getInstructionLocation( classFile, @@ -198,6 +201,7 @@ class App extends Component { }; onStringChanged = (newValue, stringId) => { + // TODO: use better (constant) lookup const string = this.state.context.strings.find(s => s.id === stringId); if (newValue !== string.value) { @@ -250,8 +254,24 @@ class App extends Component {
); + onStringsReplaced = newStrings => { + this.setState(state => + update(state, { + context: { + strings: { $set: newStrings }, + }, + }) + ); + }; + + onToggleReplace = () => { + this.setState(({ displayReplace }) => ({ + displayReplace: !displayReplace, + })); + }; + render() { - const { loadInfo, context } = this.state; + const { loadInfo, context, displayReplace } = this.state; if (context.loadedJar === undefined) { return this.renderAppContainer( @@ -288,10 +308,23 @@ class App extends Component { after_filter: filtered.length, })} - + + + {displayReplace && ( + + )} @@ -316,6 +349,47 @@ class App extends Component { }; } +// TODO: add regex support? +class StringReplace extends Component { + inputFromRef = React.createRef(); + inputToRef = React.createRef(); + + replaceStrings = () => { + const { strings } = this.props; + const fromValue = this.inputFromRef.current.value; + const toValue = this.inputToRef.current.value; + + if (!fromValue || !toValue) { + return; + } + + for (const string of strings) { + if (!stringContains(string.value, fromValue, true)) { + continue; + } + string.value = string.value.replace(fromValue, toValue); + string.changed = true; + } + + this.props.onReplaced(strings); + }; + + render() { + return ( +
+ Replace +
+ + +
+ +
+ ); + } +} + const Link = props => ( ( + + + +); diff --git a/src/util/string-util.js b/src/util/string-util.js index 9968995..13bb7fb 100644 --- a/src/util/string-util.js +++ b/src/util/string-util.js @@ -20,7 +20,7 @@ * @param {string} haystack * @param {string} needle */ -export function stringContains(haystack, needle) { +export function stringContains(haystack, needle, caseSensitive = false) { let hay_pos = 0; let need_pos = 0; let eq_count = 0; @@ -29,7 +29,6 @@ export function stringContains(haystack, needle) { const hay_cur_char = haystack.charCodeAt(hay_pos++), needle_cur_char = needle.charCodeAt(need_pos++); - // Ignore case const case_diff = hay_cur_char >= 97 && hay_cur_char <= 122 ? -32 @@ -37,7 +36,7 @@ export function stringContains(haystack, needle) { if ( hay_cur_char === needle_cur_char || - hay_cur_char + case_diff === needle_cur_char + (!caseSensitive && hay_cur_char + case_diff === needle_cur_char) ) { eq_count++; } else { From be1f122ac36bd84f41581fb3eb2eb717fb6551ea Mon Sep 17 00:00:00 2001 From: Leonardo Santos Date: Mon, 8 Oct 2018 13:39:13 -0300 Subject: [PATCH 3/3] remove unused 'replace' icon --- src/App.js | 1 - src/icons/replace.js | 13 ------------- 2 files changed, 14 deletions(-) delete mode 100644 src/icons/replace.js diff --git a/src/App.js b/src/App.js index 0d0de8e..66aa156 100644 --- a/src/App.js +++ b/src/App.js @@ -33,7 +33,6 @@ import Settings from './settings'; import GearSvg from './icons/gear'; import CoffeIcon from './icons/coffee'; -import ReplaceIcon from './icons/replace'; import './App.css'; diff --git a/src/icons/replace.js b/src/icons/replace.js deleted file mode 100644 index b1d2e1b..0000000 --- a/src/icons/replace.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -export default () => ( - - - -);