Skip to content

Commit 21b2daf

Browse files
author
david califf
committed
Multiselect, allow users to select multiple commands with shift key
1 parent 9973c26 commit 21b2daf

File tree

5 files changed

+156
-28
lines changed

5 files changed

+156
-28
lines changed

Diff for: packages/selenium-ide/src/neo/components/TestRow/index.jsx

+53-8
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class TestRow extends React.Component {
108108
static propTypes = {
109109
index: PropTypes.number,
110110
selected: PropTypes.bool,
111+
lastSelected: PropTypes.bool,
111112
className: PropTypes.string,
112113
status: PropTypes.string,
113114
readOnly: PropTypes.bool,
@@ -133,17 +134,23 @@ class TestRow extends React.Component {
133134
setContextMenu: PropTypes.func,
134135
level: PropTypes.number,
135136
scrollToLastPos: PropTypes.func,
137+
selectCommandByRange: PropTypes.func,
136138
}
137139
componentDidMount() {
138140
if (this.props.selected) {
141+
this.node.focus()
139142
this.props.scrollToLastPos()
140143
this.props.setSectionFocus('editor', () => {
141144
this.node.focus()
142145
})
143146
}
144147
}
145148
componentDidUpdate(prevProps) {
146-
if (this.props.selected && !prevProps.selected) {
149+
if (
150+
this.props.selected &&
151+
!prevProps.lastSelected &&
152+
this.props.lastSelected
153+
) {
147154
this.scrollToRowIfNeeded(this.node)
148155
this.node.focus()
149156
this.props.setSectionFocus('editor', () => {
@@ -170,7 +177,7 @@ class TestRow extends React.Component {
170177
noModifiers &&
171178
(e.key === 'Delete' || e.key == 'Backspace')
172179
) {
173-
this.remove()
180+
this.remove(this.props.index)
174181
} else if (!this.props.isPristine && noModifiers && key === 'B') {
175182
this.props.command.toggleBreakpoint()
176183
} else if (!this.props.isPristine && noModifiers && key === 'S') {
@@ -185,13 +192,19 @@ class TestRow extends React.Component {
185192
this.props.executeCommand(this.props.command)
186193
} else if (this.props.moveSelection && noModifiers && e.key === 'ArrowUp') {
187194
e.preventDefault()
195+
if (!e.shiftKey) {
196+
this.props.clearAllSelectedCommands()
197+
}
188198
this.props.moveSelection(this.props.index - 1)
189199
} else if (
190200
this.props.moveSelection &&
191201
noModifiers &&
192202
e.key === 'ArrowDown'
193203
) {
194204
e.preventDefault()
205+
if (!e.shiftKey) {
206+
this.props.clearAllSelectedCommands()
207+
}
195208
this.props.moveSelection(this.props.index + 1)
196209
} else if (!this.props.isPristine && onlyPrimary && key === 'X') {
197210
this.cut()
@@ -219,20 +232,46 @@ class TestRow extends React.Component {
219232
cut() {
220233
if (!this.props.readOnly) {
221234
this.props.copyToClipboard(this.props.command)
222-
this.props.remove(this.props.index, this.props.command)
235+
this.props.remove()
223236
}
224237
}
225238
paste() {
226239
if (!this.props.readOnly) {
227240
this.props.pasteFromClipboard(this.props.index)
228241
}
229242
}
230-
select() {
243+
select(e) {
244+
if (
245+
(e.type == 'contextmenu' || e.type == 'mousedown') &&
246+
this.props.selected
247+
) {
248+
return
249+
}
250+
251+
if (e.type == 'click' && (e.shiftKey || this.props.lastSelected)) {
252+
if (!e.shiftKey) {
253+
this.props.clearAllSelectedCommands()
254+
this.props.select(this.props.command)
255+
}
256+
return
257+
}
258+
259+
if (e && e.shiftKey === false) {
260+
this.props.clearAllSelectedCommands()
261+
}
262+
if (this.props.isPristine) {
263+
this.props.clearAllSelectedCommands()
264+
}
265+
if (e && e.shiftKey && !this.props.isPristine) {
266+
this.props.selectCommandByRange(this.props.index)
267+
}
268+
231269
this.props.select(this.props.command)
232270
}
233-
remove() {
271+
272+
remove(index) {
234273
if (!this.props.readOnly) {
235-
this.props.remove(this.props.index, this.props.command)
274+
this.props.remove(index)
236275
}
237276
}
238277
async clearAll() {
@@ -278,7 +317,12 @@ class TestRow extends React.Component {
278317
>
279318
Paste
280319
</ListMenuItem>
281-
<ListMenuItem label="Del" onClick={this.remove}>
320+
<ListMenuItem
321+
label="Del"
322+
onClick={() => {
323+
this.remove(this.props.index)
324+
}}
325+
>
282326
Delete
283327
</ListMenuItem>
284328
<ListMenuSeparator />
@@ -363,13 +407,14 @@ class TestRow extends React.Component {
363407
: null
364408
}
365409
onClick={this.select}
410+
onFocus={this.select}
411+
onMouseDown={this.select}
366412
onDoubleClick={() => {
367413
this.props.executeCommand && this.props.singleCommandExecutionEnabled
368414
? this.props.executeCommand(this.props.command)
369415
: undefined
370416
}}
371417
onKeyDown={this.handleKeyDown.bind(this)}
372-
onFocus={this.select}
373418
style={{
374419
opacity: this.props.isDragging ? '0' : '1',
375420
}}

Diff for: packages/selenium-ide/src/neo/components/TestTable/index.jsx

+33-3
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,19 @@ export default class TestTable extends React.Component {
4242
)
4343
this.commandLevels = []
4444
this.node = null
45+
this.selectCommandByRange = this.selectCommandByRange.bind(this)
4546
}
4647
static propTypes = {
4748
commands: MobxPropTypes.arrayOrObservableArray,
4849
callstackIndex: PropTypes.number,
4950
selectedCommand: PropTypes.string,
51+
selectedCommands: MobxPropTypes.arrayOrObservableArray,
5052
selectCommand: PropTypes.func,
5153
addCommand: PropTypes.func,
52-
removeCommand: PropTypes.func,
54+
removeSelectedCommands: PropTypes.func,
5355
swapCommands: PropTypes.func,
5456
clearAllCommands: PropTypes.func,
57+
clearAllSelectedCommand: PropTypes.func,
5558
}
5659
detectNewCommand(change) {
5760
this.newCommand = change.added[0]
@@ -77,6 +80,20 @@ export default class TestTable extends React.Component {
7780
}
7881
}
7982
}
83+
selectCommandByRange(index) {
84+
if (this.props.selectedCommands.length > 0) {
85+
let fromIndex = this.props.commands.indexOf(
86+
this.props.selectedCommands[0]
87+
)
88+
fromIndex = fromIndex == -1 ? index : fromIndex
89+
const toIndex = index
90+
const from = fromIndex > toIndex ? toIndex : fromIndex
91+
const to = fromIndex > toIndex ? fromIndex : toIndex
92+
for (let i = from; i <= to; i++) {
93+
UiState.addToSelectedCommands(this.props.commands[i])
94+
}
95+
}
96+
}
8097
handleScroll() {
8198
UiState.selectedTest.test.scrollY = this.node.scrollTop
8299
}
@@ -130,7 +147,13 @@ export default class TestTable extends React.Component {
130147
).state
131148
: ''
132149
)}
133-
selected={this.props.selectedCommand === command.id}
150+
selected={
151+
this.props.selectedCommand === command.id ||
152+
!!this.props.selectedCommands.find(
153+
cmd => cmd.id === command.id
154+
)
155+
}
156+
lastSelected={this.props.selectedCommand === command.id}
134157
readOnly={PlaybackState.isPlaying}
135158
singleCommandExecutionEnabled={
136159
PlaybackState.isSingleCommandExecutionEnabled
@@ -145,15 +168,19 @@ export default class TestTable extends React.Component {
145168
scrollToLastPos={this.scrollToLastPos}
146169
isPristine={false}
147170
select={this.props.selectCommand}
171+
selectCommandByRange={this.selectCommandByRange}
148172
startPlayingHere={PlaybackState.startPlaying}
149173
executeCommand={PlaybackState.playCommand}
150174
moveSelection={UiState.selectCommandByIndex}
151175
addCommand={this.props.addCommand}
152-
remove={this.props.removeCommand}
176+
remove={this.props.removeSelectedCommands}
153177
swapCommands={this.props.swapCommands}
154178
copyToClipboard={UiState.copyToClipboard}
155179
pasteFromClipboard={UiState.pasteFromClipboard}
156180
clearAllCommands={this.props.clearAllCommands}
181+
clearAllSelectedCommands={
182+
this.props.clearAllSelectedCommands
183+
}
157184
setSectionFocus={UiState.setSectionFocus}
158185
level={this.commandLevels[index]}
159186
/>
@@ -174,6 +201,9 @@ export default class TestTable extends React.Component {
174201
moveSelection={UiState.selectCommandByIndex}
175202
pasteFromClipboard={UiState.pasteFromClipboard}
176203
setSectionFocus={UiState.setSectionFocus}
204+
clearAllSelectedCommands={
205+
this.props.clearAllSelectedCommands
206+
}
177207
/>
178208
)
179209
: null}

Diff for: packages/selenium-ide/src/neo/containers/Editor/index.jsx

+15-12
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default class Editor extends React.Component {
3838
constructor(props) {
3939
super(props)
4040
this.addCommand = this.addCommand.bind(this)
41-
this.removeCommand = this.removeCommand.bind(this)
41+
this.removeSelectedCommands = this.removeSelectedCommands.bind(this)
4242
}
4343
addCommand(index, command) {
4444
if (command) {
@@ -50,17 +50,18 @@ export default class Editor extends React.Component {
5050
return newCommand
5151
}
5252
}
53-
removeCommand(index, command) {
53+
removeSelectedCommands(index) {
5454
const { test } = this.props
55-
test.removeCommand(command)
56-
if (UiState.selectedCommand === command) {
57-
if (test.commands.length > index) {
58-
UiState.selectCommand(test.commands[index])
59-
} else if (test.commands.length) {
60-
UiState.selectCommand(test.commands[test.commands.length - 1])
61-
} else {
62-
UiState.selectCommand(UiState.pristineCommand)
63-
}
55+
index = index - (UiState.selectedCommands.length - 1)
56+
index = Math.max(index, 0)
57+
UiState.selectedCommands.forEach(command => test.removeCommand(command))
58+
UiState.clearAllSelectedCommands()
59+
if (test.commands.length > index) {
60+
UiState.selectCommand(test.commands[index])
61+
} else if (test.commands.length) {
62+
UiState.selectCommand(test.commands[test.commands.length - 1])
63+
} else {
64+
UiState.selectCommand(UiState.pristineCommand)
6465
}
6566
}
6667
handleKeyDown(event) {
@@ -94,12 +95,14 @@ export default class Editor extends React.Component {
9495
selectedCommand={
9596
UiState.selectedCommand ? UiState.selectedCommand.id : null
9697
}
98+
selectedCommands={UiState.selectedCommands}
9799
selectCommand={UiState.selectCommand}
98100
addCommand={this.addCommand}
99-
removeCommand={this.removeCommand}
101+
removeSelectedCommands={this.removeSelectedCommands}
100102
clearAllCommands={
101103
this.props.test ? this.props.test.clearAllCommands : null
102104
}
105+
clearAllSelectedCommands={UiState.clearAllSelectedCommands}
103106
swapCommands={this.props.test ? this.props.test.swapCommands : null}
104107
/>
105108
<CommandForm

Diff for: packages/selenium-ide/src/neo/models/TestCase.js

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export default class TestCase {
3131
@observable
3232
selectedCommand = null
3333
@observable
34+
selectedCommands = []
35+
@observable
3436
scrollY = null
3537

3638
constructor(id = uuidv4(), name = 'Untitled Test') {

Diff for: packages/selenium-ide/src/neo/stores/view/UiState.js

+53-5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class UiState {
3636
@observable
3737
selectedCommand = null
3838
@observable
39+
selectedCommands = []
40+
@observable
3941
filterTerm = ''
4042
@observable
4143
clipboard = null
@@ -168,15 +170,22 @@ class UiState {
168170
}
169171

170172
@action.bound
171-
copyToClipboard(item) {
172-
this.clipboard = item
173+
copyToClipboard() {
174+
// sorting by index
175+
this.clipboard = this.selectedCommands.sort((c1, c2) => {
176+
let c1Index = this.displayedTest.commands.indexOf(c1)
177+
let c2Index = this.displayedTest.commands.indexOf(c2)
178+
return c1Index - c2Index
179+
})
173180
}
174181

175182
@action.bound
176183
pasteFromClipboard(index) {
177-
if (this.clipboard && this.displayedTest) {
178-
const newCommand = this.clipboard.clone()
179-
this.displayedTest.insertCommandAt(newCommand, index)
184+
if (this.clipboard.length && this.displayedTest) {
185+
this.clipboard.forEach((command, idx) => {
186+
const newCommand = command.clone()
187+
this.displayedTest.insertCommandAt(newCommand, index + idx + 1)
188+
})
180189
}
181190
}
182191

@@ -200,11 +209,18 @@ class UiState {
200209
_test &&
201210
(_test !== this.displayedTest || suite !== this.selectedTest.suite)
202211
) {
212+
if (this.selectedTest.test) {
213+
this.selectedTest.test.selectedCommands = this.selectedCommands.slice(
214+
0
215+
)
216+
}
217+
this.selectedCommands.clear()
203218
this.selectedTest = {
204219
test,
205220
suite,
206221
stack: stack >= 0 ? stack : undefined,
207222
}
223+
this.selectedCommands = this.selectedTest.test.selectedCommands.slice(0)
208224
if (PlaybackState.isPlaying && !PlaybackState.paused) {
209225
this.selectCommand(undefined)
210226
} else if (_test && _test.commands.length) {
@@ -295,6 +311,7 @@ class UiState {
295311
if (this.selectedTest.test) {
296312
this.selectedTest.test.selectedCommand = command
297313
this.selectedCommand = command
314+
this.addToSelectedCommands(command)
298315
} else {
299316
this.selectedCommand = undefined
300317
}
@@ -307,10 +324,41 @@ class UiState {
307324
if (index >= 0 && index < test.commands.length) {
308325
this.selectCommand(test.commands[index], opts)
309326
} else if (index === test.commands.length) {
327+
this.clearAllSelectedCommands()
310328
this.selectCommand(this.pristineCommand, opts)
311329
}
312330
}
313331

332+
@action.bound
333+
clearAllSelectedCommands() {
334+
if (this.selectedTest.test) {
335+
this.selectedTest.test.selectedCommand = undefined
336+
this.selectedTest.test.selectedCommands.clear()
337+
this.selectedCommands.clear()
338+
this.selectedCommand = undefined
339+
} else {
340+
this.selectedCommand = undefined
341+
this.selectedCommands.clear()
342+
}
343+
}
344+
345+
@action.bound
346+
addToSelectedCommands(command) {
347+
if (!PlaybackState.isPlaying || PlaybackState.paused) {
348+
if (command) {
349+
if (
350+
this.selectTest &&
351+
this.selectedTest.test.commands.find(c => c === command)
352+
) {
353+
if (
354+
this.selectedCommands.findIndex(c => c.id === command.id) === -1
355+
) {
356+
this.selectedCommands.push(command)
357+
}
358+
}
359+
}
360+
}
361+
}
314362
@action.bound
315363
selectNextCommand(opts = { from: undefined, isCommandTarget: false }) {
316364
this.selectCommandByIndex(this.nextCommandIndex(opts.from), opts)

0 commit comments

Comments
 (0)