gramene-search 2.11.0 → 2.11.2

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
@@ -2426,10 +2426,12 @@ const $0f839422d0d8c772$var$grameneFieldCatalog = (0, $gXNCa$reduxbundler.create
2426
2426
  }
2427
2427
  });
2428
2428
  // Auto-fetch the field catalog only when a view that needs it (exprViz fields
2429
- // modal, exporter field tree) is enabled. Other views never read it.
2429
+ // modal, exporter field tree, attribute-table column picker) is enabled. Other
2430
+ // views never read it.
2430
2431
  const $0f839422d0d8c772$var$FIELD_CATALOG_VIEWS = [
2431
2432
  'exprViz',
2432
- 'export'
2433
+ 'export',
2434
+ 'attrTable'
2433
2435
  ];
2434
2436
  $0f839422d0d8c772$var$grameneFieldCatalog.reactGrameneFieldCatalog = (0, $gXNCa$reduxbundler.createSelector)('selectGrameneFieldCatalogShouldUpdate', 'selectGrameneViewsOn', (shouldUpdate, viewsOn)=>{
2435
2437
  if (!shouldUpdate) return;
@@ -4823,24 +4825,54 @@ const $4b8473139e48e77e$export$705aff68da3ddba0 = [
4823
4825
 
4824
4826
 
4825
4827
  // Backing store for the "Attribute table" view: the genes in the current search
4826
- // result set, fetched in pages and rendered as a gene × attribute table (basic
4827
- // identity columns + the expression-attribute heatmap + any extra attribute
4828
- // columns the user picks from the field catalog).
4828
+ // result set, fetched in pages and rendered as a gene × attribute table.
4829
4829
  //
4830
4830
  // Paging mirrors exprViz's doFetchExprVizData: a recursive fetchPage(offset)
4831
4831
  // with a request-id guard so a superseded fetch can never write stale rows.
4832
+ //
4833
+ // The offered columns are deliberately limited to two field-catalog groups —
4834
+ // Core identifiers and Expression attributes — and *all* of them are fetched up
4835
+ // front. That keeps the column picker a pure visibility control: toggling a
4836
+ // column is instant and never triggers a refetch.
4832
4837
  const $12feb7ca48271e17$var$PAGE_SIZE = 1000;
4833
4838
  const $12feb7ca48271e17$var$MAX_GENES = 5000;
4834
- // Identity/basic columns that are always fetched.
4835
- const $12feb7ca48271e17$var$BASE_FIELDS = [
4839
+ // The catalog groups whose fields the column picker offers.
4840
+ const $12feb7ca48271e17$export$53e90de8d659db95 = [
4841
+ 'core',
4842
+ 'exprattrs'
4843
+ ];
4844
+ // The 'core' group's fields (bundles/../fieldCatalog.overlay.json).
4845
+ const $12feb7ca48271e17$export$9d77d82cc773e55e = [
4836
4846
  'id',
4837
4847
  'name',
4848
+ 'alt_id',
4849
+ 'synonyms',
4850
+ 'description',
4851
+ 'summary',
4852
+ 'biotype',
4838
4853
  'system_name',
4839
4854
  'taxon_id',
4855
+ 'db_type'
4856
+ ];
4857
+ // Always fetched, so visibility toggles never need the network. taxon_id is
4858
+ // needed by the genome filter even when its column is hidden.
4859
+ const $12feb7ca48271e17$var$FETCH_FIELDS = [
4860
+ ...new Set([
4861
+ ...$12feb7ca48271e17$export$9d77d82cc773e55e,
4862
+ ...(0, $4b8473139e48e77e$export$705aff68da3ddba0)
4863
+ ])
4864
+ ];
4865
+ const $12feb7ca48271e17$export$e4bc2ac09afbc604 = [
4866
+ 'id',
4867
+ 'name',
4868
+ 'system_name',
4840
4869
  'biotype',
4841
- 'region',
4842
- 'start',
4843
- 'end'
4870
+ 'expr_class__attr_ss',
4871
+ 'expr_organ_level__attr_ss',
4872
+ 'expr_tau__attr_f',
4873
+ 'expr_max_tpm__attr_f',
4874
+ 'expr_activated_by__attr_ss',
4875
+ 'expr_repressed_by__attr_ss'
4844
4876
  ];
4845
4877
  let $12feb7ca48271e17$var$fetchPendingId = 0;
4846
4878
  // Only send fq=taxon_id:(...) when the user has actually subset the genomes —
@@ -4857,8 +4889,10 @@ function $12feb7ca48271e17$var$genomeSubset(g, m) {
4857
4889
  key: subset ? sorted.join(',') : ''
4858
4890
  };
4859
4891
  }
4860
- function $12feb7ca48271e17$var$computeSignature(q, g, m, selectedFields) {
4861
- return `${q}|${$12feb7ca48271e17$var$genomeSubset(g, m).key}|${(selectedFields || []).slice().sort().join(',')}`;
4892
+ // Visible columns are deliberately NOT part of the signature — they don't
4893
+ // affect what we fetch.
4894
+ function $12feb7ca48271e17$var$computeSignature(q, g, m) {
4895
+ return `${q}|${$12feb7ca48271e17$var$genomeSubset(g, m).key}`;
4862
4896
  }
4863
4897
  const $12feb7ca48271e17$var$attrTable = {
4864
4898
  name: 'attrTable',
@@ -4871,7 +4905,7 @@ const $12feb7ca48271e17$var$attrTable = {
4871
4905
  signature: null,
4872
4906
  error: null,
4873
4907
  requestId: 0,
4874
- selectedFields: [] // extra attribute columns chosen from the field catalog
4908
+ visibleFields: $12feb7ca48271e17$export$e4bc2ac09afbc604.slice()
4875
4909
  };
4876
4910
  return (state = initialState, { type: type, payload: payload })=>{
4877
4911
  switch(type){
@@ -4908,34 +4942,39 @@ const $12feb7ca48271e17$var$attrTable = {
4908
4942
  };
4909
4943
  case 'ATTRTABLE_FIELD_TOGGLED':
4910
4944
  {
4911
- const set = new Set(state.selectedFields);
4945
+ const set = new Set(state.visibleFields);
4912
4946
  if (set.has(payload)) set.delete(payload);
4913
4947
  else set.add(payload);
4914
4948
  return {
4915
4949
  ...state,
4916
- selectedFields: [
4950
+ visibleFields: [
4917
4951
  ...set
4918
4952
  ]
4919
4953
  };
4920
4954
  }
4921
4955
  case 'ATTRTABLE_FIELDS_BULK_SET':
4922
4956
  {
4923
- const set = new Set(state.selectedFields);
4957
+ const set = new Set(state.visibleFields);
4924
4958
  (payload.names || []).forEach((n)=>{
4925
4959
  if (payload.selected) set.add(n);
4926
4960
  else set.delete(n);
4927
4961
  });
4928
4962
  return {
4929
4963
  ...state,
4930
- selectedFields: [
4964
+ visibleFields: [
4931
4965
  ...set
4932
4966
  ]
4933
4967
  };
4934
4968
  }
4969
+ case 'ATTRTABLE_FIELDS_RESET':
4970
+ return {
4971
+ ...state,
4972
+ visibleFields: $12feb7ca48271e17$export$e4bc2ac09afbc604.slice()
4973
+ };
4935
4974
  case 'GRAMENE_SEARCH_CLEARED':
4936
4975
  return {
4937
4976
  ...initialState,
4938
- selectedFields: state.selectedFields
4977
+ visibleFields: state.visibleFields
4939
4978
  };
4940
4979
  default:
4941
4980
  return state;
@@ -4943,10 +4982,9 @@ const $12feb7ca48271e17$var$attrTable = {
4943
4982
  };
4944
4983
  },
4945
4984
  doFetchAttrTable: ()=>({ dispatch: dispatch, store: store })=>{
4946
- const { selectedFields: selectedFields } = store.selectAttrTable();
4947
4985
  const q = store.selectGrameneFiltersQueryString();
4948
4986
  const { fq: fq } = $12feb7ca48271e17$var$genomeSubset(store.selectGrameneGenomes(), store.selectGrameneMaps());
4949
- const signature = $12feb7ca48271e17$var$computeSignature(q, store.selectGrameneGenomes(), store.selectGrameneMaps(), selectedFields);
4987
+ const signature = $12feb7ca48271e17$var$computeSignature(q, store.selectGrameneGenomes(), store.selectGrameneMaps());
4950
4988
  const requestId = ++$12feb7ca48271e17$var$fetchPendingId;
4951
4989
  dispatch({
4952
4990
  type: 'ATTRTABLE_FETCH_STARTED',
@@ -4957,13 +4995,7 @@ const $12feb7ca48271e17$var$attrTable = {
4957
4995
  });
4958
4996
  const api = store.selectGrameneAPI();
4959
4997
  // Explicit fl — never fl=*, which would drag in every per-sample __expr column.
4960
- const fl = [
4961
- ...new Set([
4962
- ...$12feb7ca48271e17$var$BASE_FIELDS,
4963
- ...(0, $4b8473139e48e77e$export$705aff68da3ddba0),
4964
- ...selectedFields
4965
- ])
4966
- ].join(',');
4998
+ const fl = $12feb7ca48271e17$var$FETCH_FIELDS.join(',');
4967
4999
  const fetchPage = (offset)=>{
4968
5000
  if (requestId !== $12feb7ca48271e17$var$fetchPendingId) return; // superseded
4969
5001
  const rows = Math.min($12feb7ca48271e17$var$PAGE_SIZE, $12feb7ca48271e17$var$MAX_GENES - offset);
@@ -5006,16 +5038,17 @@ const $12feb7ca48271e17$var$attrTable = {
5006
5038
  selected: selected
5007
5039
  }
5008
5040
  }),
5041
+ doResetAttrTableFields: ()=>({ dispatch: dispatch })=>dispatch({
5042
+ type: 'ATTRTABLE_FIELDS_RESET'
5043
+ }),
5009
5044
  selectAttrTable: (state)=>state.attrTable,
5010
- selectAttrTableSelectedFields: (state)=>state.attrTable.selectedFields,
5011
- // Fetch only while the view is actually on, and only when the query context or
5012
- // the chosen columns have changed. A toggle made mid-load is picked up when the
5013
- // in-flight fetch settles (the signature will no longer match).
5045
+ selectAttrTableVisibleFields: (state)=>state.attrTable.visibleFields,
5046
+ // Fetch only while the view is on, and only when the query context changes.
5014
5047
  reactAttrTableFetch: (0, $gXNCa$reduxbundler.createSelector)('selectAttrTable', 'selectGrameneFiltersStatus', 'selectGrameneViewsOn', 'selectGrameneFiltersQueryString', 'selectGrameneGenomes', 'selectGrameneMaps', (at, filtersStatus, viewsOn, q, g, m)=>{
5015
5048
  if (!at || filtersStatus === 'init') return;
5016
5049
  if (!viewsOn || !viewsOn.has('attrTable')) return;
5017
5050
  if (at.status === 'loading') return;
5018
- const sig = $12feb7ca48271e17$var$computeSignature(q, g, m, at.selectedFields);
5051
+ const sig = $12feb7ca48271e17$var$computeSignature(q, g, m);
5019
5052
  if (at.signature === sig && (at.status === 'ready' || at.status === 'error')) return;
5020
5053
  return {
5021
5054
  actionCreator: 'doFetchAttrTable'
@@ -8125,7 +8158,11 @@ const $cd8bc494277e92a4$export$44e8c3b1eee47e9e = {
8125
8158
  // Stay hidden until its async data lands; tbrowse's auto-enable effect flips
8126
8159
  // it on once isAvailable() turns true (same lifecycle as neighborhood/genome).
8127
8160
  isAvailable: (data)=>Boolean(data.hostData && data.hostData.exprAttrs && data.hostData.exprAttrs.organs && data.hostData.exprAttrs.organs.length),
8128
- defaultVisible: false
8161
+ defaultVisible: false,
8162
+ // Join tbrowse's built-in 'detail' group (MSA / Neighborhood / Gene
8163
+ // structure): only one of these wide per-leaf tracks shows at a time
8164
+ // unless the user decouples the group with the `multi` toggle.
8165
+ exclusiveGroup: 'detail'
8129
8166
  };
8130
8167
 
8131
8168
 
@@ -16910,42 +16947,68 @@ var $1fd2507769d5bd00$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.conn
16910
16947
 
16911
16948
 
16912
16949
  const { MAX_GENES: $8f8e530286a37ce9$var$MAX_GENES } = (0, $12feb7ca48271e17$export$d00a9fd01497b827);
16950
+ // expr_organ_level expands into one column per organ (the heatmap block); every
16951
+ // other offered field maps to exactly one column.
16952
+ const $8f8e530286a37ce9$var$ORGAN_FIELD = 'expr_organ_level__attr_ss';
16913
16953
  const $8f8e530286a37ce9$var$DEFAULT_COL_DEF = {
16914
16954
  resizable: true,
16915
16955
  sortable: true,
16916
16956
  filter: false,
16917
16957
  suppressHeaderMenuButton: true
16918
16958
  };
16919
- // ↑activated / ↓repressed condition chips, matching the tbrowse Expression zone.
16920
- const $8f8e530286a37ce9$var$StressCell = ({ value: value })=>{
16921
- const { up: up = [], down: down = [] } = value || {};
16922
- if (!up.length && !down.length) return null;
16923
- const chip = (c, dir, key)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
16924
- style: {
16925
- fontSize: 10,
16926
- lineHeight: '16px',
16927
- padding: '0 4px',
16928
- borderRadius: 2,
16929
- marginRight: 3,
16930
- whiteSpace: 'nowrap',
16931
- background: (0, $4b8473139e48e77e$export$c6dbe9be8c8b382e)[dir].bg,
16932
- color: (0, $4b8473139e48e77e$export$c6dbe9be8c8b382e)[dir].fg
16933
- },
16934
- children: (dir === 'up' ? "\u2191" : "\u2193") + c
16935
- }, key);
16936
- return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
16937
- children: [
16938
- up.map((c, i)=>chip(c, 'up', `u${i}`)),
16939
- down.map((c, i)=>chip(c, 'down', `d${i}`))
16940
- ]
16941
- });
16959
+ const $8f8e530286a37ce9$var$WIDTHS = {
16960
+ id: 190,
16961
+ name: 130,
16962
+ system_name: 150,
16963
+ biotype: 120,
16964
+ taxon_id: 100,
16965
+ db_type: 100,
16966
+ alt_id: 180,
16967
+ synonyms: 160,
16968
+ description: 260,
16969
+ summary: 260,
16970
+ expr_class__attr_ss: 150,
16971
+ expr_tau__attr_f: 80,
16972
+ expr_max_tpm__attr_f: 100,
16973
+ expr_n_organs_detected__attr_i: 90,
16974
+ expr_activated_by__attr_ss: 190,
16975
+ expr_repressed_by__attr_ss: 190,
16976
+ expr_specific_to__attr_ss: 150,
16977
+ expr_enhanced_in__attr_ss: 150,
16978
+ expr_high_in__attr_ss: 150
16942
16979
  };
16980
+ const $8f8e530286a37ce9$var$joinValues = (p)=>Array.isArray(p.value) ? p.value.map((v)=>String(v).replace(/_/g, ' ')).join(', ') : p.value ?? '';
16981
+ // ↑activated / ↓repressed condition chips.
16982
+ const $8f8e530286a37ce9$var$chipRenderer = (dir)=>({ value: value })=>{
16983
+ const list = Array.isArray(value) ? value : value ? [
16984
+ value
16985
+ ] : [];
16986
+ if (!list.length) return null;
16987
+ return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
16988
+ children: list.map((c, i)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
16989
+ style: {
16990
+ fontSize: 10,
16991
+ lineHeight: '16px',
16992
+ padding: '0 4px',
16993
+ borderRadius: 2,
16994
+ marginRight: 3,
16995
+ whiteSpace: 'nowrap',
16996
+ background: (0, $4b8473139e48e77e$export$c6dbe9be8c8b382e)[dir].bg,
16997
+ color: (0, $4b8473139e48e77e$export$c6dbe9be8c8b382e)[dir].fg
16998
+ },
16999
+ children: (dir === 'up' ? "\u2191" : "\u2193") + c
17000
+ }, i))
17001
+ });
17002
+ };
16943
17003
  const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
16944
- const { attrTable: attrTable, fieldCatalog: fieldCatalog, fieldCatalogByName: fieldCatalogByName, doToggleAttrTableField: doToggleAttrTableField, doBulkSetAttrTableFields: doBulkSetAttrTableFields } = props;
17004
+ const { attrTable: attrTable, fieldCatalog: fieldCatalog, fieldCatalogByName: fieldCatalogByName, doToggleAttrTableField: doToggleAttrTableField, doBulkSetAttrTableFields: doBulkSetAttrTableFields, doResetAttrTableFields: doResetAttrTableFields } = props;
16945
17005
  const [showColumns, setShowColumns] = (0, $gXNCa$react.useState)(false);
16946
17006
  const [fieldQuery, setFieldQuery] = (0, $gXNCa$react.useState)('');
16947
- const { docs: docs, total: total, truncated: truncated, status: status, error: error, selectedFields: selectedFields } = attrTable;
16948
- // Row objects + the organ union and TPM range needed to build the heatmap.
17007
+ const { docs: docs, total: total, truncated: truncated, status: status, error: error, visibleFields: visibleFields } = attrTable;
17008
+ const visibleSet = (0, $gXNCa$react.useMemo)(()=>new Set(visibleFields), [
17009
+ visibleFields
17010
+ ]);
17011
+ // Rows + the organ union and TPM range the heatmap needs.
16949
17012
  const { rows: rows, organs: organs, tpmRange: tpmRange } = (0, $gXNCa$react.useMemo)(()=>{
16950
17013
  const organSet = new Set();
16951
17014
  let tpmMin = Infinity;
@@ -16957,27 +17020,12 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
16957
17020
  if (a.maxTpm < tpmMin) tpmMin = a.maxTpm;
16958
17021
  if (a.maxTpm > tpmMax) tpmMax = a.maxTpm;
16959
17022
  }
16960
- const row = {
16961
- id: d.id,
16962
- name: d.name,
16963
- system_name: d.system_name,
16964
- biotype: d.biotype,
16965
- location: d.region ? `${d.region}:${d.start}-${d.end}` : '',
16966
- _cls: (0, $4b8473139e48e77e$export$1f04174626f2ac64)(a.cls) || '',
16967
- _tau: a.tau,
16968
- _maxTpm: a.maxTpm,
16969
- _stress: {
16970
- up: a.activatedBy,
16971
- down: a.repressedBy
16972
- },
17023
+ return {
17024
+ ...d,
16973
17025
  _organ: a.organLevels,
16974
17026
  _specific: a.specificTo,
16975
17027
  _enhanced: a.enhancedIn
16976
17028
  };
16977
- (selectedFields || []).forEach((f)=>{
16978
- row[f] = d[f];
16979
- });
16980
- return row;
16981
17029
  });
16982
17030
  return {
16983
17031
  rows: out,
@@ -16988,130 +17036,126 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
16988
17036
  }
16989
17037
  };
16990
17038
  }, [
16991
- docs,
16992
- selectedFields
17039
+ docs
17040
+ ]);
17041
+ // Only the Core identifiers + Expression attributes groups are offered, in
17042
+ // catalog order. Falls back to the built-in lists until the catalog loads.
17043
+ const orderedFields = (0, $gXNCa$react.useMemo)(()=>{
17044
+ const groups = fieldCatalog && fieldCatalog.groups || [];
17045
+ const out = [];
17046
+ (0, $12feb7ca48271e17$export$53e90de8d659db95).forEach((gid)=>{
17047
+ const g = groups.find((x)=>x.id === gid);
17048
+ if (g && g.fields) out.push(...g.fields);
17049
+ });
17050
+ return out.length ? out : [
17051
+ ...(0, $12feb7ca48271e17$export$9d77d82cc773e55e),
17052
+ ...(0, $4b8473139e48e77e$export$705aff68da3ddba0)
17053
+ ];
17054
+ }, [
17055
+ fieldCatalog
17056
+ ]);
17057
+ const pickerCatalog = (0, $gXNCa$react.useMemo)(()=>{
17058
+ if (!fieldCatalog || !fieldCatalog.groups) return null;
17059
+ return {
17060
+ ...fieldCatalog,
17061
+ groups: fieldCatalog.groups.filter((g)=>(0, $12feb7ca48271e17$export$53e90de8d659db95).includes(g.id))
17062
+ };
17063
+ }, [
17064
+ fieldCatalog
16993
17065
  ]);
16994
17066
  const columnDefs = (0, $gXNCa$react.useMemo)(()=>{
16995
- const cols = [
16996
- {
16997
- colId: 'id',
16998
- field: 'id',
16999
- headerName: 'Gene ID',
17000
- pinned: 'left',
17001
- width: 190
17002
- },
17003
- {
17004
- colId: 'name',
17005
- field: 'name',
17006
- headerName: 'Name',
17007
- pinned: 'left',
17008
- width: 130
17009
- },
17010
- {
17011
- colId: 'system_name',
17012
- field: 'system_name',
17013
- headerName: 'Species',
17014
- width: 150
17015
- },
17016
- {
17017
- colId: 'biotype',
17018
- field: 'biotype',
17019
- headerName: 'Biotype',
17020
- width: 120
17021
- },
17022
- {
17023
- colId: 'location',
17024
- field: 'location',
17025
- headerName: 'Location',
17026
- width: 150
17027
- },
17028
- {
17029
- colId: '_cls',
17030
- field: '_cls',
17031
- headerName: 'Expression class',
17032
- width: 150
17033
- },
17034
- {
17035
- colId: '_tau',
17036
- field: '_tau',
17037
- headerName: 'Tau',
17038
- width: 80,
17039
- type: 'numericColumn',
17040
- valueFormatter: (p)=>Number.isFinite(p.value) ? p.value.toFixed(3) : ''
17041
- },
17042
- {
17043
- colId: '_maxTpm',
17044
- field: '_maxTpm',
17045
- headerName: 'Max TPM',
17046
- width: 100,
17047
- type: 'numericColumn',
17048
- valueFormatter: (p)=>(0, $4b8473139e48e77e$export$8eaa32ef86afdd9c)(p.value),
17049
- cellStyle: (p)=>({
17050
- background: (0, $4b8473139e48e77e$export$4b443417c5259620)(p.value, tpmRange)
17051
- })
17052
- },
17053
- {
17054
- colId: '_stress',
17055
- field: '_stress',
17056
- headerName: 'Stress',
17057
- width: 220,
17058
- sortable: false,
17059
- // The renderer draws the chips; the formatter just keeps ag-grid from
17060
- // warning about an object-valued cell with no formatter.
17061
- valueFormatter: ()=>'',
17062
- cellRenderer: $8f8e530286a37ce9$var$StressCell
17067
+ const labelOf = (f)=>fieldCatalogByName && fieldCatalogByName[f] && fieldCatalogByName[f].label || f;
17068
+ const cols = [];
17069
+ orderedFields.forEach((f)=>{
17070
+ if (!visibleSet.has(f)) return;
17071
+ const headerName = labelOf(f);
17072
+ const width = $8f8e530286a37ce9$var$WIDTHS[f] || 150;
17073
+ if (f === $8f8e530286a37ce9$var$ORGAN_FIELD) {
17074
+ organs.forEach((o)=>cols.push({
17075
+ // No ':' in colId — ag-grid uses colId in internal CSS selectors, where
17076
+ // a colon is a metacharacter and silently breaks the column.
17077
+ colId: `organ_${o}`,
17078
+ headerName: (0, $4b8473139e48e77e$export$f8541c2790c3baeb)(o),
17079
+ headerTooltip: (0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o),
17080
+ width: 46,
17081
+ valueGetter: (p)=>p.data && p.data._organ[o] || '',
17082
+ valueFormatter: ()=>'',
17083
+ tooltipValueGetter: (p)=>{
17084
+ const lvl = p.data && p.data._organ[o];
17085
+ if (!lvl) return `${(0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o)}: not assayed`;
17086
+ const sp = p.data._specific.has(o) ? " \xb7 specific" : p.data._enhanced.has(o) ? " \xb7 enhanced" : '';
17087
+ return `${(0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o)}: ${(0, $4b8473139e48e77e$export$433053e2c141d410)[lvl] || lvl}${sp}`;
17088
+ },
17089
+ comparator: (a, b)=>((0, $4b8473139e48e77e$export$a61929d6d484c6d4)[a] ?? -1) - ((0, $4b8473139e48e77e$export$a61929d6d484c6d4)[b] ?? -1),
17090
+ cellStyle: (p)=>{
17091
+ const style = {
17092
+ background: p.value ? (0, $4b8473139e48e77e$export$b352e11f7a2ac9c5)[p.value] || 'transparent' : 'transparent'
17093
+ };
17094
+ if (p.data && p.data._specific.has(o)) style.boxShadow = `inset 0 0 0 2px ${0, $4b8473139e48e77e$export$39a35029fe99c21}`;
17095
+ else if (p.data && p.data._enhanced.has(o)) style.boxShadow = `inset 0 0 0 1px ${0, $4b8473139e48e77e$export$39a35029fe99c21}`;
17096
+ return style;
17097
+ }
17098
+ }));
17099
+ return;
17100
+ }
17101
+ if (f === 'expr_max_tpm__attr_f') {
17102
+ cols.push({
17103
+ colId: f,
17104
+ field: f,
17105
+ headerName: headerName,
17106
+ width: width,
17107
+ type: 'numericColumn',
17108
+ valueFormatter: (p)=>(0, $4b8473139e48e77e$export$8eaa32ef86afdd9c)(p.value),
17109
+ cellStyle: (p)=>({
17110
+ background: (0, $4b8473139e48e77e$export$4b443417c5259620)(p.value, tpmRange)
17111
+ })
17112
+ });
17113
+ return;
17114
+ }
17115
+ if (f === 'expr_tau__attr_f') {
17116
+ cols.push({
17117
+ colId: f,
17118
+ field: f,
17119
+ headerName: headerName,
17120
+ width: width,
17121
+ type: 'numericColumn',
17122
+ valueFormatter: (p)=>Number.isFinite(p.value) ? p.value.toFixed(3) : ''
17123
+ });
17124
+ return;
17125
+ }
17126
+ if (f === 'expr_activated_by__attr_ss' || f === 'expr_repressed_by__attr_ss') {
17127
+ cols.push({
17128
+ colId: f,
17129
+ field: f,
17130
+ headerName: headerName,
17131
+ width: width,
17132
+ sortable: false,
17133
+ valueFormatter: ()=>'',
17134
+ cellRenderer: $8f8e530286a37ce9$var$chipRenderer(f === 'expr_activated_by__attr_ss' ? 'up' : 'down')
17135
+ });
17136
+ return;
17063
17137
  }
17064
- ];
17065
- // Per-organ heatmap: one narrow, colour-only column per organ.
17066
- organs.forEach((o)=>{
17067
- cols.push({
17068
- // No ':' in colId — ag-grid uses colId in internal CSS selectors, where a
17069
- // colon is a metacharacter and silently breaks rendering of the column.
17070
- colId: `organ_${o}`,
17071
- headerName: (0, $4b8473139e48e77e$export$f8541c2790c3baeb)(o),
17072
- headerTooltip: (0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o),
17073
- width: 46,
17074
- valueGetter: (p)=>p.data && p.data._organ[o] || '',
17075
- valueFormatter: ()=>'',
17076
- tooltipValueGetter: (p)=>{
17077
- const lvl = p.data && p.data._organ[o];
17078
- if (!lvl) return `${(0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o)}: not assayed`;
17079
- const sp = p.data._specific.has(o) ? " \xb7 specific" : p.data._enhanced.has(o) ? " \xb7 enhanced" : '';
17080
- return `${(0, $4b8473139e48e77e$export$50c1f966fd3af83e)(o)}: ${(0, $4b8473139e48e77e$export$433053e2c141d410)[lvl] || lvl}${sp}`;
17081
- },
17082
- comparator: (a, b)=>((0, $4b8473139e48e77e$export$a61929d6d484c6d4)[a] ?? -1) - ((0, $4b8473139e48e77e$export$a61929d6d484c6d4)[b] ?? -1),
17083
- cellStyle: (p)=>{
17084
- const lvl = p.value;
17085
- const style = {
17086
- background: lvl ? (0, $4b8473139e48e77e$export$b352e11f7a2ac9c5)[lvl] || 'transparent' : 'transparent'
17087
- };
17088
- if (p.data && p.data._specific.has(o)) style.boxShadow = `inset 0 0 0 2px ${0, $4b8473139e48e77e$export$39a35029fe99c21}`;
17089
- else if (p.data && p.data._enhanced.has(o)) style.boxShadow = `inset 0 0 0 1px ${0, $4b8473139e48e77e$export$39a35029fe99c21}`;
17090
- return style;
17091
- }
17092
- });
17093
- });
17094
- // Extra attribute columns chosen from the field catalog.
17095
- (selectedFields || []).forEach((f)=>{
17096
- const meta = fieldCatalogByName && fieldCatalogByName[f] || {};
17097
17138
  cols.push({
17098
17139
  colId: f,
17099
17140
  field: f,
17100
- headerName: meta.label || f,
17141
+ headerName: headerName,
17101
17142
  headerTooltip: f,
17102
- width: 170,
17103
- valueFormatter: (p)=>Array.isArray(p.value) ? p.value.join(', ') : p.value ?? ''
17143
+ width: width,
17144
+ pinned: f === 'id' || f === 'name' ? 'left' : undefined,
17145
+ valueFormatter: $8f8e530286a37ce9$var$joinValues
17104
17146
  });
17105
17147
  });
17106
17148
  return cols;
17107
17149
  }, [
17150
+ orderedFields,
17151
+ visibleSet,
17108
17152
  organs,
17109
- selectedFields,
17110
- fieldCatalogByName,
17111
- tpmRange
17153
+ tpmRange,
17154
+ fieldCatalogByName
17112
17155
  ]);
17113
17156
  const shown = rows.length;
17114
17157
  const loading = status === 'loading';
17158
+ const organShown = visibleSet.has($8f8e530286a37ce9$var$ORGAN_FIELD);
17115
17159
  return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
17116
17160
  className: "attrtable-view",
17117
17161
  children: [
@@ -17142,7 +17186,7 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
17142
17186
  loading && " \u2014 loading\u2026"
17143
17187
  ]
17144
17188
  }),
17145
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
17189
+ organShown && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
17146
17190
  className: "attrtable-legend",
17147
17191
  title: "expression level",
17148
17192
  children: (0, $4b8473139e48e77e$export$4bb936dc8856d37a).map((lv)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
@@ -17157,8 +17201,9 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
17157
17201
  variant: showColumns ? 'primary' : 'outline-secondary',
17158
17202
  onClick: ()=>setShowColumns((v)=>!v),
17159
17203
  children: [
17160
- "Columns",
17161
- selectedFields.length ? ` (${selectedFields.length})` : ''
17204
+ "Columns (",
17205
+ columnDefs.length,
17206
+ ")"
17162
17207
  ]
17163
17208
  })
17164
17209
  ]
@@ -17185,19 +17230,33 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
17185
17230
  showColumns && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
17186
17231
  className: "attrtable-columns-panel",
17187
17232
  children: [
17188
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("input", {
17189
- type: "search",
17190
- className: "form-control form-control-sm attrtable-field-search",
17191
- placeholder: "Search fields\u2026",
17192
- value: fieldQuery,
17193
- onChange: (e)=>setFieldQuery(e.target.value)
17233
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
17234
+ className: "attrtable-columns-head",
17235
+ children: [
17236
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("input", {
17237
+ type: "search",
17238
+ className: "form-control form-control-sm attrtable-field-search",
17239
+ placeholder: "Search fields\u2026",
17240
+ value: fieldQuery,
17241
+ onChange: (e)=>setFieldQuery(e.target.value)
17242
+ }),
17243
+ /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
17244
+ size: "sm",
17245
+ variant: "link",
17246
+ onClick: doResetAttrTableFields,
17247
+ children: "Reset"
17248
+ })
17249
+ ]
17194
17250
  }),
17195
- /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $08e62a9eec4f328f$export$8af8343a8541cf1f), {
17196
- catalog: fieldCatalog,
17197
- selectedFields: selectedFields,
17251
+ pickerCatalog ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $08e62a9eec4f328f$export$8af8343a8541cf1f), {
17252
+ catalog: pickerCatalog,
17253
+ selectedFields: visibleFields,
17198
17254
  onToggle: doToggleAttrTableField,
17199
17255
  onBulkSet: doBulkSetAttrTableFields,
17200
17256
  query: fieldQuery
17257
+ }) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
17258
+ className: "exporter-panel-empty",
17259
+ children: "Loading field catalog\u2026"
17201
17260
  })
17202
17261
  ]
17203
17262
  }),
@@ -17206,6 +17265,11 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
17206
17265
  children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
17207
17266
  children: "No genes to show."
17208
17267
  })
17268
+ }) : columnDefs.length === 0 ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
17269
+ className: "attrtable-empty",
17270
+ children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
17271
+ children: "No columns selected \u2014 pick some under \u201CColumns\u201D."
17272
+ })
17209
17273
  }) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
17210
17274
  className: "ag-theme-quartz attrtable-aggrid",
17211
17275
  children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$aggridreact.AgGridReact), {
@@ -17223,7 +17287,7 @@ const $8f8e530286a37ce9$var$AttrTableViewCmp = (props)=>{
17223
17287
  ]
17224
17288
  });
17225
17289
  };
17226
- var $8f8e530286a37ce9$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectAttrTable', 'selectFieldCatalog', 'selectFieldCatalogByName', 'doToggleAttrTableField', 'doBulkSetAttrTableFields', $8f8e530286a37ce9$var$AttrTableViewCmp);
17290
+ var $8f8e530286a37ce9$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectAttrTable', 'selectFieldCatalog', 'selectFieldCatalogByName', 'doToggleAttrTableField', 'doBulkSetAttrTableFields', 'doResetAttrTableFields', $8f8e530286a37ce9$var$AttrTableViewCmp);
17227
17291
 
17228
17292
 
17229
17293