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..66aa156 100644 --- a/src/App.js +++ b/src/App.js @@ -37,6 +37,7 @@ import CoffeIcon from './icons/coffee'; 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 +135,7 @@ class App extends Component { method, ...rest } = string; + const value = getUtf8String(classFile.constant_pool, constantIndex); const location = getInstructionLocation( classFile, @@ -198,6 +200,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 +253,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 +307,23 @@ class App extends Component { after_filter: filtered.length, })} - + + + {displayReplace && ( + + )} @@ -316,6 +348,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 => ( { + 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 ? ( +
+ +
+ ) : ( + + )}
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 {