Skip to content

Commit 2aa178e

Browse files
authored
Merge pull request #1070 from UC-Davis-molecular-computing/1055-context-menu-does-not-always-appear-on-right-click
1055 context menu does not always appear on right click
2 parents 88852bb + a1c6883 commit 2aa178e

17 files changed

Lines changed: 145 additions & 57 deletions

lib/src/actions/actions.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,8 +725,7 @@ abstract class CrossoverOpacitySameHelixSet
725725

726726
CrossoverOpacitySameHelixSet._();
727727

728-
static Serializer<CrossoverOpacitySameHelixSet> get serializer =>
729-
_$crossoverOpacitySameHelixSetSerializer;
728+
static Serializer<CrossoverOpacitySameHelixSet> get serializer => _$crossoverOpacitySameHelixSetSerializer;
730729
}
731730

732731
abstract class SetModificationDisplayConnector

lib/src/constants.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ const LEFT_CLICK_BUTTON = 0;
8585
const MIDDLE_CLICK_BUTTON = 1;
8686
const RIGHT_CLICK_BUTTON = 2;
8787

88+
const context_menu_event_name = 'contextmenu';
89+
8890
const KEY_CODE_SHOW_POTENTIAL_HELIX = KeyCode.H;
8991
const KEY_CODE_MOUSEOVER_HELIX_VIEW_INFO = KeyCode.W;
9092
final KEY_CODE_COMMAND_MAC =

lib/src/middleware/export_svg.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,13 +333,17 @@ void _remove_end_elements_if_needed(Element svg_element, {required bool export_5
333333
if (!export_5p) {
334334
// 5' ends are <rect> elements with class five-prime-end or five-prime-end-first-substrand
335335
svg_element
336-
.querySelectorAll('.${constants.css_selector_end_5p_domain}, .${constants.css_selector_end_5p_strand}')
336+
.querySelectorAll(
337+
'.${constants.css_selector_end_5p_domain}, .${constants.css_selector_end_5p_strand}',
338+
)
337339
.forEach((e) => e.remove());
338340
}
339341
if (!export_3p) {
340342
// 3' ends are <polygon> elements with class three-prime-end or three-prime-end-last-substrand
341343
svg_element
342-
.querySelectorAll('.${constants.css_selector_end_3p_domain}, .${constants.css_selector_end_3p_strand}')
344+
.querySelectorAll(
345+
'.${constants.css_selector_end_3p_domain}, .${constants.css_selector_end_3p_strand}',
346+
)
343347
.forEach((e) => e.remove());
344348
}
345349
}

lib/src/reducers/app_ui_state_reducer.dart

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -572,12 +572,14 @@ AppUIStateStorables app_ui_state_storable_local_reducer(AppUIStateStorables stor
572572
..major_tick_width_font_size = TypedReducer<double, actions.MajorTickWidthFontSizeSet>(
573573
major_tick_width_font_size_reducer,
574574
)(storables.major_tick_width_font_size, action)
575-
..stroke_width = TypedReducer<double, actions.StrokeWidthSet>(
576-
stroke_width_reducer,
577-
)(storables.stroke_width, action)
578-
..crossover_opacity = TypedReducer<double, actions.CrossoverOpacitySet>(
579-
crossover_opacity_reducer,
580-
)(storables.crossover_opacity, action)
575+
..stroke_width = TypedReducer<double, actions.StrokeWidthSet>(stroke_width_reducer)(
576+
storables.stroke_width,
577+
action,
578+
)
579+
..crossover_opacity = TypedReducer<double, actions.CrossoverOpacitySet>(crossover_opacity_reducer)(
580+
storables.crossover_opacity,
581+
action,
582+
)
581583
..crossover_opacity_same_helix = TypedReducer<double, actions.CrossoverOpacitySameHelixSet>(
582584
crossover_opacity_same_helix_reducer,
583585
)(storables.crossover_opacity_same_helix, action)
@@ -684,12 +686,14 @@ AppUIStateStorables app_ui_state_storable_local_reducer(AppUIStateStorables stor
684686
..export_svg_text_separately = TypedReducer<bool, actions.ExportSvgTextSeparatelySet>(
685687
export_svg_text_separately_reducer,
686688
)(storables.export_svg_text_separately, action)
687-
..export_svg_5p_ends = TypedReducer<bool, actions.ExportSvg5pEndsSet>(
688-
export_svg_5p_ends_reducer,
689-
)(storables.export_svg_5p_ends, action)
690-
..export_svg_3p_ends = TypedReducer<bool, actions.ExportSvg3pEndsSet>(
691-
export_svg_3p_ends_reducer,
692-
)(storables.export_svg_3p_ends, action)
689+
..export_svg_5p_ends = TypedReducer<bool, actions.ExportSvg5pEndsSet>(export_svg_5p_ends_reducer)(
690+
storables.export_svg_5p_ends,
691+
action,
692+
)
693+
..export_svg_3p_ends = TypedReducer<bool, actions.ExportSvg3pEndsSet>(export_svg_3p_ends_reducer)(
694+
storables.export_svg_3p_ends,
695+
action,
696+
)
693697
..ox_export_only_selected_strands = TypedReducer<bool, actions.OxExportOnlySelectedStrandsSet>(
694698
ox_export_only_selected_strands_reducer,
695699
)(storables.ox_export_only_selected_strands, action)

lib/src/view/design_main_dna_mismatches.dart

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,7 @@ class DesignMainDNAMismatchesComponent extends UiComponent2<DesignMainDNAMismatc
4444
var group = props.design.groups[helix.group]!;
4545
var geometry = group.geometry ?? props.design.geometry;
4646
var svg_position_y = props.helix_idx_to_svg_position_y_map[helix.idx]!;
47-
var base_svg_pos = helix.svg_base_pos(
48-
mismatch.offset,
49-
domain.forward,
50-
svg_position_y,
51-
geometry,
52-
);
47+
var base_svg_pos = helix.svg_base_pos(mismatch.offset, domain.forward, svg_position_y, geometry);
5348
// For now, if there is a mismatch in an insertion we simply display it for the whole insertion,
5449
// not for a specific base. We maintain React keys to agree on any mismatches in the same
5550
// insertion, and we only render one of them.

lib/src/view/design_main_helix.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,15 @@ class DesignMainHelixComponent extends UiComponent2<DesignMainHelixProps> with P
139139
componentDidMount() {
140140
if (props.show_helix_circles) {
141141
var elt = querySelector('#${group_id()}')!;
142-
elt.addEventListener('contextmenu', on_context_menu);
142+
elt.addEventListener(constants.context_menu_event_name, on_context_menu);
143143
}
144144
}
145145

146146
@override
147147
componentWillUnmount() {
148148
if (props.show_helix_circles) {
149149
var elt = querySelector('#${group_id()}')!;
150-
elt.removeEventListener('contextmenu', on_context_menu);
150+
elt.removeEventListener(constants.context_menu_event_name, on_context_menu);
151151
}
152152
super.componentWillUnmount();
153153
}

lib/src/view/design_main_strand_crossover.dart

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,38 @@ class DesignMainStrandCrossoverComponent
153153
// (Dom.svgTitle()(tooltip));
154154
}
155155

156+
// React may replace the underlying DOM element on re-render (e.g., after moving a strand),
157+
// so we track the element we attached to and re-attach in componentDidUpdate if it changed.
158+
Element? _attachedElement;
159+
160+
void _attachContextMenuListener() {
161+
var element = querySelector('#${props.crossover.id}');
162+
if (element != null && !identical(element, _attachedElement)) {
163+
_detachContextMenuListener();
164+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
165+
_attachedElement = element;
166+
}
167+
}
168+
169+
void _detachContextMenuListener() {
170+
_attachedElement?.removeEventListener(constants.context_menu_event_name, on_context_menu);
171+
_attachedElement = null;
172+
}
173+
156174
@override
157175
componentDidMount() {
158-
var element = querySelector('#${props.crossover.id}')!;
159-
element.addEventListener('contextmenu', on_context_menu);
176+
_attachContextMenuListener();
160177
super.componentDidMount();
161178
}
162179

180+
@override
181+
componentDidUpdate(Map prevProps, Map prevState, [dynamic snapshot]) {
182+
_attachContextMenuListener();
183+
}
184+
163185
@override
164186
componentWillUnmount() {
165-
var element = querySelector('#${props.crossover.id}')!;
166-
element.removeEventListener('contextmenu', on_context_menu);
187+
_detachContextMenuListener();
167188
super.componentWillUnmount();
168189
}
169190

lib/src/view/design_main_strand_dna_end.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ class DesignMainDNAEndComponent extends UiComponent2<DesignMainDNAEndProps> with
216216
id = props.domain != null ? props.domain!.dnaend_3p.id : props.ext!.dnaend_free.id;
217217
}
218218
var element = querySelector('#${id}')!;
219-
element.addEventListener('contextmenu', on_context_menu);
219+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
220220
}
221221

222222
@override
@@ -228,7 +228,7 @@ class DesignMainDNAEndComponent extends UiComponent2<DesignMainDNAEndProps> with
228228
id = props.domain != null ? props.domain!.dnaend_3p.id : props.ext!.dnaend_free.id;
229229
}
230230
var element = querySelector('#${id}')!;
231-
element.removeEventListener('contextmenu', on_context_menu);
231+
element.removeEventListener(constants.context_menu_event_name, on_context_menu);
232232
super.componentWillUnmount();
233233
}
234234

lib/src/view/design_main_strand_domain.dart

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,38 @@ class DesignMainDomainComponent extends UiComponent2<DesignMainDomainProps> with
189189

190190
// needed for capturing right-click events with React:
191191
// https://medium.com/@ericclemmons/react-event-preventdefault-78c28c950e46
192+
//
193+
// React may replace the underlying DOM element on re-render (e.g., after moving a strand),
194+
// so we track the element we attached to and re-attach in componentDidUpdate if it changed.
195+
Element? _attachedElement;
196+
197+
void _attachContextMenuListener() {
198+
var element = querySelector('#${props.domain.id}');
199+
if (element != null && !identical(element, _attachedElement)) {
200+
_detachContextMenuListener();
201+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
202+
_attachedElement = element;
203+
}
204+
}
205+
206+
void _detachContextMenuListener() {
207+
_attachedElement?.removeEventListener(constants.context_menu_event_name, on_context_menu);
208+
_attachedElement = null;
209+
}
210+
192211
@override
193212
componentDidMount() {
194-
var element = querySelector('#${props.domain.id}')!;
195-
element.addEventListener('contextmenu', on_context_menu);
213+
_attachContextMenuListener();
214+
}
215+
216+
@override
217+
componentDidUpdate(Map prevProps, Map prevState, [dynamic snapshot]) {
218+
_attachContextMenuListener();
196219
}
197220

198221
@override
199222
componentWillUnmount() {
200-
var element = querySelector('#${props.domain.id}')!;
201-
element.removeEventListener('contextmenu', on_context_menu);
223+
_detachContextMenuListener();
202224
super.componentWillUnmount();
203225
}
204226

lib/src/view/design_main_strand_domain_text.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,13 @@ class DesignMainStrandDomainTextComponent extends UiComponent2<DesignMainStrandD
8888
@override
8989
componentDidMount() {
9090
var element = querySelector('#${id()}')!;
91-
element.addEventListener('contextmenu', on_context_menu);
91+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
9292
}
9393

9494
@override
9595
componentWillUnmount() {
9696
var element = querySelector('#${id()}')!;
97-
element.removeEventListener('contextmenu', on_context_menu);
97+
element.removeEventListener(constants.context_menu_event_name, on_context_menu);
9898
super.componentWillUnmount();
9999
}
100100

0 commit comments

Comments
 (0)