gramene-search 2.21.0 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -850,6 +850,42 @@ function $af4441dd29af05df$var$markSubtree(node, source, invert) {
850
850
  });
851
851
  }
852
852
  const $af4441dd29af05df$export$6b95404109a3e2ec = 'Genomic intervals';
853
+ function $af4441dd29af05df$export$634f7214da1b780a({ map: map, region: region, start: start, end: end }) {
854
+ return `(map:${map} AND region:${region}` + ` AND start:[* TO ${end}] AND end:[${start} TO *])`;
855
+ }
856
+ function $af4441dd29af05df$export$535c09441f37845d({ mapLabel: mapLabel, map: map, region: region, start: start, end: end }) {
857
+ return `${mapLabel || map} ${region}:${Number(start).toLocaleString()}-${Number(end).toLocaleString()}`;
858
+ }
859
+ // Note the field order is inverted relative to the interval: `start` carries the
860
+ // interval's END and `end` carries its START, because the pair is an overlap test.
861
+ const $af4441dd29af05df$var$INTERVAL_RE = /^\(map:(\S+) AND region:(\S+) AND start:\[\* TO (\d+)\] AND end:\[(\d+) TO \*\]\)$/;
862
+ function $af4441dd29af05df$export$67920d3e3cb3f136(fqValue) {
863
+ const m = $af4441dd29af05df$var$INTERVAL_RE.exec(String(fqValue || ''));
864
+ if (!m) return null;
865
+ return {
866
+ map: m[1],
867
+ region: m[2],
868
+ end: Number(m[3]),
869
+ start: Number(m[4])
870
+ };
871
+ }
872
+ /** `[lo TO hi]`, as produced by the expression brush and the Location tab. */ const $af4441dd29af05df$var$RANGE_RE = /^\[\s*(\S+)\s+TO\s+(\S+)\s*\]$/;
873
+ function $af4441dd29af05df$export$9ee0ceaa589fc567(fqValue) {
874
+ const m = $af4441dd29af05df$var$RANGE_RE.exec(String(fqValue || ''));
875
+ return m ? {
876
+ lo: m[1],
877
+ hi: m[2]
878
+ } : null;
879
+ }
880
+ function $af4441dd29af05df$export$cd9b2ffd001c6d65(lo, hi) {
881
+ return `[${lo} TO ${hi}]`;
882
+ }
883
+ function $af4441dd29af05df$export$a7f4fa59930e5088(node) {
884
+ if (!node || node.hasOwnProperty('children')) return null;
885
+ if ($af4441dd29af05df$export$67920d3e3cb3f136(node.fq_value)) return 'interval';
886
+ if ($af4441dd29af05df$export$9ee0ceaa589fc567(node.fq_value)) return 'range';
887
+ return 'value';
888
+ }
853
889
  const $af4441dd29af05df$export$da3188ffd2da2525 = {
854
890
  orthologs: {
855
891
  label: 'orthologs',
@@ -1061,8 +1097,19 @@ const $af4441dd29af05df$var$grameneFilters = {
1061
1097
  // start, so a gene that merely crosses the boundary still matches.
1062
1098
  const intervalNode = {
1063
1099
  fq_field: 'location',
1064
- fq_value: `(map:${map} AND region:${region}` + ` AND start:[* TO ${end}] AND end:[${start} TO *])`,
1065
- name: `${mapLabel} ${region}:${start.toLocaleString()}-${end.toLocaleString()}`,
1100
+ fq_value: $af4441dd29af05df$export$634f7214da1b780a({
1101
+ map: map,
1102
+ region: region,
1103
+ start: start,
1104
+ end: end
1105
+ }),
1106
+ name: $af4441dd29af05df$export$535c09441f37845d({
1107
+ mapLabel: mapLabel,
1108
+ map: map,
1109
+ region: region,
1110
+ start: start,
1111
+ end: end
1112
+ }),
1066
1113
  category: 'Genomic interval',
1067
1114
  negate: false,
1068
1115
  showMenu: false
@@ -1084,6 +1131,24 @@ const $af4441dd29af05df$var$grameneFilters = {
1084
1131
  $af4441dd29af05df$var$markSubtree(newState, newState, false);
1085
1132
  return newState;
1086
1133
  }
1134
+ case 'GRAMENE_FILTER_EDITED':
1135
+ {
1136
+ // Edits only rewrite a leaf's value and label. The node keeps its place,
1137
+ // its negate flag and any expansion, and the tree shape is untouched — so
1138
+ // unlike an insert there is nothing to reindex.
1139
+ newState = Object.assign({}, state, {
1140
+ status: 'search',
1141
+ showMarked: true,
1142
+ searchOffset: 0
1143
+ });
1144
+ const node = $af4441dd29af05df$var$findNodeWithLeftIdx(newState, payload.leftIdx);
1145
+ if (!node || node.hasOwnProperty('children')) break;
1146
+ node.fq_value = payload.fq_value;
1147
+ if (payload.name !== undefined) node.name = payload.name;
1148
+ node.showMenu = false;
1149
+ $af4441dd29af05df$var$markSubtree(newState, node, false);
1150
+ return newState;
1151
+ }
1087
1152
  case 'GRAMENE_FILTER_EXPANSION_SET':
1088
1153
  {
1089
1154
  // Setting or clearing an expansion is just a field on the node — the tree
@@ -1419,6 +1484,28 @@ const $af4441dd29af05df$var$grameneFilters = {
1419
1484
  ]
1420
1485
  });
1421
1486
  },
1487
+ // Replace one leaf's value in place, keeping its position in the tree.
1488
+ doEditGrameneFilter: (filter, fq_value, name)=>({ dispatch: dispatch })=>{
1489
+ dispatch({
1490
+ type: 'BATCH_ACTIONS',
1491
+ actions: [
1492
+ {
1493
+ type: 'GRAMENE_SEARCH_CLEARED'
1494
+ },
1495
+ {
1496
+ type: 'GRAMENE_TAXONOMY_CLEARED'
1497
+ },
1498
+ {
1499
+ type: 'GRAMENE_FILTER_EDITED',
1500
+ payload: {
1501
+ leftIdx: filter.leftIdx,
1502
+ fq_value: fq_value,
1503
+ name: name
1504
+ }
1505
+ }
1506
+ ]
1507
+ });
1508
+ },
1422
1509
  doToggleGrameneFilterMenu: (node)=>({ dispatch: dispatch })=>{
1423
1510
  dispatch({
1424
1511
  type: 'GRAMENE_FILTER_MENU_TOGGLED',
@@ -20883,18 +20970,26 @@ function $909272efb41be16f$export$94955a075dcb14b2(genome) {
20883
20970
  length: lengths[i]
20884
20971
  })).filter((r)=>!$909272efb41be16f$var$UNPLACED.test(String(r.name)));
20885
20972
  }
20886
- const $909272efb41be16f$var$GenomicIntervalForm = ({ grameneMaps: grameneMaps, targetTaxonId: targetTaxonId, onAdd: onAdd })=>{
20887
- const [open, setOpen] = (0, $gXNCa$react.useState)(false);
20973
+ const $909272efb41be16f$export$4fa3dba3103266ea = ({ grameneMaps: grameneMaps, targetTaxonId: targetTaxonId, initial: initial, submitLabel: submitLabel, onSubmit: onSubmit, onCancel: onCancel })=>{
20888
20974
  const [taxonId, setTaxonId] = (0, $gXNCa$react.useState)('');
20889
- const [region, setRegion] = (0, $gXNCa$react.useState)('');
20890
- const [start, setStart] = (0, $gXNCa$react.useState)('');
20891
- const [end, setEnd] = (0, $gXNCa$react.useState)('');
20975
+ const [region, setRegion] = (0, $gXNCa$react.useState)(initial ? String(initial.region) : '');
20976
+ const [start, setStart] = (0, $gXNCa$react.useState)(initial ? String(initial.start) : '');
20977
+ const [end, setEnd] = (0, $gXNCa$react.useState)(initial ? String(initial.end) : '');
20892
20978
  const { reference: reference, anchors: anchors, rest: rest } = (0, $gXNCa$react.useMemo)(()=>$909272efb41be16f$export$ac97d17982e0cb21(grameneMaps, targetTaxonId), [
20893
20979
  grameneMaps,
20894
20980
  targetTaxonId
20895
20981
  ]);
20896
- // Default to the site's reference genome once maps have loaded.
20897
- const effectiveTaxon = taxonId || reference[0] && String(reference[0].taxon_id) || '';
20982
+ // An edit starts on the filter's own genome; a new interval starts on the
20983
+ // site's reference. `initial.map` is an assembly accession, so find its genome.
20984
+ const initialTaxon = (0, $gXNCa$react.useMemo)(()=>{
20985
+ if (!initial) return '';
20986
+ const hit = Object.values(grameneMaps || {}).find((m)=>m && m._id === initial.map);
20987
+ return hit ? String(hit.taxon_id) : '';
20988
+ }, [
20989
+ initial,
20990
+ grameneMaps
20991
+ ]);
20992
+ const effectiveTaxon = taxonId || initialTaxon || reference[0] && String(reference[0].taxon_id) || '';
20898
20993
  const genome = effectiveTaxon ? (grameneMaps || {})[effectiveTaxon] : null;
20899
20994
  const chromosomes = (0, $gXNCa$react.useMemo)(()=>$909272efb41be16f$export$94955a075dcb14b2(genome), [
20900
20995
  genome
@@ -20906,8 +21001,7 @@ const $909272efb41be16f$var$GenomicIntervalForm = ({ grameneMaps: grameneMaps, t
20906
21001
  // Validated against the assembly's own region lengths, so the message can name
20907
21002
  // the actual limit rather than just refusing.
20908
21003
  const error = (()=>{
20909
- if (!genome) return null;
20910
- if (s === null || e === null) return null;
21004
+ if (!genome || s === null || e === null) return null;
20911
21005
  if (!Number.isFinite(s) || !Number.isFinite(e)) return 'Positions must be numbers.';
20912
21006
  if (s < 1 || e < 1) return 'Positions start at 1.';
20913
21007
  if (s > e) return 'Start must not be greater than end.';
@@ -20917,112 +21011,134 @@ const $909272efb41be16f$var$GenomicIntervalForm = ({ grameneMaps: grameneMaps, t
20917
21011
  const ready = !!genome && !!effectiveRegion && s !== null && e !== null && !error;
20918
21012
  const submit = ()=>{
20919
21013
  if (!ready) return;
20920
- onAdd({
21014
+ onSubmit({
20921
21015
  map: genome._id,
20922
21016
  mapLabel: genome.display_name || genome._id,
20923
21017
  region: effectiveRegion,
20924
21018
  start: s,
20925
21019
  end: e
20926
21020
  });
20927
- setStart('');
20928
- setEnd('');
21021
+ if (!initial) {
21022
+ setStart('');
21023
+ setEnd('');
21024
+ }
20929
21025
  };
20930
21026
  const genomeOption = (m)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("option", {
20931
21027
  value: m.taxon_id,
20932
21028
  children: m.display_name || m.system_name
20933
21029
  }, m.taxon_id);
20934
21030
  return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
20935
- className: "gramene-interval",
21031
+ className: "gramene-interval-form",
20936
21032
  children: [
20937
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
20938
- className: "gramene-interval-toggle",
20939
- onClick: ()=>setOpen(!open),
21033
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Form).Select, {
21034
+ size: "sm",
21035
+ className: "mb-1",
21036
+ value: effectiveTaxon,
21037
+ onChange: (ev)=>{
21038
+ setTaxonId(ev.target.value);
21039
+ setRegion('');
21040
+ },
20940
21041
  children: [
20941
- open ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsChevronDown), {}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsChevronRight), {}),
20942
- " Add genomic interval"
21042
+ reference.map(genomeOption),
21043
+ anchors.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("optgroup", {
21044
+ label: "Reference genomes",
21045
+ children: anchors.map(genomeOption)
21046
+ }),
21047
+ rest.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("optgroup", {
21048
+ label: "Other genomes",
21049
+ children: rest.map(genomeOption)
21050
+ })
20943
21051
  ]
20944
21052
  }),
20945
- open && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
20946
- className: "gramene-interval-form",
20947
- children: [
20948
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Form).Select, {
20949
- size: "sm",
20950
- className: "mb-1",
20951
- value: effectiveTaxon,
20952
- onChange: (ev)=>{
20953
- setTaxonId(ev.target.value);
20954
- setRegion('');
20955
- },
21053
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Select, {
21054
+ size: "sm",
21055
+ className: "mb-1",
21056
+ value: effectiveRegion,
21057
+ onChange: (ev)=>setRegion(ev.target.value),
21058
+ children: chromosomes.map((c)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("option", {
21059
+ value: c.name,
20956
21060
  children: [
20957
- reference.map(genomeOption),
20958
- anchors.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("optgroup", {
20959
- label: "Reference genomes",
20960
- children: anchors.map(genomeOption)
20961
- }),
20962
- rest.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("optgroup", {
20963
- label: "Other genomes",
20964
- children: rest.map(genomeOption)
20965
- })
21061
+ c.name,
21062
+ c.length ? ` (${Math.round(c.length / 1e6)} Mb)` : ''
20966
21063
  ]
20967
- }),
20968
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Select, {
21064
+ }, c.name))
21065
+ }),
21066
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21067
+ className: "gramene-interval-range",
21068
+ children: [
21069
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
20969
21070
  size: "sm",
20970
- className: "mb-1",
20971
- value: effectiveRegion,
20972
- onChange: (ev)=>setRegion(ev.target.value),
20973
- children: chromosomes.map((c)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("option", {
20974
- value: c.name,
20975
- children: [
20976
- c.name,
20977
- c.length ? ` (${Math.round(c.length / 1e6)} Mb)` : ''
20978
- ]
20979
- }, c.name))
20980
- }),
20981
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
20982
- className: "gramene-interval-range",
20983
- children: [
20984
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
20985
- size: "sm",
20986
- type: "number",
20987
- min: "1",
20988
- placeholder: "start",
20989
- value: start,
20990
- onChange: (ev)=>setStart(ev.target.value)
20991
- }),
20992
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
20993
- className: "gramene-interval-dash",
20994
- children: "\u2013"
20995
- }),
20996
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
20997
- size: "sm",
20998
- type: "number",
20999
- min: "1",
21000
- placeholder: "end",
21001
- value: end,
21002
- onChange: (ev)=>setEnd(ev.target.value),
21003
- onKeyDown: (ev)=>{
21004
- if (ev.key === 'Enter') submit();
21005
- }
21006
- })
21007
- ]
21071
+ type: "number",
21072
+ min: "1",
21073
+ placeholder: "start",
21074
+ value: start,
21075
+ onChange: (ev)=>setStart(ev.target.value)
21008
21076
  }),
21009
- error && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
21010
- className: "gramene-interval-error",
21011
- children: error
21077
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
21078
+ className: "gramene-interval-dash",
21079
+ children: "\u2013"
21012
21080
  }),
21081
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
21082
+ size: "sm",
21083
+ type: "number",
21084
+ min: "1",
21085
+ placeholder: "end",
21086
+ value: end,
21087
+ onChange: (ev)=>setEnd(ev.target.value),
21088
+ onKeyDown: (ev)=>{
21089
+ if (ev.key === 'Enter') submit();
21090
+ }
21091
+ })
21092
+ ]
21093
+ }),
21094
+ error && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
21095
+ className: "gramene-interval-error",
21096
+ children: error
21097
+ }),
21098
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21099
+ className: "gramene-interval-actions",
21100
+ children: [
21013
21101
  /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
21014
21102
  size: "sm",
21015
21103
  variant: "primary",
21016
21104
  className: "gramene-interval-submit",
21017
21105
  disabled: !ready,
21018
21106
  onClick: submit,
21019
- children: "Add interval"
21107
+ children: submitLabel || 'Add interval'
21108
+ }),
21109
+ onCancel && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
21110
+ size: "sm",
21111
+ variant: "link",
21112
+ className: "gramene-interval-cancel",
21113
+ onClick: onCancel,
21114
+ children: "cancel"
21020
21115
  })
21021
21116
  ]
21022
21117
  })
21023
21118
  ]
21024
21119
  });
21025
21120
  };
21121
+ /** The collapsible "Add genomic interval" entry at the foot of the Filters panel. */ const $909272efb41be16f$var$GenomicIntervalForm = ({ grameneMaps: grameneMaps, targetTaxonId: targetTaxonId, onAdd: onAdd })=>{
21122
+ const [open, setOpen] = (0, $gXNCa$react.useState)(false);
21123
+ return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21124
+ className: "gramene-interval",
21125
+ children: [
21126
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21127
+ className: "gramene-interval-toggle",
21128
+ onClick: ()=>setOpen(!open),
21129
+ children: [
21130
+ open ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsChevronDown), {}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsChevronRight), {}),
21131
+ " Add genomic interval"
21132
+ ]
21133
+ }),
21134
+ open && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($909272efb41be16f$export$4fa3dba3103266ea, {
21135
+ grameneMaps: grameneMaps,
21136
+ targetTaxonId: targetTaxonId,
21137
+ onSubmit: onAdd
21138
+ })
21139
+ ]
21140
+ });
21141
+ };
21026
21142
  var $909272efb41be16f$export$2e2bcd8739ae039 = $909272efb41be16f$var$GenomicIntervalForm;
21027
21143
 
21028
21144
 
@@ -21031,6 +21147,146 @@ var $909272efb41be16f$export$2e2bcd8739ae039 = $909272efb41be16f$var$GenomicInte
21031
21147
 
21032
21148
 
21033
21149
 
21150
+ /**
21151
+ * Edit one filter leaf in place, with a form shaped to what that leaf actually is:
21152
+ * a genomic interval gets the genome/chromosome/start/end fields, a `[lo TO hi]`
21153
+ * range gets two number boxes, anything else gets its raw value.
21154
+ *
21155
+ * Groups are not editable — their only parameter is the operator, which the menu
21156
+ * already exposes as "convert to".
21157
+ *
21158
+ * The plain-value case is honest but limited: many leaves carry an opaque id (a
21159
+ * pathway number, an ontology term, a saved-search hash) whose replacement a user
21160
+ * has no way to know. It is offered because a typo in a gene name or a term is
21161
+ * worth fixing without deleting and starting over.
21162
+ */ const $91efc9c46e3775e3$var$FilterEditor = ({ node: node, grameneMaps: grameneMaps, targetTaxonId: targetTaxonId, onSave: onSave, onCancel: onCancel })=>{
21163
+ const kind = (0, $af4441dd29af05df$export$a7f4fa59930e5088)(node);
21164
+ const range = kind === 'range' ? (0, $af4441dd29af05df$export$9ee0ceaa589fc567)(node.fq_value) : null;
21165
+ const [lo, setLo] = (0, $gXNCa$react.useState)(range ? range.lo : '');
21166
+ const [hi, setHi] = (0, $gXNCa$react.useState)(range ? range.hi : '');
21167
+ const [value, setValue] = (0, $gXNCa$react.useState)(kind === 'value' ? String(node.fq_value ?? '') : '');
21168
+ if (!kind) return null;
21169
+ if (kind === 'interval') {
21170
+ const initial = (0, $af4441dd29af05df$export$67920d3e3cb3f136)(node.fq_value);
21171
+ return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
21172
+ className: "gramene-filter-editor",
21173
+ children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $909272efb41be16f$export$4fa3dba3103266ea), {
21174
+ grameneMaps: grameneMaps,
21175
+ targetTaxonId: targetTaxonId,
21176
+ initial: initial,
21177
+ submitLabel: "Save",
21178
+ onCancel: onCancel,
21179
+ onSubmit: (iv)=>onSave((0, $af4441dd29af05df$export$634f7214da1b780a)(iv), (0, $af4441dd29af05df$export$535c09441f37845d)(iv))
21180
+ })
21181
+ });
21182
+ }
21183
+ if (kind === 'range') {
21184
+ const ok = lo !== '' && hi !== '';
21185
+ const save = ()=>{
21186
+ if (ok) onSave((0, $af4441dd29af05df$export$cd9b2ffd001c6d65)(lo, hi), `${lo}-${hi}`);
21187
+ };
21188
+ return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21189
+ className: "gramene-filter-editor",
21190
+ children: [
21191
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21192
+ className: "gramene-interval-range",
21193
+ children: [
21194
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
21195
+ size: "sm",
21196
+ value: lo,
21197
+ onChange: (e)=>setLo(e.target.value),
21198
+ placeholder: "from"
21199
+ }),
21200
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
21201
+ className: "gramene-interval-dash",
21202
+ children: "\u2013"
21203
+ }),
21204
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
21205
+ size: "sm",
21206
+ value: hi,
21207
+ onChange: (e)=>setHi(e.target.value),
21208
+ placeholder: "to",
21209
+ onKeyDown: (e)=>{
21210
+ if (e.key === 'Enter') save();
21211
+ }
21212
+ })
21213
+ ]
21214
+ }),
21215
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21216
+ className: "gramene-interval-actions",
21217
+ children: [
21218
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
21219
+ size: "sm",
21220
+ variant: "primary",
21221
+ className: "gramene-interval-submit",
21222
+ disabled: !ok,
21223
+ onClick: save,
21224
+ children: "Save"
21225
+ }),
21226
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
21227
+ size: "sm",
21228
+ variant: "link",
21229
+ className: "gramene-interval-cancel",
21230
+ onClick: onCancel,
21231
+ children: "cancel"
21232
+ })
21233
+ ]
21234
+ })
21235
+ ]
21236
+ });
21237
+ }
21238
+ const ok = String(value).trim() !== '';
21239
+ // Keep the node's own label in step with its value, unless the label was
21240
+ // something other than the value to begin with (a suggestion's display name),
21241
+ // in which case leave it alone rather than overwriting it with a raw id.
21242
+ const save = ()=>{
21243
+ if (!ok) return;
21244
+ const v = String(value).trim();
21245
+ const nameFollowedValue = String(node.name ?? '') === String(node.fq_value ?? '');
21246
+ onSave(v, nameFollowedValue ? v : undefined);
21247
+ };
21248
+ return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21249
+ className: "gramene-filter-editor",
21250
+ children: [
21251
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
21252
+ size: "sm",
21253
+ value: value,
21254
+ onChange: (e)=>setValue(e.target.value),
21255
+ onKeyDown: (e)=>{
21256
+ if (e.key === 'Enter') save();
21257
+ }
21258
+ }),
21259
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21260
+ className: "gramene-interval-actions",
21261
+ children: [
21262
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
21263
+ size: "sm",
21264
+ variant: "primary",
21265
+ className: "gramene-interval-submit",
21266
+ disabled: !ok,
21267
+ onClick: save,
21268
+ children: "Save"
21269
+ }),
21270
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
21271
+ size: "sm",
21272
+ variant: "link",
21273
+ className: "gramene-interval-cancel",
21274
+ onClick: onCancel,
21275
+ children: "cancel"
21276
+ })
21277
+ ]
21278
+ })
21279
+ ]
21280
+ });
21281
+ };
21282
+ var $91efc9c46e3775e3$export$2e2bcd8739ae039 = $91efc9c46e3775e3$var$FilterEditor;
21283
+
21284
+
21285
+
21286
+
21287
+
21288
+
21289
+
21034
21290
 
21035
21291
 
21036
21292
  // "Save this view" UI — sits inside the Auth sidebar panel.
@@ -21475,6 +21731,7 @@ function $693dd8c7a5607c3a$var$logAction(action) {
21475
21731
  });
21476
21732
  }
21477
21733
  const $693dd8c7a5607c3a$var$Filter = ({ node: node, moveCopyMode: moveCopyMode, showMarked: showMarked, actions: actions })=>{
21734
+ const [editing, setEditing] = (0, $gXNCa$react.useState)(false);
21478
21735
  let classes = 'gramene-filter gramene-filter';
21479
21736
  if (node.operation) classes = `${classes}-${(0, $af4441dd29af05df$export$c8b6102be216752a)(node.operation)}`;
21480
21737
  // An expansion decorates whatever node it sits on, leaf or group — same as negate.
@@ -21534,6 +21791,14 @@ const $693dd8c7a5607c3a$var$Filter = ({ node: node, moveCopyMode: moveCopyMode,
21534
21791
  ]
21535
21792
  }, key++));
21536
21793
  });
21794
+ if ((0, $af4441dd29af05df$export$a7f4fa59930e5088)(node)) menuItems.push(/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("li", {
21795
+ onClick: ()=>{
21796
+ $693dd8c7a5607c3a$var$logAction(`Edit filter`);
21797
+ actions.toggleMenu(node);
21798
+ setEditing(true);
21799
+ },
21800
+ children: "edit"
21801
+ }, key++));
21537
21802
  if (node.leftIdx > 0) {
21538
21803
  menuItems.push(/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("li", {
21539
21804
  onClick: ()=>{
@@ -21642,12 +21907,23 @@ const $693dd8c7a5607c3a$var$Filter = ({ node: node, moveCopyMode: moveCopyMode,
21642
21907
  })
21643
21908
  ]
21644
21909
  });
21910
+ const editor = editing ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $91efc9c46e3775e3$export$2e2bcd8739ae039), {
21911
+ node: node,
21912
+ grameneMaps: actions.grameneMaps,
21913
+ targetTaxonId: actions.targetTaxonId,
21914
+ onCancel: ()=>setEditing(false),
21915
+ onSave: (fq_value, name)=>{
21916
+ setEditing(false);
21917
+ actions.editNode(node, fq_value, name);
21918
+ }
21919
+ }) : null;
21645
21920
  return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
21646
21921
  className: classes,
21647
21922
  onClick: (e)=>$693dd8c7a5607c3a$var$handleClick(e, moveCopyMode, showMarked, node, actions),
21648
21923
  children: [
21649
21924
  content,
21650
21925
  menu,
21926
+ editor,
21651
21927
  children
21652
21928
  ]
21653
21929
  }, node.leftIdx);
@@ -21662,7 +21938,12 @@ const $693dd8c7a5607c3a$var$FiltersCmp = (props)=>{
21662
21938
  selectTarget: props.doMoveOrCopyGrameneFilter,
21663
21939
  markTargets: props.doMarkGrameneFilterTargets,
21664
21940
  unmarkTargets: props.doUnmarkGrameneFilterTargets,
21665
- toggleMenu: props.doToggleGrameneFilterMenu
21941
+ toggleMenu: props.doToggleGrameneFilterMenu,
21942
+ editNode: props.doEditGrameneFilter,
21943
+ // Carried alongside the actions so every nested Filter can reach them without
21944
+ // threading two more props through the recursion.
21945
+ grameneMaps: props.grameneMaps,
21946
+ targetTaxonId: props.targetTaxonId
21666
21947
  };
21667
21948
  const intervalForm = /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $909272efb41be16f$export$2e2bcd8739ae039), {
21668
21949
  grameneMaps: props.grameneMaps,
@@ -21710,7 +21991,7 @@ const $693dd8c7a5607c3a$var$FiltersCmp = (props)=>{
21710
21991
  })
21711
21992
  });
21712
21993
  };
21713
- const $693dd8c7a5607c3a$export$92a576f896a9bf72 = (0, $gXNCa$reduxbundlerreact.connect)('selectGrameneFilters', 'selectConfiguration', 'doNegateGrameneFilter', 'doDeleteGrameneFilter', 'doChangeGrameneFilterOperation', 'doExpandGrameneFilter', 'doRemoveGrameneExpansion', 'doMoveOrCopyGrameneFilter', 'doMarkGrameneFilterTargets', 'doUnmarkGrameneFilterTargets', 'doToggleGrameneFilterMenu', 'selectGrameneMaps', 'selectTargetTaxonId', 'doAddGrameneInterval', 'doClearGrameneFilters', $693dd8c7a5607c3a$var$FiltersCmp);
21994
+ const $693dd8c7a5607c3a$export$92a576f896a9bf72 = (0, $gXNCa$reduxbundlerreact.connect)('selectGrameneFilters', 'selectConfiguration', 'doNegateGrameneFilter', 'doDeleteGrameneFilter', 'doChangeGrameneFilterOperation', 'doExpandGrameneFilter', 'doRemoveGrameneExpansion', 'doMoveOrCopyGrameneFilter', 'doMarkGrameneFilterTargets', 'doUnmarkGrameneFilterTargets', 'doToggleGrameneFilterMenu', 'selectGrameneMaps', 'selectTargetTaxonId', 'doAddGrameneInterval', 'doEditGrameneFilter', 'doClearGrameneFilters', $693dd8c7a5607c3a$var$FiltersCmp);
21714
21995
  const $693dd8c7a5607c3a$var$ResultsCmp = (props)=>{
21715
21996
  let activeViews = props.grameneViews.options;
21716
21997
  return props.grameneFilters.rightIdx > 0 ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {