Skip to content

Commit

Permalink
closes #501; make deletions/insertions selectable/deletable; this mak…
Browse files Browse the repository at this point in the history
…es it easier to select large groups of deletions or insertions and delete them, instead of clicking one at a time
  • Loading branch information
dave-doty committed Nov 6, 2020
1 parent 2a6e225 commit 8112c43
Show file tree
Hide file tree
Showing 16 changed files with 396 additions and 52 deletions.
2 changes: 2 additions & 0 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ const css_selector_helix__mouseover_invisible_rectangle = 'helix-mouseover';

const css_selector_insertion = 'insertion-curve';
const css_selector_deletion = 'deletion-cross';
const css_selector_insertion_group = 'insertion-group';
const css_selector_deletion_group = 'deletion-group';
const css_selector_selected = 'selected';


3 changes: 2 additions & 1 deletion lib/src/middleware/selections_intersect_box_compute.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ selections_intersect_box_compute_middleware(Store<AppState> store, action, NextD

//XXX: Firefox does not have getIntersectionList or getEnclosureList
// (no progress for 10 years on that: https://bugzilla.mozilla.org/show_bug.cgi?id=501421)
// Besides, it didn't work well in Chrome and I basically had to implement it myself based on bounding boxes.
// Besides, it didn't work well in Chrome and I
// basically had to implement it myself based on bounding boxes.

bool is_origami = state.design.is_origami;
var select_modes = state.ui_state.select_mode_state.modes;
Expand Down
67 changes: 66 additions & 1 deletion lib/src/reducers/delete_reducer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ BuiltList<Strand> delete_all_reducer(
} else if (select_mode_state.domains_selectable()) {
var domains = List<Domain>.from(items.where((item) => item is Domain));
strands = remove_domains(strands, state, domains);
} else if (select_mode_state.deletions_selectable() || select_mode_state.insertions_selectable()) {
var deletions = select_mode_state.deletions_selectable()
? List<SelectableDeletion>.from(items.where((item) => item is SelectableDeletion))
: [];
var insertions = select_mode_state.insertions_selectable()
? List<SelectableInsertion>.from(items.where((item) => item is SelectableInsertion))
: [];
strands = remove_deletions_and_insertions(strands, state, deletions, insertions);
}

return strands;
Expand Down Expand Up @@ -210,7 +218,7 @@ BuiltList<Strand> remove_domains(BuiltList<Strand> strands, AppState state, Iter
strand_to_substrands[strand].add(substrand);
}

// remove crossovers one strand at a time
// remove domains one strand at a time
for (var strand in strand_to_substrands.keys) {
strands_to_remove.add(strand);
var split_strands = _remove_domains_from_strand(strand, strand_to_substrands[strand]);
Expand Down Expand Up @@ -259,3 +267,60 @@ List<Strand> _remove_domains_from_strand(Strand strand, Set<Domain> substrands_t

return create_new_strands_from_substrand_lists(substrands_list, strand);
}

BuiltList<Strand> remove_deletions_and_insertions(BuiltList<Strand> strands, AppState state,
List<SelectableDeletion> deletions, List<SelectableInsertion> insertions) {
// collect all deletions/insertions for each strand

Map<Strand, Map<Domain, Set<SelectableDeletion>>> strand_to_deletions = {};
Map<Strand, Map<Domain, Set<SelectableInsertion>>> strand_to_insertions = {};
for (var strand in strands) {
strand_to_deletions[strand] = {};
strand_to_insertions[strand] = {};
for (var domain in strand.domains()) {
strand_to_deletions[strand][domain] = {};
strand_to_insertions[strand][domain] = {};
}
}
for (var deletion in deletions) {
var strand = state.design.substrand_to_strand[deletion.domain];
strand_to_deletions[strand][deletion.domain].add(deletion);
}
for (var insertion in insertions) {
var strand = state.design.substrand_to_strand[insertion.domain];
strand_to_insertions[strand][insertion.domain].add(insertion);
}

// remove deletions/insertions one strand at a time
// need to do deletions and insertions at the same time because Domain built object will be invalidated
// after removing one of them.
var new_strands = strands.toList();
for (int i = 0; i < strands.length; i++) {
Strand strand = strands[i];
var substrands = strand.substrands.toList();
for (int j = 0; j < substrands.length; j++) {
if (substrands[j] is Domain) {
var domain = substrands[j] as Domain;
var deletions = strand_to_deletions[strand][domain];
var insertions = strand_to_insertions[strand][domain];
var deletions_offsets_to_remove = {for (var deletion in deletions) deletion.offset};
var insertions_offsets_to_remove = {for (var insertion in insertions) insertion.insertion.offset};
if (deletions_offsets_to_remove.isNotEmpty || insertions_offsets_to_remove.isNotEmpty) {
var deletions_existing = domain.deletions.toList();
var insertions_existing = domain.insertions.toList();
deletions_existing.removeWhere((offset) => deletions_offsets_to_remove.contains(offset));
insertions_existing
.removeWhere((insertion) => insertions_offsets_to_remove.contains(insertion.offset));
domain = domain.rebuild(
(b) => b..deletions.replace(deletions_existing)..insertions.replace(insertions_existing));
substrands[j] = domain;
}
}
}
strand = strand.rebuild((b) => b..substrands.replace(substrands));
strand = strand.initialize();
new_strands[i] = strand;
}

return new_strands.build();
}
60 changes: 50 additions & 10 deletions lib/src/reducers/select_mode_state_reducer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,31 @@ SelectModeState toggle_select_mode_reducer(SelectModeState state, actions.Select
} else if (SelectModeChoice.strand_parts.contains(mode)) {
new_state = new_state.remove_mode(SelectModeChoice.strand);
if (mode == SelectModeChoice.crossover || mode == SelectModeChoice.loopout) {
new_state = new_state.remove_modes(SelectModeChoice.ends.toList() + [SelectModeChoice.domain]);
new_state = new_state.remove_modes(SelectModeChoice.ends.toList() +
[SelectModeChoice.domain, SelectModeChoice.deletion, SelectModeChoice.insertion]);
} else if (SelectModeChoice.ends.contains(mode)) {
new_state = new_state
.remove_modes([SelectModeChoice.crossover, SelectModeChoice.loopout, SelectModeChoice.domain]);
new_state = new_state.remove_modes([
SelectModeChoice.crossover,
SelectModeChoice.loopout,
SelectModeChoice.domain,
SelectModeChoice.deletion,
SelectModeChoice.insertion,
]);
} else if (mode == SelectModeChoice.domain) {
new_state = new_state.remove_modes(
SelectModeChoice.ends.toList() + [SelectModeChoice.crossover, SelectModeChoice.loopout]);
new_state = new_state.remove_modes(SelectModeChoice.ends.toList() +
[
SelectModeChoice.crossover,
SelectModeChoice.loopout,
SelectModeChoice.deletion,
SelectModeChoice.insertion,
]);
} else if (mode == SelectModeChoice.deletion || mode == SelectModeChoice.insertion) {
new_state = new_state.remove_modes(SelectModeChoice.ends.toList() +
[
SelectModeChoice.crossover,
SelectModeChoice.loopout,
SelectModeChoice.domain,
]);
}
}
}
Expand All @@ -49,13 +67,35 @@ SelectModeState add_select_modes_reducer(SelectModeState state, actions.SelectMo
} else if (SelectModeChoice.strand_parts.contains(mode)) {
new_state = new_state.remove_mode(SelectModeChoice.strand);
if (mode == SelectModeChoice.crossover || mode == SelectModeChoice.loopout) {
new_state = new_state.remove_modes(SelectModeChoice.ends.toList() + [SelectModeChoice.domain]);
new_state = new_state.remove_modes(SelectModeChoice.ends.toList() +
[
SelectModeChoice.domain,
SelectModeChoice.deletion,
SelectModeChoice.insertion,
]);
} else if (SelectModeChoice.ends.contains(mode)) {
new_state = new_state
.remove_modes([SelectModeChoice.crossover, SelectModeChoice.loopout, SelectModeChoice.domain]);
new_state = new_state.remove_modes([
SelectModeChoice.crossover,
SelectModeChoice.loopout,
SelectModeChoice.domain,
SelectModeChoice.deletion,
SelectModeChoice.insertion,
]);
} else if (mode == SelectModeChoice.domain) {
new_state = new_state.remove_modes(
SelectModeChoice.ends.toList() + [SelectModeChoice.crossover, SelectModeChoice.loopout]);
new_state = new_state.remove_modes(SelectModeChoice.ends.toList() +
[
SelectModeChoice.crossover,
SelectModeChoice.loopout,
SelectModeChoice.deletion,
SelectModeChoice.insertion,
]);
}else if (mode == SelectModeChoice.deletion || mode == SelectModeChoice.insertion) {
new_state = new_state.remove_modes(SelectModeChoice.ends.toList() +
[
SelectModeChoice.crossover,
SelectModeChoice.loopout,
SelectModeChoice.domain,
]);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/src/serializers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ part 'serializers.g.dart';
ExportDNA,
ExportDNAFormat,
ErrorMessageSet,
Insertion,
SelectableInsertion,
SelectableDeletion,
InsertionAdd,
DeletionAdd,
InsertionRemove,
Expand Down
36 changes: 35 additions & 1 deletion lib/src/state/design.dart
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,32 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
return builder.build();
}

@memoized
BuiltMap<String, SelectableDeletion> get deletions_by_id {
var builder = MapBuilder<String, SelectableDeletion>();
for (var strand in strands) {
for (var domain in strand.domains()) {
for (var deletion in domain.selectable_deletions) {
builder[deletion.id()] = deletion;
}
}
}
return builder.build();
}

@memoized
BuiltMap<String, SelectableInsertion> get insertions_by_id {
var builder = MapBuilder<String, SelectableInsertion>();
for (var strand in strands) {
for (var domain in strand.domains()) {
for (var insertion in domain.selectable_insertions) {
builder[insertion.id()] = insertion;
}
}
}
return builder.build();
}

@memoized
BuiltMap<String, DNAEnd> get ends_by_id {
var builder = MapBuilder<String, DNAEnd>();
Expand Down Expand Up @@ -357,7 +383,15 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
@memoized
BuiltMap<String, Selectable> get selectable_by_id {
Map<String, Selectable> map = {};
for (var map_small in [strands_by_id, loopouts_by_id, crossovers_by_id, ends_by_id, domains_by_id]) {
for (var map_small in [
strands_by_id,
loopouts_by_id,
crossovers_by_id,
ends_by_id,
domains_by_id,
deletions_by_id,
insertions_by_id,
]) {
for (var key in map_small.keys) {
var obj = map_small[key];
map[key] = obj;
Expand Down
27 changes: 20 additions & 7 deletions lib/src/state/domain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ abstract class Domain
substrand_is_last: is_last,
substrand_id: id());

@memoized
BuiltList<SelectableDeletion> get selectable_deletions => [
for (int deletion in deletions)
SelectableDeletion(offset: deletion, domain: this, is_scaffold: is_scaffold)
].build();


@memoized
BuiltList<SelectableInsertion> get selectable_insertions => [
for (var insertion in insertions)
SelectableInsertion(insertion: insertion, domain: this, is_scaffold: is_scaffold)
].build();

Domain set_start(int start_new) => rebuild((ss) => ss..start = start_new);

Domain set_end(int end_new) => rebuild((ss) => ss..end = end_new);
Expand Down Expand Up @@ -221,16 +234,16 @@ abstract class Domain

deletions = util.remove_duplicates(deletions);
insertions = util.remove_duplicates(insertions);
for (int i=0; i<insertions.length; i++) {
for (int j=i+1; j<insertions.length; j++) {
for (int i = 0; i < insertions.length; i++) {
for (int j = i + 1; j < insertions.length; j++) {
var ins1 = insertions[i];
var ins2 = insertions[j];
if (ins1.offset == ins2.offset) {
assert(ins1.length != ins2.length);
throw IllegalDesignError('two insertions on a domain have the same offset but different lengths:'
'\n${ins1}'
'\n${ins2}'
'\n${pre_domain_description(helix, forward, start, end)}');
'\n${pre_domain_description(helix, forward, start, end)}');
}
}
}
Expand Down Expand Up @@ -277,10 +290,10 @@ abstract class Domain
}

static String pre_domain_description(int helix, bool forward, int start, int end) =>
'This occurred on a ${forward? "forward": "reverse"} Domain with'
'\n helix = ${helix}'
'\n start = ${start}'
'\n end = ${end}.';
'This occurred on a ${forward ? "forward" : "reverse"} Domain with'
'\n helix = ${helix}'
'\n start = ${start}'
'\n end = ${end}.';

static List<Insertion> parse_json_insertions(json_encoded_insertions) {
// need to use List.from because List.map returns Iterable, not List
Expand Down
8 changes: 8 additions & 0 deletions lib/src/state/select_mode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class SelectModeChoice extends EnumClass {
static const SelectModeChoice crossover = _$crossover;
static const SelectModeChoice loopout = _$loopout;
static const SelectModeChoice strand = _$strand;
static const SelectModeChoice deletion = _$deletion;
static const SelectModeChoice insertion = _$insertion;
static const SelectModeChoice scaffold = _$scaffold;
static const SelectModeChoice staple = _$staple;

Expand Down Expand Up @@ -61,6 +63,10 @@ class SelectModeChoice extends EnumClass {
return constants.css_selector_crossover;
case loopout:
return constants.css_selector_loopout;
case deletion:
return constants.css_selector_deletion;
case insertion:
return constants.css_selector_insertion;
case strand:
return constants.css_selector_strand;
case scaffold:
Expand All @@ -87,6 +93,8 @@ class SelectModeChoice extends EnumClass {
end_3p_domain,
crossover,
loopout,
deletion,
insertion,
]);

static final BuiltList<SelectModeChoice> ends = BuiltList<SelectModeChoice>([
Expand Down
6 changes: 6 additions & 0 deletions lib/src/state/select_mode_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ abstract class SelectModeState implements Built<SelectModeState, SelectModeState
bool domains_selectable() =>
modes.contains(SelectModeChoice.domain);

bool deletions_selectable() =>
modes.contains(SelectModeChoice.deletion);

bool insertions_selectable() =>
modes.contains(SelectModeChoice.insertion);

String to_json() {
List<String> lst = [for (var mode in modes) mode.name];
var js = jsonEncode(lst);
Expand Down
Loading

0 comments on commit 8112c43

Please sign in to comment.