gramene-search 2.1.10 → 2.2.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/.claude/launch.json +11 -0
- package/.claude/settings.local.json +21 -1
- package/.env.example +6 -0
- package/.parcel-cache/13f2d5707e7af45c-RequestGraph +0 -0
- package/.parcel-cache/5ae0570a78c0dba3-AssetGraph +0 -0
- package/.parcel-cache/9ac092379278e465-BundleGraph +0 -0
- package/.parcel-cache/data.mdb +0 -0
- package/.parcel-cache/lock.mdb +0 -0
- package/.parcel-cache/snapshot-13f2d5707e7af45c.txt +2 -2
- package/dist/index.css +1 -4
- package/dist/index.css.map +1 -1
- package/dist/index.js +2308 -433
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/bundles/api.js +24 -42
- package/src/bundles/docs.js +2 -1
- package/src/bundles/exprViz.js +97 -1
- package/src/bundles/index.js +4 -1
- package/src/bundles/ontologyEnrichment.js +14 -1
- package/src/bundles/savedViews.js +335 -0
- package/src/bundles/uiViewState.js +174 -0
- package/src/bundles/viewSnapshot.js +313 -0
- package/src/bundles/views.js +24 -2
- package/src/components/Auth.js +23 -3
- package/src/components/SaveView.js +157 -0
- package/src/components/exprViz/ExprVizView.js +16 -11
- package/src/components/exprViz/ParallelCoordsPlot.js +15 -0
- package/src/components/results/GeneList.js +45 -49
- package/src/components/results/OntologyEnrichment.js +13 -6
- package/src/components/results/TaxDist.js +11 -0
- package/src/components/results/details/BAR.js +148 -0
- package/src/components/results/details/Expression.js +50 -14
- package/src/components/results/details/Homology.js +171 -39
- package/src/components/results/details/Pathways.js +4 -2
- package/src/components/results/details/Sequences.js +24 -8
- package/src/components/results/details/VEP.js +65 -19
- package/src/components/styles.css +1 -4
- package/src/demo.js +30 -13
- package/src/index.js +2 -1
- package/src/suppressDevWarnings.js +13 -0
- package/src/utils/bootView.js +38 -0
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@ require("ag-grid-community/styles/ag-grid.css");
|
|
|
7
7
|
require("ag-grid-community/styles/ag-theme-quartz.css");
|
|
8
8
|
var $gXNCa$reduxbundler = require("redux-bundler");
|
|
9
9
|
var $gXNCa$lodash = require("lodash");
|
|
10
|
-
var $gXNCa$
|
|
11
|
-
var $gXNCa$
|
|
10
|
+
var $gXNCa$gramenebinsclientsrcbins = require("gramene-bins-client/src/bins");
|
|
11
|
+
var $gXNCa$gramenetreesclientsrctaxonomy = require("gramene-trees-client/src/taxonomy");
|
|
12
12
|
var $gXNCa$gramenetaxonomywithgenomes = require("gramene-taxonomy-with-genomes");
|
|
13
13
|
var $gXNCa$reactga4 = require("react-ga4");
|
|
14
14
|
var $gXNCa$moneyclip = require("money-clip");
|
|
@@ -17,8 +17,8 @@ var $gXNCa$reactswitch = require("react-switch");
|
|
|
17
17
|
var $gXNCa$reacticonsio5 = require("react-icons/io5");
|
|
18
18
|
var $gXNCa$reacticonsbs = require("react-icons/bs");
|
|
19
19
|
var $gXNCa$reacticonsgr = require("react-icons/gr");
|
|
20
|
-
var $gXNCa$grameneefpbrowser = require("gramene-efp-browser");
|
|
21
20
|
var $gXNCa$gramenegenetreevis = require("gramene-genetree-vis");
|
|
21
|
+
var $gXNCa$gramenetreesclientsrcgenetree = require("gramene-trees-client/src/genetree");
|
|
22
22
|
var $gXNCa$tbrowse = require("tbrowse");
|
|
23
23
|
var $gXNCa$reactdom = require("react-dom");
|
|
24
24
|
var $gXNCa$lodashkeyBy = require("lodash/keyBy");
|
|
@@ -222,6 +222,7 @@ $parcel$export(module.exports, "Filters", () => $693dd8c7a5607c3a$export$92a576f
|
|
|
222
222
|
$parcel$export(module.exports, "Results", () => $693dd8c7a5607c3a$export$4a34de49b46a2bfa);
|
|
223
223
|
$parcel$export(module.exports, "Views", () => $693dd8c7a5607c3a$export$5cb791131c501f6a);
|
|
224
224
|
$parcel$export(module.exports, "Auth", () => $c5d403787de8b05f$export$2e2bcd8739ae039);
|
|
225
|
+
$parcel$export(module.exports, "bootViewFromUrl", () => $c69c61828e36a328$export$2e2bcd8739ae039);
|
|
225
226
|
|
|
226
227
|
|
|
227
228
|
|
|
@@ -399,24 +400,6 @@ $9d9aeaf9299e61a1$var$expressionSamples.reactExpressionSamples = (0, $gXNCa$redu
|
|
|
399
400
|
actionCreator: 'doFetchExpressionSamples'
|
|
400
401
|
};
|
|
401
402
|
});
|
|
402
|
-
const $9d9aeaf9299e61a1$var$curatedGenes = (0, $gXNCa$reduxbundler.createAsyncResourceBundle)({
|
|
403
|
-
name: 'curatedGenes',
|
|
404
|
-
actionBaseType: 'CURATED_GENES',
|
|
405
|
-
persist: true,
|
|
406
|
-
staleAfter: 86400000,
|
|
407
|
-
getPromise: ({ store: store })=>{
|
|
408
|
-
return fetch(`https://devdata.gramene.org/curation/curations?rows=0&minFlagged=0&since=12-12-2029`).then((res)=>res.json()).then((curation)=>(0, ($parcel$interopDefault($gXNCa$lodash))).keyBy(curation.genes, 'gene_id'));
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
// Curated annotations are consumed by GeneList rows and Homology details,
|
|
412
|
-
// both inside the gene-list view.
|
|
413
|
-
$9d9aeaf9299e61a1$var$curatedGenes.reactCuratedGenes = (0, $gXNCa$reduxbundler.createSelector)('selectCuratedGenesShouldUpdate', 'selectGrameneViewsOn', (shouldUpdate, viewsOn)=>{
|
|
414
|
-
if (!shouldUpdate) return;
|
|
415
|
-
if (!viewsOn || !viewsOn.has('list')) return;
|
|
416
|
-
return {
|
|
417
|
-
actionCreator: 'doFetchCuratedGenes'
|
|
418
|
-
};
|
|
419
|
-
});
|
|
420
403
|
const $9d9aeaf9299e61a1$var$grameneGermplasm = (0, $gXNCa$reduxbundler.createAsyncResourceBundle)({
|
|
421
404
|
name: 'grameneGermplasm',
|
|
422
405
|
actionBaseType: 'GRAMENE_GERMPLASM',
|
|
@@ -585,26 +568,25 @@ const $9d9aeaf9299e61a1$var$grameneSearch = (0, $gXNCa$reduxbundler.createAsyncR
|
|
|
585
568
|
const noFilters = q === '*:*' && !userSubsetGenomes;
|
|
586
569
|
const effectiveRows = noFilters ? 0 : rows;
|
|
587
570
|
const facetField = noFilters ? $9d9aeaf9299e61a1$var$noFilterFacets : $9d9aeaf9299e61a1$var$facets;
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
|
|
571
|
+
// Set of taxon_ids the UI knows about — keep only these in the response.
|
|
572
|
+
// Anything else (hidden taxa, or taxa that exist in Solr but not in this
|
|
573
|
+
// site's maps collection) is stripped so downstream consumers — notably
|
|
574
|
+
// the TaxDist Vis which throws on unknown tids — see a clean visible-only
|
|
575
|
+
// view. Mirrors the old server-side `fq=taxon_id:(visible)` behavior.
|
|
576
|
+
const visibleTaxaSet = new Set(visibleTaxa);
|
|
591
577
|
// return fetch(`${store.selectGrameneAPI()}/search?q=${q}&facet.field=${facetField}&rows=${effectiveRows}&start=${offset}${fq}&stats=true&${statsFields.join('&')}`)
|
|
592
578
|
return fetch(`${store.selectGrameneAPI()}/search?q=${q}&facet.field=${facetField}&rows=${effectiveRows}&start=${offset}${fq}`).then((res)=>res.json()).then((res)=>{
|
|
593
|
-
|
|
594
|
-
// on the server, the response reflects every taxon; we subtract
|
|
595
|
-
// hidden ones from totals and facets so the rest of the UI sees a
|
|
596
|
-
// "visible-only" view.
|
|
597
|
-
if (hiddenTaxa.size && res.facet_counts && res.facet_counts.facet_fields) {
|
|
579
|
+
if (visibleTaxaSet.size && res.facet_counts && res.facet_counts.facet_fields) {
|
|
598
580
|
const tf = res.facet_counts.facet_fields.taxon_id;
|
|
599
581
|
if (Array.isArray(tf)) {
|
|
600
|
-
let
|
|
582
|
+
let droppedGeneCount = 0;
|
|
601
583
|
const kept = [];
|
|
602
|
-
for(let i = 0; i < tf.length; i += 2)if (
|
|
603
|
-
else
|
|
584
|
+
for(let i = 0; i < tf.length; i += 2)if (visibleTaxaSet.has(String(tf[i]))) kept.push(tf[i], tf[i + 1]);
|
|
585
|
+
else droppedGeneCount += tf[i + 1];
|
|
604
586
|
res.facet_counts.facet_fields.taxon_id = kept;
|
|
605
587
|
if (res.response) {
|
|
606
|
-
res.response.numFound = Math.max(0, res.response.numFound -
|
|
607
|
-
if (Array.isArray(res.response.docs)) res.response.docs = res.response.docs.filter((d)
|
|
588
|
+
res.response.numFound = Math.max(0, res.response.numFound - droppedGeneCount);
|
|
589
|
+
if (Array.isArray(res.response.docs)) res.response.docs = res.response.docs.filter((d)=>visibleTaxaSet.has(String(d.taxon_id)));
|
|
608
590
|
}
|
|
609
591
|
}
|
|
610
592
|
}
|
|
@@ -657,8 +639,8 @@ const $9d9aeaf9299e61a1$var$grameneTaxDist = {
|
|
|
657
639
|
grameneTaxonomy[tid].name = map.display_name;
|
|
658
640
|
});
|
|
659
641
|
const binnedResults = $9d9aeaf9299e61a1$var$formatFacetCountsForViz(grameneSearch.facet_counts.facet_fields.fixed_1000__bin);
|
|
660
|
-
let speciesTree = (0, ($parcel$interopDefault($gXNCa$
|
|
661
|
-
let binMapper = (0, ($parcel$interopDefault($gXNCa$
|
|
642
|
+
let speciesTree = (0, ($parcel$interopDefault($gXNCa$gramenetreesclientsrctaxonomy))).tree(Object.values(grameneTaxonomy));
|
|
643
|
+
let binMapper = (0, ($parcel$interopDefault($gXNCa$gramenebinsclientsrcbins)))(grameneMaps);
|
|
662
644
|
let taxDist = (0, $gXNCa$gramenetaxonomywithgenomes.build)(binMapper, speciesTree);
|
|
663
645
|
taxDist.setBinType('fixed', 1000);
|
|
664
646
|
taxDist.setResults(binnedResults);
|
|
@@ -816,7 +798,6 @@ $9d9aeaf9299e61a1$export$2e2bcd8739ae039 = [
|
|
|
816
798
|
$9d9aeaf9299e61a1$var$grameneTaxDist,
|
|
817
799
|
$9d9aeaf9299e61a1$var$grameneOrthologs,
|
|
818
800
|
$9d9aeaf9299e61a1$var$grameneParalogs,
|
|
819
|
-
$9d9aeaf9299e61a1$var$curatedGenes,
|
|
820
801
|
$9d9aeaf9299e61a1$var$grameneGermplasm,
|
|
821
802
|
$9d9aeaf9299e61a1$var$expressionSamples,
|
|
822
803
|
$9d9aeaf9299e61a1$var$expressionStudies
|
|
@@ -1521,14 +1502,14 @@ const $24971af0a229e0e3$var$grameneViews = {
|
|
|
1521
1502
|
{
|
|
1522
1503
|
id: 'expression',
|
|
1523
1504
|
name: 'Gene expression',
|
|
1524
|
-
show: '
|
|
1505
|
+
show: 'disabled',
|
|
1525
1506
|
shouldScroll: false,
|
|
1526
1507
|
desiredSamples: {}
|
|
1527
1508
|
},
|
|
1528
1509
|
{
|
|
1529
1510
|
id: 'attribs',
|
|
1530
1511
|
name: 'Gene attributes',
|
|
1531
|
-
show: '
|
|
1512
|
+
show: 'disabled',
|
|
1532
1513
|
shouldScroll: false
|
|
1533
1514
|
}
|
|
1534
1515
|
],
|
|
@@ -1569,6 +1550,26 @@ const $24971af0a229e0e3$var$grameneViews = {
|
|
|
1569
1550
|
newState = Object.assign({}, state);
|
|
1570
1551
|
newState.options.forEach((v)=>v.shouldScroll = false);
|
|
1571
1552
|
return newState;
|
|
1553
|
+
case 'GRAMENE_VIEWS_REPLACED':
|
|
1554
|
+
{
|
|
1555
|
+
// Snapshot restore. payload: { on: Set|Array<id>, touched: {id: true} }
|
|
1556
|
+
// Sets options[].show directly to 'on' or 'off' based on membership in
|
|
1557
|
+
// `on`. Leaves 'disabled' entries untouched (site config wins).
|
|
1558
|
+
const onSet = payload.on instanceof Set ? payload.on : new Set(payload.on || []);
|
|
1559
|
+
newState = Object.assign({}, state);
|
|
1560
|
+
newState.touched = {
|
|
1561
|
+
...payload.touched || {}
|
|
1562
|
+
};
|
|
1563
|
+
newState.options = state.options.map((v)=>{
|
|
1564
|
+
if (v.show === 'disabled') return v;
|
|
1565
|
+
return {
|
|
1566
|
+
...v,
|
|
1567
|
+
show: onSet.has(v.id) ? 'on' : 'off',
|
|
1568
|
+
shouldScroll: false
|
|
1569
|
+
};
|
|
1570
|
+
});
|
|
1571
|
+
return newState;
|
|
1572
|
+
}
|
|
1572
1573
|
default:
|
|
1573
1574
|
return state;
|
|
1574
1575
|
}
|
|
@@ -1592,6 +1593,19 @@ const $24971af0a229e0e3$var$grameneViews = {
|
|
|
1592
1593
|
payload: null
|
|
1593
1594
|
});
|
|
1594
1595
|
},
|
|
1596
|
+
// Snapshot restore. `on` is an array of view ids to switch on; everything
|
|
1597
|
+
// else (except disabled views) goes off. `touched` is the same map the
|
|
1598
|
+
// user-toggled view bundle maintains, so the auto-default logic in
|
|
1599
|
+
// selectGrameneViews respects what the user has explicitly set.
|
|
1600
|
+
doReplaceGrameneViews: ({ on: on, touched: touched })=>({ dispatch: dispatch })=>{
|
|
1601
|
+
dispatch({
|
|
1602
|
+
type: 'GRAMENE_VIEWS_REPLACED',
|
|
1603
|
+
payload: {
|
|
1604
|
+
on: on,
|
|
1605
|
+
touched: touched
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
},
|
|
1595
1609
|
selectRawGrameneViews: (state)=>state.grameneViews,
|
|
1596
1610
|
selectGrameneViews: (0, $gXNCa$reduxbundler.createSelector)('selectRawGrameneViews', 'selectConfiguration', 'selectGrameneSearch', 'selectGrameneFilters', (raw, config, search, filters)=>{
|
|
1597
1611
|
const overrides = config && config.views || null;
|
|
@@ -1656,9 +1670,10 @@ var $24971af0a229e0e3$export$2e2bcd8739ae039 = $24971af0a229e0e3$var$grameneView
|
|
|
1656
1670
|
// indefinitely (default maxAge of `Infinity`). The pathway set is small
|
|
1657
1671
|
// and stable enough to keep around across sessions, so we bulk-load it
|
|
1658
1672
|
// once with `?rows=-1` and reuse it instead of issuing per-id requests.
|
|
1673
|
+
// Scoped to the subsite so per-site pathway corpora don't collide.
|
|
1659
1674
|
const $671312b287158a8a$var$pathwayCache = (0, $gXNCa$moneyclip.getConfiguredCache)({
|
|
1660
1675
|
version: 1,
|
|
1661
|
-
name: '
|
|
1676
|
+
name: `gramene_pathways_${process.env.SUBSITE || 'default'}`
|
|
1662
1677
|
});
|
|
1663
1678
|
let $671312b287158a8a$var$pathwaysBulkPromise = null;
|
|
1664
1679
|
const $671312b287158a8a$var$grameneDocs = {
|
|
@@ -3932,8 +3947,12 @@ var $c921a0d2b34aadb6$export$2e2bcd8739ae039 = $c921a0d2b34aadb6$var$ontologies;
|
|
|
3932
3947
|
// activeTaxon: <taxon_id|null>,
|
|
3933
3948
|
// byTaxon: {
|
|
3934
3949
|
// [taxon_id]: {
|
|
3935
|
-
// selectedFields: [<solr field name>...],
|
|
3950
|
+
// selectedFields: [<solr field name>...], // order = column/axis order
|
|
3936
3951
|
// fieldsModalOpen: <bool>,
|
|
3952
|
+
// vizMode: 'heatmap'|'parallel', // persisted view config
|
|
3953
|
+
// scale: 'linear'|'log', // persisted view config
|
|
3954
|
+
// brushes: { [field]: [lo, hi] }, // persisted parallel-coords brushes
|
|
3955
|
+
// pendingLoad: <bool>, // transient: a restored view wants a re-fetch
|
|
3937
3956
|
// fetch: { status, offset, total, signature, requestId, error },
|
|
3938
3957
|
// rows: [<doc>...]
|
|
3939
3958
|
// }
|
|
@@ -3983,6 +4002,10 @@ const $4f15cd8a7d970b18$var$exprViz = {
|
|
|
3983
4002
|
return {
|
|
3984
4003
|
selectedFields: [],
|
|
3985
4004
|
fieldsModalOpen: false,
|
|
4005
|
+
vizMode: 'heatmap',
|
|
4006
|
+
scale: 'linear',
|
|
4007
|
+
brushes: {},
|
|
4008
|
+
pendingLoad: false,
|
|
3986
4009
|
fetch: {
|
|
3987
4010
|
status: 'idle',
|
|
3988
4011
|
offset: 0,
|
|
@@ -4065,6 +4088,9 @@ const $4f15cd8a7d970b18$var$exprViz = {
|
|
|
4065
4088
|
[payload.taxon]: {
|
|
4066
4089
|
...t,
|
|
4067
4090
|
selectedFields: payload.fields,
|
|
4091
|
+
// A new field selection invalidates any prior brushes (they
|
|
4092
|
+
// reference axes that may no longer exist) and the loaded rows.
|
|
4093
|
+
brushes: {},
|
|
4068
4094
|
rows: [],
|
|
4069
4095
|
fetch: {
|
|
4070
4096
|
status: 'idle',
|
|
@@ -4078,6 +4104,77 @@ const $4f15cd8a7d970b18$var$exprViz = {
|
|
|
4078
4104
|
}
|
|
4079
4105
|
};
|
|
4080
4106
|
}
|
|
4107
|
+
case 'EXPRVIZ_VIZMODE_SET':
|
|
4108
|
+
{
|
|
4109
|
+
const t = ensureTaxon(state, payload.taxon);
|
|
4110
|
+
return {
|
|
4111
|
+
...state,
|
|
4112
|
+
byTaxon: {
|
|
4113
|
+
...state.byTaxon,
|
|
4114
|
+
[payload.taxon]: {
|
|
4115
|
+
...t,
|
|
4116
|
+
vizMode: payload.vizMode
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
case 'EXPRVIZ_SCALE_SET':
|
|
4122
|
+
{
|
|
4123
|
+
const t = ensureTaxon(state, payload.taxon);
|
|
4124
|
+
return {
|
|
4125
|
+
...state,
|
|
4126
|
+
byTaxon: {
|
|
4127
|
+
...state.byTaxon,
|
|
4128
|
+
[payload.taxon]: {
|
|
4129
|
+
...t,
|
|
4130
|
+
scale: payload.scale
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
4135
|
+
case 'EXPRVIZ_BRUSHES_SET':
|
|
4136
|
+
{
|
|
4137
|
+
const t = ensureTaxon(state, payload.taxon);
|
|
4138
|
+
return {
|
|
4139
|
+
...state,
|
|
4140
|
+
byTaxon: {
|
|
4141
|
+
...state.byTaxon,
|
|
4142
|
+
[payload.taxon]: {
|
|
4143
|
+
...t,
|
|
4144
|
+
brushes: payload.brushes || {}
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
case 'EXPRVIZ_RESTORED':
|
|
4150
|
+
{
|
|
4151
|
+
// Re-apply persisted view config from a saved-view snapshot. Async
|
|
4152
|
+
// data (rows) is NOT in the snapshot — instead each taxon with a
|
|
4153
|
+
// field selection is flagged pendingLoad so reactExprVizRestoreLoad
|
|
4154
|
+
// re-fetches it once the search context is ready.
|
|
4155
|
+
const byTaxon = {
|
|
4156
|
+
...state.byTaxon
|
|
4157
|
+
};
|
|
4158
|
+
const cfg = payload.byTaxon || {};
|
|
4159
|
+
for (const tid of Object.keys(cfg)){
|
|
4160
|
+
const base = ensureTaxon(state, tid);
|
|
4161
|
+
const c = cfg[tid] || {};
|
|
4162
|
+
const selectedFields = Array.isArray(c.selectedFields) ? c.selectedFields : [];
|
|
4163
|
+
byTaxon[tid] = {
|
|
4164
|
+
...base,
|
|
4165
|
+
selectedFields: selectedFields,
|
|
4166
|
+
vizMode: c.vizMode || 'heatmap',
|
|
4167
|
+
scale: c.scale || 'linear',
|
|
4168
|
+
brushes: c.brushes || {},
|
|
4169
|
+
pendingLoad: selectedFields.length > 0
|
|
4170
|
+
};
|
|
4171
|
+
}
|
|
4172
|
+
return {
|
|
4173
|
+
...state,
|
|
4174
|
+
activeTaxon: payload.activeTaxon || state.activeTaxon,
|
|
4175
|
+
byTaxon: byTaxon
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4081
4178
|
case 'EXPRVIZ_FIELDS_REORDERED':
|
|
4082
4179
|
{
|
|
4083
4180
|
const t = state.byTaxon[payload.taxon];
|
|
@@ -4108,6 +4205,7 @@ const $4f15cd8a7d970b18$var$exprViz = {
|
|
|
4108
4205
|
...state.byTaxon,
|
|
4109
4206
|
[payload.taxon]: {
|
|
4110
4207
|
...t,
|
|
4208
|
+
pendingLoad: false,
|
|
4111
4209
|
fetch: {
|
|
4112
4210
|
...t.fetch,
|
|
4113
4211
|
status: 'loading',
|
|
@@ -4365,6 +4463,41 @@ const $4f15cd8a7d970b18$var$exprViz = {
|
|
|
4365
4463
|
fields: fields
|
|
4366
4464
|
}
|
|
4367
4465
|
}),
|
|
4466
|
+
doSetExprVizVizMode: (taxon, vizMode)=>({ dispatch: dispatch })=>dispatch({
|
|
4467
|
+
type: 'EXPRVIZ_VIZMODE_SET',
|
|
4468
|
+
payload: {
|
|
4469
|
+
taxon: taxon,
|
|
4470
|
+
vizMode: vizMode
|
|
4471
|
+
}
|
|
4472
|
+
}),
|
|
4473
|
+
doSetExprVizScale: (taxon, scale)=>({ dispatch: dispatch })=>dispatch({
|
|
4474
|
+
type: 'EXPRVIZ_SCALE_SET',
|
|
4475
|
+
payload: {
|
|
4476
|
+
taxon: taxon,
|
|
4477
|
+
scale: scale
|
|
4478
|
+
}
|
|
4479
|
+
}),
|
|
4480
|
+
doSetExprVizBrushes: (taxon, brushes)=>({ dispatch: dispatch })=>dispatch({
|
|
4481
|
+
type: 'EXPRVIZ_BRUSHES_SET',
|
|
4482
|
+
payload: {
|
|
4483
|
+
taxon: taxon,
|
|
4484
|
+
brushes: brushes
|
|
4485
|
+
}
|
|
4486
|
+
}),
|
|
4487
|
+
// Re-apply persisted exprViz view config from a saved-view snapshot. Called
|
|
4488
|
+
// by viewSnapshot's doApplyViewSnapshot after filters/views have been
|
|
4489
|
+
// restored, so the re-fetch (driven by reactExprVizRestoreLoad) sees the
|
|
4490
|
+
// restored query context.
|
|
4491
|
+
doApplyExprVizSnapshot: (snap)=>({ dispatch: dispatch })=>{
|
|
4492
|
+
if (!snap || typeof snap !== 'object') return;
|
|
4493
|
+
dispatch({
|
|
4494
|
+
type: 'EXPRVIZ_RESTORED',
|
|
4495
|
+
payload: {
|
|
4496
|
+
activeTaxon: snap.activeTaxon || null,
|
|
4497
|
+
byTaxon: snap.byTaxon || {}
|
|
4498
|
+
}
|
|
4499
|
+
});
|
|
4500
|
+
},
|
|
4368
4501
|
doFetchExprVizData: (taxon)=>({ dispatch: dispatch, store: store })=>{
|
|
4369
4502
|
const ev = store.selectExprViz();
|
|
4370
4503
|
const t = ev.byTaxon[taxon];
|
|
@@ -4485,6 +4618,25 @@ const $4f15cd8a7d970b18$var$exprViz = {
|
|
|
4485
4618
|
actionCreator: 'doFetchExprVizPivot'
|
|
4486
4619
|
};
|
|
4487
4620
|
}),
|
|
4621
|
+
// After a saved view is applied, EXPRVIZ_RESTORED flags each taxon that had
|
|
4622
|
+
// a field selection with pendingLoad. Once the search context is live (not
|
|
4623
|
+
// 'init') and the view is on, re-fetch that taxon's rows. Fires one taxon at
|
|
4624
|
+
// a time; EXPRVIZ_FETCH_STARTED clears the flag so this won't loop. Robust to
|
|
4625
|
+
// a GRAMENE_SEARCH_CLEARED that wipes rows mid-restore — the flag persists.
|
|
4626
|
+
reactExprVizRestoreLoad: (0, $gXNCa$reduxbundler.createSelector)('selectExprViz', 'selectGrameneFiltersStatus', 'selectGrameneViews', (ev, filtersStatus, views)=>{
|
|
4627
|
+
if (!ev || filtersStatus === 'init') return;
|
|
4628
|
+
const onView = views && views.options && views.options.find((v)=>v.id === 'exprViz');
|
|
4629
|
+
if (!onView || onView.show !== 'on') return;
|
|
4630
|
+
for (const tid of Object.keys(ev.byTaxon || {})){
|
|
4631
|
+
const t = ev.byTaxon[tid];
|
|
4632
|
+
if (t && t.pendingLoad && t.selectedFields.length > 0 && t.rows.length === 0 && t.fetch.status !== 'loading') return {
|
|
4633
|
+
actionCreator: 'doFetchExprVizData',
|
|
4634
|
+
args: [
|
|
4635
|
+
tid
|
|
4636
|
+
]
|
|
4637
|
+
};
|
|
4638
|
+
}
|
|
4639
|
+
}),
|
|
4488
4640
|
selectExprViz: (state)=>state.exprViz,
|
|
4489
4641
|
selectExprVizPivot: (state)=>state.exprViz.pivot,
|
|
4490
4642
|
selectExprVizActiveTaxon: (state)=>state.exprViz.activeTaxon,
|
|
@@ -4588,7 +4740,10 @@ const $d365d8c287ab0c94$var$ontologyEnrichment = {
|
|
|
4588
4740
|
maxGSSize: 500,
|
|
4589
4741
|
mostSpecific: false,
|
|
4590
4742
|
ontology: 'all',
|
|
4591
|
-
search: ''
|
|
4743
|
+
search: '',
|
|
4744
|
+
// Per-section table sort, keyed by ontology section id (e.g.
|
|
4745
|
+
// 'GO:biological_process'): { [sectionKey]: { key, dir } }.
|
|
4746
|
+
sort: {}
|
|
4592
4747
|
}
|
|
4593
4748
|
};
|
|
4594
4749
|
function ensureTaxon(state, tid) {
|
|
@@ -4792,6 +4947,21 @@ const $d365d8c287ab0c94$var$ontologyEnrichment = {
|
|
|
4792
4947
|
type: 'ONTOLOGY_ENRICHMENT_UI_SET',
|
|
4793
4948
|
payload: patch
|
|
4794
4949
|
}),
|
|
4950
|
+
// Re-apply persisted view config from a saved-view snapshot. Setting the
|
|
4951
|
+
// active taxon (last) lets reactOntologyEnrichmentFetch re-fetch the
|
|
4952
|
+
// foreground/background facets for the restored filters — no bulk data is
|
|
4953
|
+
// carried in the snapshot.
|
|
4954
|
+
doApplyOntologyEnrichmentSnapshot: (snap)=>({ dispatch: dispatch })=>{
|
|
4955
|
+
if (!snap || typeof snap !== 'object') return;
|
|
4956
|
+
if (snap.ui) dispatch({
|
|
4957
|
+
type: 'ONTOLOGY_ENRICHMENT_UI_SET',
|
|
4958
|
+
payload: snap.ui
|
|
4959
|
+
});
|
|
4960
|
+
if (snap.activeTaxon) dispatch({
|
|
4961
|
+
type: 'ONTOLOGY_ENRICHMENT_ACTIVE_TAXON_SET',
|
|
4962
|
+
payload: snap.activeTaxon
|
|
4963
|
+
});
|
|
4964
|
+
},
|
|
4795
4965
|
doFetchOntologyEnrichmentForeground: (taxon)=>({ dispatch: dispatch, store: store })=>{
|
|
4796
4966
|
const q = store.selectGrameneFiltersQueryString();
|
|
4797
4967
|
const signature = $d365d8c287ab0c94$var$fgSig(q, taxon);
|
|
@@ -5149,143 +5319,1149 @@ function $d365d8c287ab0c94$var$collapseToMostSpecific(ontKey, rows, recs) {
|
|
|
5149
5319
|
var $d365d8c287ab0c94$export$2e2bcd8739ae039 = $d365d8c287ab0c94$var$ontologyEnrichment;
|
|
5150
5320
|
|
|
5151
5321
|
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
}) : '';
|
|
5181
|
-
const fewerButton = rows > 20 ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("button", {
|
|
5182
|
-
onClick: (e)=>doChangeQuantity('Genes', -20),
|
|
5183
|
-
children: "fewer"
|
|
5184
|
-
}) : '';
|
|
5185
|
-
const docsToShow = results.response.docs.slice(0, rows);
|
|
5186
|
-
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
5187
|
-
id: "Genes",
|
|
5188
|
-
className: "container mb40 anchor",
|
|
5189
|
-
children: [
|
|
5190
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
5191
|
-
className: "fancy-title mb40",
|
|
5192
|
-
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("h4", {
|
|
5193
|
-
children: "Genes"
|
|
5194
|
-
})
|
|
5195
|
-
}),
|
|
5196
|
-
docsToShow.map((doc, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($2fec4872fbf7ebd2$var$Gene, {
|
|
5197
|
-
gene: doc
|
|
5198
|
-
}, idx)),
|
|
5199
|
-
fewerButton,
|
|
5200
|
-
moreButton
|
|
5201
|
-
]
|
|
5202
|
-
});
|
|
5203
|
-
}
|
|
5204
|
-
};
|
|
5205
|
-
const $2fec4872fbf7ebd2$var$Pathway = ({ pathway: pathway })=>{
|
|
5206
|
-
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
5207
|
-
className: "row",
|
|
5208
|
-
children: pathway.name
|
|
5209
|
-
});
|
|
5322
|
+
// Per-gene UI state lifted out of <Gene> and <Homology> class components.
|
|
5323
|
+
// Keyed by geneId so it survives unmount/remount (e.g. scrolling a row out
|
|
5324
|
+
// of view and back), and so a snapshot serializer can later round-trip it
|
|
5325
|
+
// for the shareable-views feature.
|
|
5326
|
+
//
|
|
5327
|
+
// What lives here:
|
|
5328
|
+
// - expandedDetail: which detail tab is open in the gene-list card
|
|
5329
|
+
// - fullscreen: whether the expanded detail is full-screened
|
|
5330
|
+
// - homology.viewer: 'treevis' | 'tbrowse'
|
|
5331
|
+
// - homology.height: drag-resize height of the homology detail pane
|
|
5332
|
+
// - homology.tbrowse: the tbrowse ViewState (collapsedNodeIds, prunedNodeIds,
|
|
5333
|
+
// swappedNodeIds, compressedNodeIds, nodeOfInterestId, zones, zoneStates,
|
|
5334
|
+
// search, selectedNodeId). Tbrowse is driven in controlled mode from this.
|
|
5335
|
+
// - sequences: the Sequences detail's internal state — tab ('dna'|'rna'|'pep'),
|
|
5336
|
+
// tid (selected transcript id), upstream/downstream (flanking bp). Driven in
|
|
5337
|
+
// controlled mode from this so a saved view restores the chosen sub-tab and
|
|
5338
|
+
// isoform.
|
|
5339
|
+
// - expression: the Expression detail's internal state — activeTab
|
|
5340
|
+
// ('gene'|'paralogs'|'eFP'), atlasExperiment (selected GXA experiment id),
|
|
5341
|
+
// barStudy (selected eFP/BAR study). Driven in controlled mode from this so a
|
|
5342
|
+
// saved view restores the chosen sub-tab and study.
|
|
5343
|
+
//
|
|
5344
|
+
// What does NOT live here:
|
|
5345
|
+
// - derived/computed state like `details` (per-render config from
|
|
5346
|
+
// props.config.details + capabilities)
|
|
5347
|
+
// - async-fetched data caches in <Homology> (neighborhood, geneStructures)
|
|
5348
|
+
const $7f865ea0feda21af$var$initialState = {
|
|
5349
|
+
byGene: {}
|
|
5210
5350
|
};
|
|
5211
|
-
const $
|
|
5212
|
-
if (
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
results.pathways.map((doc, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($2fec4872fbf7ebd2$var$Pathway, {
|
|
5223
|
-
pathway: doc
|
|
5224
|
-
}, idx))
|
|
5225
|
-
]
|
|
5226
|
-
});
|
|
5351
|
+
const $7f865ea0feda21af$var$ensureGene = (state, geneId)=>{
|
|
5352
|
+
if (state.byGene[geneId]) return state;
|
|
5353
|
+
return {
|
|
5354
|
+
...state,
|
|
5355
|
+
byGene: {
|
|
5356
|
+
...state.byGene,
|
|
5357
|
+
[geneId]: {
|
|
5358
|
+
homology: {}
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
};
|
|
5227
5362
|
};
|
|
5228
|
-
const $
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5363
|
+
const $7f865ea0feda21af$var$setGene = (state, geneId, patch)=>{
|
|
5364
|
+
const next = $7f865ea0feda21af$var$ensureGene(state, geneId);
|
|
5365
|
+
return {
|
|
5366
|
+
...next,
|
|
5367
|
+
byGene: {
|
|
5368
|
+
...next.byGene,
|
|
5369
|
+
[geneId]: {
|
|
5370
|
+
...next.byGene[geneId],
|
|
5371
|
+
...patch
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5374
|
+
};
|
|
5233
5375
|
};
|
|
5234
|
-
const $
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
}
|
|
5376
|
+
const $7f865ea0feda21af$var$setHomology = (state, geneId, patch)=>{
|
|
5377
|
+
const next = $7f865ea0feda21af$var$ensureGene(state, geneId);
|
|
5378
|
+
const prev = next.byGene[geneId];
|
|
5379
|
+
return {
|
|
5380
|
+
...next,
|
|
5381
|
+
byGene: {
|
|
5382
|
+
...next.byGene,
|
|
5383
|
+
[geneId]: {
|
|
5384
|
+
...prev,
|
|
5385
|
+
homology: {
|
|
5386
|
+
...prev.homology || {},
|
|
5387
|
+
...patch
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
};
|
|
5250
5392
|
};
|
|
5251
|
-
const $
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5393
|
+
const $7f865ea0feda21af$var$setSequences = (state, geneId, patch)=>{
|
|
5394
|
+
const next = $7f865ea0feda21af$var$ensureGene(state, geneId);
|
|
5395
|
+
const prev = next.byGene[geneId];
|
|
5396
|
+
return {
|
|
5397
|
+
...next,
|
|
5398
|
+
byGene: {
|
|
5399
|
+
...next.byGene,
|
|
5400
|
+
[geneId]: {
|
|
5401
|
+
...prev,
|
|
5402
|
+
sequences: {
|
|
5403
|
+
...prev.sequences || {},
|
|
5404
|
+
...patch
|
|
5405
|
+
}
|
|
5406
|
+
}
|
|
5407
|
+
}
|
|
5408
|
+
};
|
|
5256
5409
|
};
|
|
5257
|
-
const $
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
}
|
|
5410
|
+
const $7f865ea0feda21af$var$setExpression = (state, geneId, patch)=>{
|
|
5411
|
+
const next = $7f865ea0feda21af$var$ensureGene(state, geneId);
|
|
5412
|
+
const prev = next.byGene[geneId];
|
|
5413
|
+
return {
|
|
5414
|
+
...next,
|
|
5415
|
+
byGene: {
|
|
5416
|
+
...next.byGene,
|
|
5417
|
+
[geneId]: {
|
|
5418
|
+
...prev,
|
|
5419
|
+
expression: {
|
|
5420
|
+
...prev.expression || {},
|
|
5421
|
+
...patch
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
};
|
|
5273
5426
|
};
|
|
5274
|
-
const $
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5427
|
+
const $7f865ea0feda21af$var$uiViewState = {
|
|
5428
|
+
name: 'uiViewState',
|
|
5429
|
+
getReducer: ()=>(state = $7f865ea0feda21af$var$initialState, { type: type, payload: payload })=>{
|
|
5430
|
+
switch(type){
|
|
5431
|
+
case 'UI_GENE_DETAIL_EXPANDED':
|
|
5432
|
+
// payload: { geneId, detail } (detail = null to collapse)
|
|
5433
|
+
return $7f865ea0feda21af$var$setGene(state, payload.geneId, {
|
|
5434
|
+
expandedDetail: payload.detail,
|
|
5435
|
+
// collapsing also exits fullscreen
|
|
5436
|
+
fullscreen: payload.detail === null ? false : state.byGene[payload.geneId]?.fullscreen || false
|
|
5437
|
+
});
|
|
5438
|
+
case 'UI_GENE_FULLSCREEN_SET':
|
|
5439
|
+
// payload: { geneId, fullscreen }
|
|
5440
|
+
return $7f865ea0feda21af$var$setGene(state, payload.geneId, {
|
|
5441
|
+
fullscreen: !!payload.fullscreen
|
|
5442
|
+
});
|
|
5443
|
+
case 'UI_HOMOLOGY_VIEWER_SET':
|
|
5444
|
+
// payload: { geneId, viewer }
|
|
5445
|
+
return $7f865ea0feda21af$var$setHomology(state, payload.geneId, {
|
|
5446
|
+
viewer: payload.viewer
|
|
5447
|
+
});
|
|
5448
|
+
case 'UI_HOMOLOGY_HEIGHT_SET':
|
|
5449
|
+
// payload: { geneId, height }
|
|
5450
|
+
return $7f865ea0feda21af$var$setHomology(state, payload.geneId, {
|
|
5451
|
+
height: payload.height
|
|
5452
|
+
});
|
|
5453
|
+
case 'UI_HOMOLOGY_TBROWSE_SET':
|
|
5454
|
+
// payload: { geneId, tbrowse } -- a full ViewState object
|
|
5455
|
+
return $7f865ea0feda21af$var$setHomology(state, payload.geneId, {
|
|
5456
|
+
tbrowse: payload.tbrowse
|
|
5457
|
+
});
|
|
5458
|
+
case 'UI_SEQUENCES_SET':
|
|
5459
|
+
// payload: { geneId, patch } -- merges into the sequences slice
|
|
5460
|
+
// (e.g. { tab }, { tid }, { upstream }, { downstream })
|
|
5461
|
+
return $7f865ea0feda21af$var$setSequences(state, payload.geneId, payload.patch);
|
|
5462
|
+
case 'UI_EXPRESSION_SET':
|
|
5463
|
+
// payload: { geneId, patch } -- merges into the expression slice
|
|
5464
|
+
// (e.g. { activeTab }, { atlasExperiment }, { barStudy })
|
|
5465
|
+
return $7f865ea0feda21af$var$setExpression(state, payload.geneId, payload.patch);
|
|
5466
|
+
case 'UI_VIEW_STATE_REPLACED':
|
|
5467
|
+
// payload: { byGene } -- used by the snapshot loader in a later phase
|
|
5468
|
+
return {
|
|
5469
|
+
byGene: payload.byGene || {}
|
|
5470
|
+
};
|
|
5471
|
+
default:
|
|
5472
|
+
return state;
|
|
5473
|
+
}
|
|
5474
|
+
},
|
|
5475
|
+
selectUiViewState: (state)=>state.uiViewState,
|
|
5476
|
+
doExpandGeneDetail: ({ geneId: geneId, detail: detail })=>({ dispatch: dispatch })=>{
|
|
5477
|
+
dispatch({
|
|
5478
|
+
type: 'UI_GENE_DETAIL_EXPANDED',
|
|
5479
|
+
payload: {
|
|
5480
|
+
geneId: geneId,
|
|
5481
|
+
detail: detail
|
|
5482
|
+
}
|
|
5483
|
+
});
|
|
5484
|
+
},
|
|
5485
|
+
doSetGeneFullscreen: ({ geneId: geneId, fullscreen: fullscreen })=>({ dispatch: dispatch })=>{
|
|
5486
|
+
dispatch({
|
|
5487
|
+
type: 'UI_GENE_FULLSCREEN_SET',
|
|
5488
|
+
payload: {
|
|
5489
|
+
geneId: geneId,
|
|
5490
|
+
fullscreen: fullscreen
|
|
5491
|
+
}
|
|
5492
|
+
});
|
|
5493
|
+
},
|
|
5494
|
+
doSetHomologyViewer: ({ geneId: geneId, viewer: viewer })=>({ dispatch: dispatch })=>{
|
|
5495
|
+
dispatch({
|
|
5496
|
+
type: 'UI_HOMOLOGY_VIEWER_SET',
|
|
5497
|
+
payload: {
|
|
5498
|
+
geneId: geneId,
|
|
5499
|
+
viewer: viewer
|
|
5500
|
+
}
|
|
5501
|
+
});
|
|
5502
|
+
},
|
|
5503
|
+
doSetHomologyHeight: ({ geneId: geneId, height: height })=>({ dispatch: dispatch })=>{
|
|
5504
|
+
dispatch({
|
|
5505
|
+
type: 'UI_HOMOLOGY_HEIGHT_SET',
|
|
5506
|
+
payload: {
|
|
5507
|
+
geneId: geneId,
|
|
5508
|
+
height: height
|
|
5509
|
+
}
|
|
5510
|
+
});
|
|
5511
|
+
},
|
|
5512
|
+
doSetHomologyTbrowseViewState: ({ geneId: geneId, tbrowse: tbrowse })=>({ dispatch: dispatch })=>{
|
|
5513
|
+
dispatch({
|
|
5514
|
+
type: 'UI_HOMOLOGY_TBROWSE_SET',
|
|
5515
|
+
payload: {
|
|
5516
|
+
geneId: geneId,
|
|
5517
|
+
tbrowse: tbrowse
|
|
5518
|
+
}
|
|
5519
|
+
});
|
|
5520
|
+
},
|
|
5521
|
+
doSetSequencesState: ({ geneId: geneId, patch: patch })=>({ dispatch: dispatch })=>{
|
|
5522
|
+
dispatch({
|
|
5523
|
+
type: 'UI_SEQUENCES_SET',
|
|
5524
|
+
payload: {
|
|
5525
|
+
geneId: geneId,
|
|
5526
|
+
patch: patch
|
|
5527
|
+
}
|
|
5528
|
+
});
|
|
5529
|
+
},
|
|
5530
|
+
doSetExpressionState: ({ geneId: geneId, patch: patch })=>({ dispatch: dispatch })=>{
|
|
5531
|
+
dispatch({
|
|
5532
|
+
type: 'UI_EXPRESSION_SET',
|
|
5533
|
+
payload: {
|
|
5534
|
+
geneId: geneId,
|
|
5535
|
+
patch: patch
|
|
5536
|
+
}
|
|
5537
|
+
});
|
|
5538
|
+
},
|
|
5539
|
+
doReplaceUiViewState: (byGene)=>({ dispatch: dispatch })=>{
|
|
5540
|
+
dispatch({
|
|
5541
|
+
type: 'UI_VIEW_STATE_REPLACED',
|
|
5542
|
+
payload: {
|
|
5543
|
+
byGene: byGene
|
|
5544
|
+
}
|
|
5545
|
+
});
|
|
5546
|
+
}
|
|
5547
|
+
};
|
|
5548
|
+
var $7f865ea0feda21af$export$2e2bcd8739ae039 = $7f865ea0feda21af$var$uiViewState;
|
|
5549
|
+
|
|
5550
|
+
|
|
5551
|
+
// Serializer/deserializer for shareable gene-search views.
|
|
5552
|
+
//
|
|
5553
|
+
// `selectViewSnapshot` produces a versioned JSON-safe blob from the current
|
|
5554
|
+
// store state. `doApplyViewSnapshot` validates a blob and restores it via
|
|
5555
|
+
// existing action creators. Persistence + UI live in later phases — this
|
|
5556
|
+
// bundle is the pure state-shape layer they build on.
|
|
5557
|
+
//
|
|
5558
|
+
// Snapshot schema, v1:
|
|
5559
|
+
// {
|
|
5560
|
+
// v: 1,
|
|
5561
|
+
// capturedAt: <ISO 8601 string>,
|
|
5562
|
+
// site: <subsite id>,
|
|
5563
|
+
// filters: <grameneFilters tree, stripped of ephemeral UI flags>,
|
|
5564
|
+
// views: { on: [id,...], touched: {id: true, ...} },
|
|
5565
|
+
// genomeSubset: { taxonId: true, ... } | null,
|
|
5566
|
+
// searchPage: { offset: number, rows: number } | null,
|
|
5567
|
+
// exprViz: { // Expression-visualization view
|
|
5568
|
+
// activeTaxon: <taxon_id|null>,
|
|
5569
|
+
// byTaxon: { [taxon_id]: { selectedFields: [field,...], vizMode, scale, brushes } }
|
|
5570
|
+
// } | undefined, // loaded rows re-fetch on apply
|
|
5571
|
+
// ontologyEnrichment: { // Ontology Enrichment view
|
|
5572
|
+
// activeTaxon: <taxon_id|null>,
|
|
5573
|
+
// ui: { pAdjCutoff, minGSSize, maxGSSize, mostSpecific, ontology, search, sort }
|
|
5574
|
+
// } | undefined, // facets re-fetch on apply
|
|
5575
|
+
// expandedDetails: [
|
|
5576
|
+
// { geneId,
|
|
5577
|
+
// expandedDetail: string|null,
|
|
5578
|
+
// fullscreen: boolean,
|
|
5579
|
+
// homology: { viewer, height, tbrowse: <ViewState> } | undefined,
|
|
5580
|
+
// sequences: { tab, tid, upstream, downstream } | undefined,
|
|
5581
|
+
// expression: { activeTab, atlasExperiment, barStudy } | undefined
|
|
5582
|
+
// }, ...
|
|
5583
|
+
// ]
|
|
5584
|
+
// }
|
|
5585
|
+
//
|
|
5586
|
+
// Anything not in this schema is intentionally NOT persisted — async fetch
|
|
5587
|
+
// caches, search-results, pagination cursors mid-flight, etc. all rehydrate
|
|
5588
|
+
// from the API once the snapshot is applied.
|
|
5589
|
+
const $472fe3745b238881$var$SCHEMA_VERSION = 1;
|
|
5590
|
+
// Keys on a filter node we want to keep. Everything else (showMenu, marked,
|
|
5591
|
+
// status flags from the root, etc.) is ephemeral UI and not meaningful in a
|
|
5592
|
+
// shared view.
|
|
5593
|
+
const $472fe3745b238881$var$FILTER_NODE_KEEP = [
|
|
5594
|
+
'leftIdx',
|
|
5595
|
+
'rightIdx',
|
|
5596
|
+
'operation',
|
|
5597
|
+
'negate',
|
|
5598
|
+
'fq_field',
|
|
5599
|
+
'fq_value',
|
|
5600
|
+
'name',
|
|
5601
|
+
'category',
|
|
5602
|
+
'warning'
|
|
5603
|
+
];
|
|
5604
|
+
const $472fe3745b238881$var$FILTER_ROOT_KEEP = [
|
|
5605
|
+
'operation',
|
|
5606
|
+
'negate',
|
|
5607
|
+
'leftIdx',
|
|
5608
|
+
'rightIdx',
|
|
5609
|
+
'children',
|
|
5610
|
+
'searchOffset',
|
|
5611
|
+
'rows'
|
|
5612
|
+
];
|
|
5613
|
+
const $472fe3745b238881$var$cleanFilterNode = (node)=>{
|
|
5614
|
+
const out = {};
|
|
5615
|
+
for (const k of $472fe3745b238881$var$FILTER_NODE_KEEP)if (node[k] !== undefined) out[k] = node[k];
|
|
5616
|
+
if (Array.isArray(node.children)) out.children = node.children.map($472fe3745b238881$var$cleanFilterNode);
|
|
5617
|
+
return out;
|
|
5618
|
+
};
|
|
5619
|
+
const $472fe3745b238881$var$cleanFilters = (filters)=>{
|
|
5620
|
+
const out = {};
|
|
5621
|
+
for (const k of $472fe3745b238881$var$FILTER_ROOT_KEEP)if (filters[k] !== undefined) out[k] = filters[k];
|
|
5622
|
+
if (Array.isArray(filters.children)) out.children = filters.children.map($472fe3745b238881$var$cleanFilterNode);
|
|
5623
|
+
return out;
|
|
5624
|
+
};
|
|
5625
|
+
// Drop bundle entries that hold no meaningful state (e.g. a row the user
|
|
5626
|
+
// scrolled past but never opened). Otherwise snapshots bloat with empties.
|
|
5627
|
+
const $472fe3745b238881$var$isMeaningfulGene = (entry)=>{
|
|
5628
|
+
if (!entry) return false;
|
|
5629
|
+
if (entry.expandedDetail) return true;
|
|
5630
|
+
if (entry.fullscreen) return true;
|
|
5631
|
+
if (entry.homology && (entry.homology.viewer !== undefined || entry.homology.height !== undefined || entry.homology.tbrowse !== undefined)) return true;
|
|
5632
|
+
if (entry.sequences && Object.keys(entry.sequences).length) return true;
|
|
5633
|
+
if (entry.expression && Object.keys(entry.expression).length) return true;
|
|
5634
|
+
return false;
|
|
5635
|
+
};
|
|
5636
|
+
const $472fe3745b238881$var$viewSnapshot = {
|
|
5637
|
+
name: 'viewSnapshot',
|
|
5638
|
+
// No reducer — this bundle is pure derivation + dispatch orchestration.
|
|
5639
|
+
getReducer: ()=>(state = {})=>state,
|
|
5640
|
+
selectViewSnapshot: $472fe3745b238881$var$createSnapshotSelector(),
|
|
5641
|
+
// Apply a snapshot to the store. Returns an array of unresolved-id warnings
|
|
5642
|
+
// so the caller can surface a non-blocking notice. The action creators we
|
|
5643
|
+
// delegate to are existing ones; nothing here knows about API/UI/share-link
|
|
5644
|
+
// — that's later phases.
|
|
5645
|
+
doApplyViewSnapshot: (snapshot)=>({ dispatch: dispatch, store: store })=>{
|
|
5646
|
+
const warnings = $472fe3745b238881$var$validateSnapshot(snapshot);
|
|
5647
|
+
if (warnings.fatal) {
|
|
5648
|
+
console.warn('viewSnapshot: refusing to apply', warnings.fatal);
|
|
5649
|
+
return {
|
|
5650
|
+
applied: false,
|
|
5651
|
+
warnings: warnings
|
|
5652
|
+
};
|
|
5653
|
+
}
|
|
5654
|
+
// 1. Filters (also clears search; replays a fresh fetch).
|
|
5655
|
+
if (snapshot.filters) dispatch({
|
|
5656
|
+
type: 'BATCH_ACTIONS',
|
|
5657
|
+
actions: [
|
|
5658
|
+
{
|
|
5659
|
+
type: 'GRAMENE_SEARCH_CLEARED'
|
|
5660
|
+
},
|
|
5661
|
+
{
|
|
5662
|
+
type: 'GRAMENE_FILTERS_REPLACED',
|
|
5663
|
+
payload: $472fe3745b238881$var$cloneForDispatch(snapshot.filters)
|
|
5664
|
+
}
|
|
5665
|
+
]
|
|
5666
|
+
});
|
|
5667
|
+
// 2. Views.
|
|
5668
|
+
if (snapshot.views) dispatch({
|
|
5669
|
+
type: 'GRAMENE_VIEWS_REPLACED',
|
|
5670
|
+
payload: {
|
|
5671
|
+
on: snapshot.views.on || [],
|
|
5672
|
+
touched: snapshot.views.touched || {}
|
|
5673
|
+
}
|
|
5674
|
+
});
|
|
5675
|
+
// 3. Genome subset, if present.
|
|
5676
|
+
if (snapshot.genomeSubset && Object.keys(snapshot.genomeSubset).length) dispatch({
|
|
5677
|
+
type: 'GRAMENE_GENOMES_UPDATED',
|
|
5678
|
+
payload: {
|
|
5679
|
+
...snapshot.genomeSubset
|
|
5680
|
+
}
|
|
5681
|
+
});
|
|
5682
|
+
// 4. Per-gene UI state (expandedDetail, fullscreen, homology viewer/
|
|
5683
|
+
// height, tbrowse view state). Replace wholesale via the uiViewState
|
|
5684
|
+
// bundle's bulk action.
|
|
5685
|
+
const byGene = {};
|
|
5686
|
+
if (Array.isArray(snapshot.expandedDetails)) for (const e of snapshot.expandedDetails){
|
|
5687
|
+
if (!e || !e.geneId) continue;
|
|
5688
|
+
byGene[e.geneId] = {
|
|
5689
|
+
expandedDetail: e.expandedDetail || null,
|
|
5690
|
+
fullscreen: !!e.fullscreen,
|
|
5691
|
+
homology: e.homology ? {
|
|
5692
|
+
...e.homology
|
|
5693
|
+
} : {},
|
|
5694
|
+
sequences: e.sequences ? {
|
|
5695
|
+
...e.sequences
|
|
5696
|
+
} : {},
|
|
5697
|
+
expression: e.expression ? {
|
|
5698
|
+
...e.expression
|
|
5699
|
+
} : {}
|
|
5700
|
+
};
|
|
5701
|
+
}
|
|
5702
|
+
dispatch({
|
|
5703
|
+
type: 'UI_VIEW_STATE_REPLACED',
|
|
5704
|
+
payload: {
|
|
5705
|
+
byGene: byGene
|
|
5706
|
+
}
|
|
5707
|
+
});
|
|
5708
|
+
// 5. Expression-visualization view config. Delegate to the exprViz bundle,
|
|
5709
|
+
// which flags each taxon for a re-fetch now that the restored filters/
|
|
5710
|
+
// genomes are in place. Done last so the re-fetch sees the live query.
|
|
5711
|
+
if (snapshot.exprViz && store.doApplyExprVizSnapshot) store.doApplyExprVizSnapshot(snapshot.exprViz);
|
|
5712
|
+
// 6. Ontology Enrichment view config. Setting the active taxon lets that
|
|
5713
|
+
// bundle's fetch reactor re-derive the enrichment for the restored
|
|
5714
|
+
// filters/genomes.
|
|
5715
|
+
if (snapshot.ontologyEnrichment && store.doApplyOntologyEnrichmentSnapshot) store.doApplyOntologyEnrichmentSnapshot(snapshot.ontologyEnrichment);
|
|
5716
|
+
return {
|
|
5717
|
+
applied: true,
|
|
5718
|
+
warnings: warnings
|
|
5719
|
+
};
|
|
5720
|
+
},
|
|
5721
|
+
// Helper exposed for the bootstrap path that wants to read warnings without
|
|
5722
|
+
// dispatching. Returns { fatal: <string|null>, unresolved: { ids/taxa/views/genes lists } }.
|
|
5723
|
+
doValidateViewSnapshot: (snapshot)=>()=>$472fe3745b238881$var$validateSnapshot(snapshot)
|
|
5724
|
+
};
|
|
5725
|
+
function $472fe3745b238881$var$createSnapshotSelector() {
|
|
5726
|
+
// We deliberately don't memoize with createSelector — snapshots are taken
|
|
5727
|
+
// on demand (Save button click, link generation) rather than on every
|
|
5728
|
+
// store tick, so a fresh build is fine and avoids stale-dep bugs.
|
|
5729
|
+
return (state)=>$472fe3745b238881$var$buildSnapshot(state);
|
|
5730
|
+
}
|
|
5731
|
+
function $472fe3745b238881$var$buildSnapshot(state) {
|
|
5732
|
+
const snap = {
|
|
5733
|
+
v: $472fe3745b238881$var$SCHEMA_VERSION,
|
|
5734
|
+
capturedAt: new Date().toISOString(),
|
|
5735
|
+
site: state.config && state.config.id || null
|
|
5736
|
+
};
|
|
5737
|
+
if (state.grameneFilters) {
|
|
5738
|
+
snap.filters = $472fe3745b238881$var$cleanFilters(state.grameneFilters);
|
|
5739
|
+
snap.searchPage = {
|
|
5740
|
+
offset: state.grameneFilters.searchOffset || 0,
|
|
5741
|
+
rows: state.grameneFilters.rows || 20
|
|
5742
|
+
};
|
|
5743
|
+
}
|
|
5744
|
+
if (state.grameneViews && Array.isArray(state.grameneViews.options)) snap.views = {
|
|
5745
|
+
on: state.grameneViews.options.filter((v)=>v && v.show === 'on').map((v)=>v.id),
|
|
5746
|
+
touched: {
|
|
5747
|
+
...state.grameneViews.touched || {}
|
|
5748
|
+
}
|
|
5749
|
+
};
|
|
5750
|
+
if (state.grameneGenomes && state.grameneGenomes.active) {
|
|
5751
|
+
const keys = Object.keys(state.grameneGenomes.active);
|
|
5752
|
+
if (keys.length) {
|
|
5753
|
+
snap.genomeSubset = {};
|
|
5754
|
+
for (const k of keys)snap.genomeSubset[k] = true;
|
|
5755
|
+
} else snap.genomeSubset = null;
|
|
5756
|
+
}
|
|
5757
|
+
if (state.uiViewState && state.uiViewState.byGene) snap.expandedDetails = Object.entries(state.uiViewState.byGene).filter(([, e])=>$472fe3745b238881$var$isMeaningfulGene(e)).map(([geneId, e])=>({
|
|
5758
|
+
geneId: geneId,
|
|
5759
|
+
expandedDetail: e.expandedDetail || null,
|
|
5760
|
+
fullscreen: !!e.fullscreen,
|
|
5761
|
+
homology: e.homology && Object.keys(e.homology).length ? {
|
|
5762
|
+
...e.homology
|
|
5763
|
+
} : undefined,
|
|
5764
|
+
sequences: e.sequences && Object.keys(e.sequences).length ? {
|
|
5765
|
+
...e.sequences
|
|
5766
|
+
} : undefined,
|
|
5767
|
+
expression: e.expression && Object.keys(e.expression).length ? {
|
|
5768
|
+
...e.expression
|
|
5769
|
+
} : undefined
|
|
5770
|
+
}));
|
|
5771
|
+
else snap.expandedDetails = [];
|
|
5772
|
+
// Expression-visualization view config. Capture only the selection/config
|
|
5773
|
+
// (active genome tab, per-taxon selected fields + column order, viz mode,
|
|
5774
|
+
// scale, brush ranges) — never the bulk fetched rows, which rehydrate via
|
|
5775
|
+
// the view's re-fetch on apply.
|
|
5776
|
+
if (state.exprViz) {
|
|
5777
|
+
const ev = state.exprViz;
|
|
5778
|
+
const byTaxon = {};
|
|
5779
|
+
for (const tid of Object.keys(ev.byTaxon || {})){
|
|
5780
|
+
const t = ev.byTaxon[tid];
|
|
5781
|
+
if (!t) continue;
|
|
5782
|
+
const selectedFields = Array.isArray(t.selectedFields) ? t.selectedFields : [];
|
|
5783
|
+
const brushes = t.brushes && Object.keys(t.brushes).length ? {
|
|
5784
|
+
...t.brushes
|
|
5785
|
+
} : undefined;
|
|
5786
|
+
const nonDefaultMode = t.vizMode && t.vizMode !== 'heatmap';
|
|
5787
|
+
const nonDefaultScale = t.scale && t.scale !== 'linear';
|
|
5788
|
+
if (selectedFields.length || brushes || nonDefaultMode || nonDefaultScale) byTaxon[tid] = {
|
|
5789
|
+
selectedFields: selectedFields,
|
|
5790
|
+
vizMode: t.vizMode || 'heatmap',
|
|
5791
|
+
scale: t.scale || 'linear',
|
|
5792
|
+
...brushes ? {
|
|
5793
|
+
brushes: brushes
|
|
5794
|
+
} : {}
|
|
5795
|
+
};
|
|
5796
|
+
}
|
|
5797
|
+
if (ev.activeTaxon || Object.keys(byTaxon).length) snap.exprViz = {
|
|
5798
|
+
activeTaxon: ev.activeTaxon || null,
|
|
5799
|
+
byTaxon: byTaxon
|
|
5800
|
+
};
|
|
5801
|
+
}
|
|
5802
|
+
// Ontology Enrichment view config: active genome + the analysis knobs
|
|
5803
|
+
// (significance cutoff, gene-set-size bounds, most-specific, ontology
|
|
5804
|
+
// section, term search, per-section sort). The enrichment facets re-fetch
|
|
5805
|
+
// on apply, so no bulk term data is carried.
|
|
5806
|
+
if (state.ontologyEnrichment) {
|
|
5807
|
+
const oe = state.ontologyEnrichment;
|
|
5808
|
+
const ui = oe.ui || {};
|
|
5809
|
+
const DEF = {
|
|
5810
|
+
pAdjCutoff: 0.05,
|
|
5811
|
+
minGSSize: 10,
|
|
5812
|
+
maxGSSize: 500,
|
|
5813
|
+
mostSpecific: false,
|
|
5814
|
+
ontology: 'all',
|
|
5815
|
+
search: ''
|
|
5816
|
+
};
|
|
5817
|
+
const hasSort = ui.sort && Object.keys(ui.sort).length;
|
|
5818
|
+
const uiChanged = Object.keys(DEF).some((k)=>ui[k] !== DEF[k]) || hasSort;
|
|
5819
|
+
if (oe.activeTaxon || uiChanged) snap.ontologyEnrichment = {
|
|
5820
|
+
activeTaxon: oe.activeTaxon || null,
|
|
5821
|
+
ui: {
|
|
5822
|
+
pAdjCutoff: ui.pAdjCutoff != null ? ui.pAdjCutoff : DEF.pAdjCutoff,
|
|
5823
|
+
minGSSize: ui.minGSSize != null ? ui.minGSSize : DEF.minGSSize,
|
|
5824
|
+
maxGSSize: ui.maxGSSize != null ? ui.maxGSSize : DEF.maxGSSize,
|
|
5825
|
+
mostSpecific: !!ui.mostSpecific,
|
|
5826
|
+
ontology: ui.ontology || DEF.ontology,
|
|
5827
|
+
search: ui.search || DEF.search,
|
|
5828
|
+
...hasSort ? {
|
|
5829
|
+
sort: {
|
|
5830
|
+
...ui.sort
|
|
5831
|
+
}
|
|
5832
|
+
} : {}
|
|
5833
|
+
}
|
|
5834
|
+
};
|
|
5835
|
+
}
|
|
5836
|
+
return snap;
|
|
5837
|
+
}
|
|
5838
|
+
function $472fe3745b238881$var$validateSnapshot(snapshot) {
|
|
5839
|
+
const warnings = {
|
|
5840
|
+
fatal: null,
|
|
5841
|
+
unresolved: {
|
|
5842
|
+
views: [],
|
|
5843
|
+
genes: [],
|
|
5844
|
+
taxa: []
|
|
5845
|
+
}
|
|
5846
|
+
};
|
|
5847
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
5848
|
+
warnings.fatal = 'snapshot is not an object';
|
|
5849
|
+
return warnings;
|
|
5850
|
+
}
|
|
5851
|
+
if (snapshot.v !== $472fe3745b238881$var$SCHEMA_VERSION) {
|
|
5852
|
+
warnings.fatal = `unsupported snapshot version ${snapshot.v} (expected ${$472fe3745b238881$var$SCHEMA_VERSION})`;
|
|
5853
|
+
return warnings;
|
|
5854
|
+
}
|
|
5855
|
+
// Strict-id checks happen later — they need the store's grameneMaps,
|
|
5856
|
+
// grameneTaxonomy, etc. which are async-loaded. The boot path will call
|
|
5857
|
+
// doValidateViewSnapshot again after those land. For now we just
|
|
5858
|
+
// structurally validate.
|
|
5859
|
+
return warnings;
|
|
5860
|
+
}
|
|
5861
|
+
// JSON.parse(JSON.stringify(x)) — bundles see a wholly fresh object so
|
|
5862
|
+
// reducers can't accidentally mutate the snapshot we're holding for retry.
|
|
5863
|
+
function $472fe3745b238881$var$cloneForDispatch(x) {
|
|
5864
|
+
return JSON.parse(JSON.stringify(x));
|
|
5865
|
+
}
|
|
5866
|
+
var $472fe3745b238881$export$2e2bcd8739ae039 = $472fe3745b238881$var$viewSnapshot;
|
|
5867
|
+
|
|
5868
|
+
|
|
5869
|
+
// Boot-time hydration of a shared view link.
|
|
5870
|
+
//
|
|
5871
|
+
// Entry points call `bootViewFromUrl(store)` once the store is created.
|
|
5872
|
+
// Public views resolve anonymously. For private views, the initial call
|
|
5873
|
+
// will 401; the Auth panel then re-invokes with `{user}` once Firebase has
|
|
5874
|
+
// emitted its first signed-in state, and the fetch retries with a Bearer
|
|
5875
|
+
// token. On success we apply the snapshot and strip `?view=` from the URL
|
|
5876
|
+
// so subsequent user actions don't sit under a stale shared-state URL.
|
|
5877
|
+
//
|
|
5878
|
+
// All errors are non-fatal — they surface in state.savedViews.fetchError
|
|
5879
|
+
// for any UI that wants to show them.
|
|
5880
|
+
const $c69c61828e36a328$var$PARAM = 'view';
|
|
5881
|
+
function $c69c61828e36a328$export$2e2bcd8739ae039(store, opts = {}) {
|
|
5882
|
+
if (typeof window === 'undefined') return Promise.resolve(null);
|
|
5883
|
+
const url = new URL(window.location.href);
|
|
5884
|
+
const hash = url.searchParams.get($c69c61828e36a328$var$PARAM);
|
|
5885
|
+
if (!hash) return Promise.resolve(null);
|
|
5886
|
+
const { user: user = null } = opts;
|
|
5887
|
+
return store.doFetchView({
|
|
5888
|
+
hash: hash,
|
|
5889
|
+
user: user
|
|
5890
|
+
}).then(({ snapshot: snapshot })=>{
|
|
5891
|
+
store.doApplyViewSnapshot(snapshot);
|
|
5892
|
+
url.searchParams.delete($c69c61828e36a328$var$PARAM);
|
|
5893
|
+
window.history.replaceState({}, '', url.toString());
|
|
5894
|
+
return {
|
|
5895
|
+
hash: hash,
|
|
5896
|
+
applied: true
|
|
5897
|
+
};
|
|
5898
|
+
}).catch((err)=>{
|
|
5899
|
+
// 401 here on the anonymous pass is expected for private views — the
|
|
5900
|
+
// Auth panel will retry once it has a user. Other errors (404, 5xx,
|
|
5901
|
+
// network) are reported and leave the param so a manual refresh can
|
|
5902
|
+
// retry too.
|
|
5903
|
+
console.warn('bootViewFromUrl:', err.message || err);
|
|
5904
|
+
return {
|
|
5905
|
+
hash: hash,
|
|
5906
|
+
applied: false,
|
|
5907
|
+
error: err.message || String(err)
|
|
5908
|
+
};
|
|
5909
|
+
});
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5912
|
+
|
|
5913
|
+
// Client for the saved-views API ({api}/saved_views — see gramene-swagger
|
|
5914
|
+
// phase 3b). Mirrors the gene_lists pattern: Firebase Bearer ID token,
|
|
5915
|
+
// hash-based addressing, public vs private scope.
|
|
5916
|
+
//
|
|
5917
|
+
// Action creators all return Promises so calling UI (modal, boot path,
|
|
5918
|
+
// list view) can await them and surface success/error inline.
|
|
5919
|
+
//
|
|
5920
|
+
// Dev-mode mock: set `window.__SAVED_VIEWS_MOCK__ = true` in the console
|
|
5921
|
+
// and saves/fetches go through localStorage instead of the network. Lets us
|
|
5922
|
+
// drive Phase 4 (UI) without waiting on Phase 3b (server). Production
|
|
5923
|
+
// builds never touch the mock unless the flag is set at runtime, so this
|
|
5924
|
+
// is safe to ship.
|
|
5925
|
+
const $85a9f6732ceb79db$var$STORAGE_PREFIX = 'gramene_saved_view_mock_v1::';
|
|
5926
|
+
const $85a9f6732ceb79db$var$initialState = {
|
|
5927
|
+
saving: false,
|
|
5928
|
+
saveError: null,
|
|
5929
|
+
lastSavedHash: null,
|
|
5930
|
+
fetching: false,
|
|
5931
|
+
fetchError: null,
|
|
5932
|
+
lastFetched: null,
|
|
5933
|
+
privateList: null,
|
|
5934
|
+
publicList: null,
|
|
5935
|
+
listError: null
|
|
5936
|
+
};
|
|
5937
|
+
const $85a9f6732ceb79db$var$savedViews = {
|
|
5938
|
+
name: 'savedViews',
|
|
5939
|
+
getReducer: ()=>(state = $85a9f6732ceb79db$var$initialState, { type: type, payload: payload })=>{
|
|
5940
|
+
switch(type){
|
|
5941
|
+
case 'SAVED_VIEW_SAVE_STARTED':
|
|
5942
|
+
return {
|
|
5943
|
+
...state,
|
|
5944
|
+
saving: true,
|
|
5945
|
+
saveError: null
|
|
5946
|
+
};
|
|
5947
|
+
case 'SAVED_VIEW_SAVE_SUCCEEDED':
|
|
5948
|
+
return {
|
|
5949
|
+
...state,
|
|
5950
|
+
saving: false,
|
|
5951
|
+
lastSavedHash: payload.hash
|
|
5952
|
+
};
|
|
5953
|
+
case 'SAVED_VIEW_SAVE_FAILED':
|
|
5954
|
+
return {
|
|
5955
|
+
...state,
|
|
5956
|
+
saving: false,
|
|
5957
|
+
saveError: payload.error
|
|
5958
|
+
};
|
|
5959
|
+
case 'SAVED_VIEW_FETCH_STARTED':
|
|
5960
|
+
return {
|
|
5961
|
+
...state,
|
|
5962
|
+
fetching: true,
|
|
5963
|
+
fetchError: null
|
|
5964
|
+
};
|
|
5965
|
+
case 'SAVED_VIEW_FETCH_SUCCEEDED':
|
|
5966
|
+
return {
|
|
5967
|
+
...state,
|
|
5968
|
+
fetching: false,
|
|
5969
|
+
lastFetched: payload
|
|
5970
|
+
};
|
|
5971
|
+
case 'SAVED_VIEW_FETCH_FAILED':
|
|
5972
|
+
return {
|
|
5973
|
+
...state,
|
|
5974
|
+
fetching: false,
|
|
5975
|
+
fetchError: payload.error
|
|
5976
|
+
};
|
|
5977
|
+
case 'SAVED_VIEW_LIST_RECEIVED':
|
|
5978
|
+
return {
|
|
5979
|
+
...state,
|
|
5980
|
+
[payload.kind === 'public' ? 'publicList' : 'privateList']: payload.rows,
|
|
5981
|
+
listError: null
|
|
5982
|
+
};
|
|
5983
|
+
case 'SAVED_VIEW_LIST_FAILED':
|
|
5984
|
+
return {
|
|
5985
|
+
...state,
|
|
5986
|
+
listError: payload.error
|
|
5987
|
+
};
|
|
5988
|
+
case 'SAVED_VIEW_RESET':
|
|
5989
|
+
return {
|
|
5990
|
+
...$85a9f6732ceb79db$var$initialState
|
|
5991
|
+
};
|
|
5992
|
+
default:
|
|
5993
|
+
return state;
|
|
5994
|
+
}
|
|
5995
|
+
},
|
|
5996
|
+
selectSavedViews: (state)=>state.savedViews,
|
|
5997
|
+
// POST a snapshot. Returns Promise<{hash, shareUrl}> on success.
|
|
5998
|
+
// `user` is a Firebase user object (must respond to .getIdToken()).
|
|
5999
|
+
doSaveView: ({ user: user, label: label, description: description, isPublic: isPublic })=>async ({ dispatch: dispatch, store: store })=>{
|
|
6000
|
+
dispatch({
|
|
6001
|
+
type: 'SAVED_VIEW_SAVE_STARTED'
|
|
6002
|
+
});
|
|
6003
|
+
try {
|
|
6004
|
+
const snapshot = store.selectViewSnapshot();
|
|
6005
|
+
const hash = await $85a9f6732ceb79db$var$computeContentHash(snapshot);
|
|
6006
|
+
const site = store.selectConfiguration && store.selectConfiguration().id || '';
|
|
6007
|
+
const token = user && user.getIdToken ? await user.getIdToken() : null;
|
|
6008
|
+
if (!token) throw new Error('Not signed in');
|
|
6009
|
+
const meta = {
|
|
6010
|
+
hash: hash,
|
|
6011
|
+
label: label,
|
|
6012
|
+
site: site,
|
|
6013
|
+
isPublic: !!isPublic,
|
|
6014
|
+
description: description || ''
|
|
6015
|
+
};
|
|
6016
|
+
if ($85a9f6732ceb79db$var$useMock()) $85a9f6732ceb79db$var$mockSave({
|
|
6017
|
+
...meta,
|
|
6018
|
+
state: snapshot,
|
|
6019
|
+
uid: user.uid || 'mock',
|
|
6020
|
+
createdAt: new Date().toISOString()
|
|
6021
|
+
});
|
|
6022
|
+
else {
|
|
6023
|
+
const api = store.selectGrameneAPI();
|
|
6024
|
+
const res = await fetch(`${api}/saved_views`, {
|
|
6025
|
+
method: 'POST',
|
|
6026
|
+
headers: {
|
|
6027
|
+
'Content-Type': 'application/json',
|
|
6028
|
+
Authorization: `Bearer ${token}`
|
|
6029
|
+
},
|
|
6030
|
+
body: JSON.stringify({
|
|
6031
|
+
...meta,
|
|
6032
|
+
state: snapshot
|
|
6033
|
+
})
|
|
6034
|
+
});
|
|
6035
|
+
if (!res.ok) throw new Error(`Save failed (${res.status})`);
|
|
6036
|
+
}
|
|
6037
|
+
const shareUrl = $85a9f6732ceb79db$var$buildShareUrl(hash);
|
|
6038
|
+
dispatch({
|
|
6039
|
+
type: 'SAVED_VIEW_SAVE_SUCCEEDED',
|
|
6040
|
+
payload: {
|
|
6041
|
+
hash: hash
|
|
6042
|
+
}
|
|
6043
|
+
});
|
|
6044
|
+
return {
|
|
6045
|
+
hash: hash,
|
|
6046
|
+
shareUrl: shareUrl
|
|
6047
|
+
};
|
|
6048
|
+
} catch (err) {
|
|
6049
|
+
dispatch({
|
|
6050
|
+
type: 'SAVED_VIEW_SAVE_FAILED',
|
|
6051
|
+
payload: {
|
|
6052
|
+
error: err.message || String(err)
|
|
6053
|
+
}
|
|
6054
|
+
});
|
|
6055
|
+
throw err;
|
|
6056
|
+
}
|
|
6057
|
+
},
|
|
6058
|
+
// GET a snapshot by share hash. Anonymous-OK for public views; token is
|
|
6059
|
+
// optional and only sent if the caller provides a user.
|
|
6060
|
+
// Returns Promise<{snapshot, meta}>.
|
|
6061
|
+
doFetchView: ({ hash: hash, user: user })=>async ({ dispatch: dispatch, store: store })=>{
|
|
6062
|
+
dispatch({
|
|
6063
|
+
type: 'SAVED_VIEW_FETCH_STARTED'
|
|
6064
|
+
});
|
|
6065
|
+
try {
|
|
6066
|
+
let row;
|
|
6067
|
+
if ($85a9f6732ceb79db$var$useMock()) {
|
|
6068
|
+
row = $85a9f6732ceb79db$var$mockFetch(hash);
|
|
6069
|
+
if (!row) throw new Error(`No saved view with hash ${hash}`);
|
|
6070
|
+
} else {
|
|
6071
|
+
const api = store.selectGrameneAPI();
|
|
6072
|
+
const headers = {
|
|
6073
|
+
Accept: 'application/json'
|
|
6074
|
+
};
|
|
6075
|
+
if (user && user.getIdToken) try {
|
|
6076
|
+
headers.Authorization = `Bearer ${await user.getIdToken()}`;
|
|
6077
|
+
} catch (_) {}
|
|
6078
|
+
const res = await fetch(`${api}/saved_views?hash=${encodeURIComponent(hash)}`, {
|
|
6079
|
+
headers: headers
|
|
6080
|
+
});
|
|
6081
|
+
if (res.status === 401) throw new Error('Sign in to load this private view.');
|
|
6082
|
+
if (res.status === 404) throw new Error('Saved view not found (it may have been deleted).');
|
|
6083
|
+
if (!res.ok) throw new Error(`Fetch failed (${res.status})`);
|
|
6084
|
+
row = await res.json();
|
|
6085
|
+
}
|
|
6086
|
+
const out = {
|
|
6087
|
+
hash: hash,
|
|
6088
|
+
snapshot: row.state,
|
|
6089
|
+
meta: {
|
|
6090
|
+
label: row.label,
|
|
6091
|
+
description: row.description || '',
|
|
6092
|
+
site: row.site,
|
|
6093
|
+
isPublic: !!row.isPublic,
|
|
6094
|
+
owner: row.owner || null,
|
|
6095
|
+
createdAt: row.createdAt || null
|
|
6096
|
+
}
|
|
6097
|
+
};
|
|
6098
|
+
dispatch({
|
|
6099
|
+
type: 'SAVED_VIEW_FETCH_SUCCEEDED',
|
|
6100
|
+
payload: out
|
|
6101
|
+
});
|
|
6102
|
+
return out;
|
|
6103
|
+
} catch (err) {
|
|
6104
|
+
dispatch({
|
|
6105
|
+
type: 'SAVED_VIEW_FETCH_FAILED',
|
|
6106
|
+
payload: {
|
|
6107
|
+
error: err.message || String(err)
|
|
6108
|
+
}
|
|
6109
|
+
});
|
|
6110
|
+
throw err;
|
|
6111
|
+
}
|
|
6112
|
+
},
|
|
6113
|
+
// List views for the current site. `scope` is 'public' (anonymous-OK)
|
|
6114
|
+
// or 'private' (requires `user`).
|
|
6115
|
+
doListSavedViews: ({ scope: scope, user: user })=>async ({ dispatch: dispatch, store: store })=>{
|
|
6116
|
+
try {
|
|
6117
|
+
const site = store.selectConfiguration && store.selectConfiguration().id || '';
|
|
6118
|
+
let rows;
|
|
6119
|
+
if ($85a9f6732ceb79db$var$useMock()) rows = $85a9f6732ceb79db$var$mockList({
|
|
6120
|
+
site: site,
|
|
6121
|
+
scope: scope,
|
|
6122
|
+
uid: user && user.uid
|
|
6123
|
+
});
|
|
6124
|
+
else {
|
|
6125
|
+
const api = store.selectGrameneAPI();
|
|
6126
|
+
const headers = {
|
|
6127
|
+
Accept: 'application/json'
|
|
6128
|
+
};
|
|
6129
|
+
if (scope === 'private') {
|
|
6130
|
+
if (!user || !user.getIdToken) throw new Error('Not signed in');
|
|
6131
|
+
headers.Authorization = `Bearer ${await user.getIdToken()}`;
|
|
6132
|
+
}
|
|
6133
|
+
const url = `${api}/saved_views?site=${encodeURIComponent(site)}&isPublic=${scope === 'public'}`;
|
|
6134
|
+
const res = await fetch(url, {
|
|
6135
|
+
headers: headers
|
|
6136
|
+
});
|
|
6137
|
+
if (!res.ok) throw new Error(`List failed (${res.status})`);
|
|
6138
|
+
rows = await res.json();
|
|
6139
|
+
}
|
|
6140
|
+
dispatch({
|
|
6141
|
+
type: 'SAVED_VIEW_LIST_RECEIVED',
|
|
6142
|
+
payload: {
|
|
6143
|
+
kind: scope,
|
|
6144
|
+
rows: rows
|
|
6145
|
+
}
|
|
6146
|
+
});
|
|
6147
|
+
return rows;
|
|
6148
|
+
} catch (err) {
|
|
6149
|
+
dispatch({
|
|
6150
|
+
type: 'SAVED_VIEW_LIST_FAILED',
|
|
6151
|
+
payload: {
|
|
6152
|
+
error: err.message || String(err)
|
|
6153
|
+
}
|
|
6154
|
+
});
|
|
6155
|
+
throw err;
|
|
6156
|
+
}
|
|
6157
|
+
},
|
|
6158
|
+
// PATCH label / isPublic on a view I own. Mirrors updateList.
|
|
6159
|
+
doUpdateSavedView: ({ viewId: viewId, user: user, label: label, isPublic: isPublic })=>async ({ store: store })=>{
|
|
6160
|
+
const updates = {};
|
|
6161
|
+
if (typeof label === 'string') updates.label = label;
|
|
6162
|
+
if (typeof isPublic === 'boolean') updates.isPublic = isPublic;
|
|
6163
|
+
if (!Object.keys(updates).length) return;
|
|
6164
|
+
if ($85a9f6732ceb79db$var$useMock()) {
|
|
6165
|
+
$85a9f6732ceb79db$var$mockUpdate(viewId, updates);
|
|
6166
|
+
return {
|
|
6167
|
+
viewId: viewId,
|
|
6168
|
+
updates: updates
|
|
6169
|
+
};
|
|
6170
|
+
}
|
|
6171
|
+
const token = await user.getIdToken();
|
|
6172
|
+
const api = store.selectGrameneAPI();
|
|
6173
|
+
const res = await fetch(`${api}/saved_views?viewId=${encodeURIComponent(viewId)}`, {
|
|
6174
|
+
method: 'PATCH',
|
|
6175
|
+
headers: {
|
|
6176
|
+
'Content-Type': 'application/json',
|
|
6177
|
+
Authorization: `Bearer ${token}`
|
|
6178
|
+
},
|
|
6179
|
+
body: JSON.stringify(updates)
|
|
6180
|
+
});
|
|
6181
|
+
if (!res.ok) throw new Error(`Update failed (${res.status})`);
|
|
6182
|
+
return res.json();
|
|
6183
|
+
},
|
|
6184
|
+
// DELETE one of my saved views. Mirrors deleteList.
|
|
6185
|
+
doDeleteSavedView: ({ viewId: viewId, user: user })=>async ({ store: store })=>{
|
|
6186
|
+
if ($85a9f6732ceb79db$var$useMock()) {
|
|
6187
|
+
$85a9f6732ceb79db$var$mockDelete(viewId);
|
|
6188
|
+
return {
|
|
6189
|
+
viewId: viewId
|
|
6190
|
+
};
|
|
6191
|
+
}
|
|
6192
|
+
const token = await user.getIdToken();
|
|
6193
|
+
const api = store.selectGrameneAPI();
|
|
6194
|
+
const res = await fetch(`${api}/saved_views?viewId=${encodeURIComponent(viewId)}`, {
|
|
6195
|
+
method: 'DELETE',
|
|
6196
|
+
headers: {
|
|
6197
|
+
Authorization: `Bearer ${token}`
|
|
6198
|
+
}
|
|
6199
|
+
});
|
|
6200
|
+
if (!res.ok) throw new Error(`Delete failed (${res.status})`);
|
|
6201
|
+
return res.json();
|
|
6202
|
+
},
|
|
6203
|
+
doResetSavedViewState: ()=>({ dispatch: dispatch })=>dispatch({
|
|
6204
|
+
type: 'SAVED_VIEW_RESET'
|
|
6205
|
+
}),
|
|
6206
|
+
// Connect-friendly wrapper around bootViewFromUrl. Auth.js calls this on
|
|
6207
|
+
// each auth-state emission, passing the current Firebase user (or null);
|
|
6208
|
+
// bootView.js no-ops when ?view= isn't in the URL, so this is cheap to
|
|
6209
|
+
// call repeatedly.
|
|
6210
|
+
doBootSharedView: ({ user: user } = {})=>({ store: store })=>{
|
|
6211
|
+
return (0, $c69c61828e36a328$export$2e2bcd8739ae039)(store, {
|
|
6212
|
+
user: user
|
|
6213
|
+
});
|
|
6214
|
+
}
|
|
6215
|
+
};
|
|
6216
|
+
// ── helpers ─────────────────────────────────────────────────────────────
|
|
6217
|
+
// Test/dev: either set the runtime-only flag (`window.__SAVED_VIEWS_MOCK__ = true`,
|
|
6218
|
+
// cleared on reload), or persist via localStorage (`localStorage.setItem(
|
|
6219
|
+
// '__SAVED_VIEWS_MOCK__', 'true')`, survives reloads — needed for the
|
|
6220
|
+
// share-link round-trip which navigates).
|
|
6221
|
+
function $85a9f6732ceb79db$var$useMock() {
|
|
6222
|
+
if (typeof window === 'undefined') return false;
|
|
6223
|
+
if (window.__SAVED_VIEWS_MOCK__) return true;
|
|
6224
|
+
try {
|
|
6225
|
+
return typeof localStorage !== 'undefined' && localStorage.getItem('__SAVED_VIEWS_MOCK__') === 'true';
|
|
6226
|
+
} catch (_) {
|
|
6227
|
+
return false;
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
function $85a9f6732ceb79db$var$buildShareUrl(hash) {
|
|
6231
|
+
if (typeof window === 'undefined') return `?view=${hash}`;
|
|
6232
|
+
const u = new URL(window.location.href);
|
|
6233
|
+
u.search = '';
|
|
6234
|
+
u.searchParams.set('view', hash);
|
|
6235
|
+
return u.toString();
|
|
6236
|
+
}
|
|
6237
|
+
// Content-addressable, ~72-bit short hash. The snapshot's `capturedAt` is
|
|
6238
|
+
// excluded from the hash input so two saves of the same view by the same
|
|
6239
|
+
// user produce the same hash (and the server's $setOnInsert preserves the
|
|
6240
|
+
// original createdAt).
|
|
6241
|
+
async function $85a9f6732ceb79db$var$computeContentHash(snapshot) {
|
|
6242
|
+
const forHash = {
|
|
6243
|
+
...snapshot,
|
|
6244
|
+
capturedAt: undefined
|
|
6245
|
+
};
|
|
6246
|
+
const text = $85a9f6732ceb79db$var$canonicalize(forHash);
|
|
6247
|
+
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
6248
|
+
const buf = new TextEncoder().encode(text);
|
|
6249
|
+
const digest = await crypto.subtle.digest('SHA-256', buf);
|
|
6250
|
+
const bytes = new Uint8Array(digest);
|
|
6251
|
+
let b64 = '';
|
|
6252
|
+
for(let i = 0; i < bytes.length; i++)b64 += String.fromCharCode(bytes[i]);
|
|
6253
|
+
return btoa(b64).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '').slice(0, 12);
|
|
6254
|
+
}
|
|
6255
|
+
// Last-resort fallback for environments without WebCrypto.
|
|
6256
|
+
let h = 0;
|
|
6257
|
+
for(let i = 0; i < text.length; i++)h = (h << 5) - h + text.charCodeAt(i) | 0;
|
|
6258
|
+
return ('h' + (h >>> 0).toString(36)).slice(0, 12);
|
|
6259
|
+
}
|
|
6260
|
+
// Stable stringify: sort object keys recursively so semantically-equivalent
|
|
6261
|
+
// snapshots hash identically.
|
|
6262
|
+
function $85a9f6732ceb79db$var$canonicalize(v) {
|
|
6263
|
+
if (v === null || typeof v !== 'object') return JSON.stringify(v);
|
|
6264
|
+
if (Array.isArray(v)) return '[' + v.map($85a9f6732ceb79db$var$canonicalize).join(',') + ']';
|
|
6265
|
+
const keys = Object.keys(v).filter((k)=>v[k] !== undefined).sort();
|
|
6266
|
+
return '{' + keys.map((k)=>JSON.stringify(k) + ':' + $85a9f6732ceb79db$var$canonicalize(v[k])).join(',') + '}';
|
|
6267
|
+
}
|
|
6268
|
+
// ── mock backend (localStorage) ─────────────────────────────────────────
|
|
6269
|
+
function $85a9f6732ceb79db$var$mockSave(row) {
|
|
6270
|
+
if (typeof localStorage === 'undefined') return;
|
|
6271
|
+
localStorage.setItem($85a9f6732ceb79db$var$STORAGE_PREFIX + row.hash, JSON.stringify({
|
|
6272
|
+
...row,
|
|
6273
|
+
_id: row.hash + ' ' + row.uid
|
|
6274
|
+
}));
|
|
6275
|
+
}
|
|
6276
|
+
function $85a9f6732ceb79db$var$mockFetch(hash) {
|
|
6277
|
+
if (typeof localStorage === 'undefined') return null;
|
|
6278
|
+
const raw = localStorage.getItem($85a9f6732ceb79db$var$STORAGE_PREFIX + hash);
|
|
6279
|
+
return raw ? JSON.parse(raw) : null;
|
|
6280
|
+
}
|
|
6281
|
+
function $85a9f6732ceb79db$var$mockList({ site: site, scope: scope, uid: uid }) {
|
|
6282
|
+
if (typeof localStorage === 'undefined') return [];
|
|
6283
|
+
const out = [];
|
|
6284
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
6285
|
+
const k = localStorage.key(i);
|
|
6286
|
+
if (!k || !k.startsWith($85a9f6732ceb79db$var$STORAGE_PREFIX)) continue;
|
|
6287
|
+
const row = JSON.parse(localStorage.getItem(k));
|
|
6288
|
+
if (row.site !== site) continue;
|
|
6289
|
+
if (scope === 'public' && !row.isPublic) continue;
|
|
6290
|
+
if (scope === 'private' && (row.isPublic || row.uid !== uid)) continue;
|
|
6291
|
+
out.push(row);
|
|
6292
|
+
}
|
|
6293
|
+
return out.sort((a, b)=>(b.createdAt || '').localeCompare(a.createdAt || ''));
|
|
6294
|
+
}
|
|
6295
|
+
function $85a9f6732ceb79db$var$mockUpdate(viewId, updates) {
|
|
6296
|
+
if (typeof localStorage === 'undefined') return;
|
|
6297
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
6298
|
+
const k = localStorage.key(i);
|
|
6299
|
+
if (!k || !k.startsWith($85a9f6732ceb79db$var$STORAGE_PREFIX)) continue;
|
|
6300
|
+
const row = JSON.parse(localStorage.getItem(k));
|
|
6301
|
+
if (row._id === viewId) {
|
|
6302
|
+
localStorage.setItem(k, JSON.stringify({
|
|
6303
|
+
...row,
|
|
6304
|
+
...updates
|
|
6305
|
+
}));
|
|
6306
|
+
return;
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6310
|
+
function $85a9f6732ceb79db$var$mockDelete(viewId) {
|
|
6311
|
+
if (typeof localStorage === 'undefined') return;
|
|
6312
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
6313
|
+
const k = localStorage.key(i);
|
|
6314
|
+
if (!k || !k.startsWith($85a9f6732ceb79db$var$STORAGE_PREFIX)) continue;
|
|
6315
|
+
const row = JSON.parse(localStorage.getItem(k));
|
|
6316
|
+
if (row._id === viewId) {
|
|
6317
|
+
localStorage.removeItem(k);
|
|
6318
|
+
return;
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
var $85a9f6732ceb79db$export$2e2bcd8739ae039 = $85a9f6732ceb79db$var$savedViews;
|
|
6323
|
+
|
|
6324
|
+
|
|
6325
|
+
var $5df6c55c1bef3469$export$2e2bcd8739ae039 = [
|
|
6326
|
+
...(0, $9d9aeaf9299e61a1$export$2e2bcd8739ae039),
|
|
6327
|
+
(0, $671312b287158a8a$export$2e2bcd8739ae039),
|
|
6328
|
+
(0, $af4441dd29af05df$export$2e2bcd8739ae039),
|
|
6329
|
+
(0, $24971af0a229e0e3$export$2e2bcd8739ae039),
|
|
6330
|
+
(0, $0d54502f6cafe273$export$2e2bcd8739ae039),
|
|
6331
|
+
(0, $0f839422d0d8c772$export$2e2bcd8739ae039),
|
|
6332
|
+
(0, $1508f5a42be6e7b5$export$2e2bcd8739ae039),
|
|
6333
|
+
(0, $c921a0d2b34aadb6$export$2e2bcd8739ae039),
|
|
6334
|
+
(0, $4f15cd8a7d970b18$export$2e2bcd8739ae039),
|
|
6335
|
+
(0, $d365d8c287ab0c94$export$2e2bcd8739ae039),
|
|
6336
|
+
(0, $7f865ea0feda21af$export$2e2bcd8739ae039),
|
|
6337
|
+
(0, $472fe3745b238881$export$2e2bcd8739ae039),
|
|
6338
|
+
(0, $85a9f6732ceb79db$export$2e2bcd8739ae039)
|
|
6339
|
+
];
|
|
6340
|
+
|
|
6341
|
+
|
|
6342
|
+
|
|
6343
|
+
|
|
6344
|
+
|
|
6345
|
+
const $2fec4872fbf7ebd2$var$Gene = ({ gene: gene })=>{
|
|
6346
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6347
|
+
className: "row",
|
|
6348
|
+
children: gene.id
|
|
6349
|
+
});
|
|
6350
|
+
};
|
|
6351
|
+
const $2fec4872fbf7ebd2$var$Genes = (results, rows, doChangeQuantity)=>{
|
|
6352
|
+
if (results && results.numFound > 0) {
|
|
6353
|
+
const moreButton = results.numFound > rows ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("button", {
|
|
6354
|
+
onClick: (e)=>doChangeQuantity('Genes', 20),
|
|
6355
|
+
children: "more"
|
|
6356
|
+
}) : '';
|
|
6357
|
+
const fewerButton = rows > 20 ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("button", {
|
|
6358
|
+
onClick: (e)=>doChangeQuantity('Genes', -20),
|
|
6359
|
+
children: "fewer"
|
|
6360
|
+
}) : '';
|
|
6361
|
+
const docsToShow = results.response.docs.slice(0, rows);
|
|
6362
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
6363
|
+
id: "Genes",
|
|
6364
|
+
className: "container mb40 anchor",
|
|
6365
|
+
children: [
|
|
6366
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6367
|
+
className: "fancy-title mb40",
|
|
6368
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("h4", {
|
|
6369
|
+
children: "Genes"
|
|
6370
|
+
})
|
|
6371
|
+
}),
|
|
6372
|
+
docsToShow.map((doc, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($2fec4872fbf7ebd2$var$Gene, {
|
|
6373
|
+
gene: doc
|
|
6374
|
+
}, idx)),
|
|
6375
|
+
fewerButton,
|
|
6376
|
+
moreButton
|
|
6377
|
+
]
|
|
6378
|
+
});
|
|
6379
|
+
}
|
|
6380
|
+
};
|
|
6381
|
+
const $2fec4872fbf7ebd2$var$Pathway = ({ pathway: pathway })=>{
|
|
6382
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6383
|
+
className: "row",
|
|
6384
|
+
children: pathway.name
|
|
6385
|
+
});
|
|
6386
|
+
};
|
|
6387
|
+
const $2fec4872fbf7ebd2$var$Pathways = (results)=>{
|
|
6388
|
+
if (results && results.numFound > 0) return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
6389
|
+
id: "Pathways",
|
|
6390
|
+
className: "container mb40 anchor",
|
|
6391
|
+
children: [
|
|
6392
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6393
|
+
className: "fancy-title",
|
|
6394
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("h4", {
|
|
6395
|
+
children: "Pathways"
|
|
6396
|
+
})
|
|
6397
|
+
}),
|
|
6398
|
+
results.pathways.map((doc, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($2fec4872fbf7ebd2$var$Pathway, {
|
|
6399
|
+
pathway: doc
|
|
6400
|
+
}, idx))
|
|
6401
|
+
]
|
|
6402
|
+
});
|
|
6403
|
+
};
|
|
6404
|
+
const $2fec4872fbf7ebd2$var$Domain = ({ domain: domain })=>{
|
|
6405
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6406
|
+
className: "row",
|
|
6407
|
+
children: domain.id
|
|
6408
|
+
});
|
|
6409
|
+
};
|
|
6410
|
+
const $2fec4872fbf7ebd2$var$Domains = (results)=>{
|
|
6411
|
+
if (results && results.numFound > 0) return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
6412
|
+
id: "Domains",
|
|
6413
|
+
className: "container mb40 anchor",
|
|
6414
|
+
children: [
|
|
6415
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6416
|
+
className: "fancy-title mb40",
|
|
6417
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("h4", {
|
|
6418
|
+
children: "Domains"
|
|
6419
|
+
})
|
|
6420
|
+
}),
|
|
6421
|
+
results.domains.map((doc, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($2fec4872fbf7ebd2$var$Domain, {
|
|
6422
|
+
domain: doc
|
|
6423
|
+
}, idx))
|
|
6424
|
+
]
|
|
6425
|
+
});
|
|
6426
|
+
};
|
|
6427
|
+
const $2fec4872fbf7ebd2$var$Taxon = ({ taxon: taxon })=>{
|
|
6428
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6429
|
+
className: "row",
|
|
6430
|
+
children: taxon.id
|
|
6431
|
+
});
|
|
6432
|
+
};
|
|
6433
|
+
const $2fec4872fbf7ebd2$var$Species = (results)=>{
|
|
6434
|
+
if (results && results.numFound > 0) return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
6435
|
+
id: "Species",
|
|
6436
|
+
className: "container mb40 anchor",
|
|
6437
|
+
children: [
|
|
6438
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6439
|
+
className: "fancy-title mb40",
|
|
6440
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("h4", {
|
|
6441
|
+
children: "Species"
|
|
6442
|
+
})
|
|
6443
|
+
}),
|
|
6444
|
+
results.taxonomy.map((doc, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($2fec4872fbf7ebd2$var$Taxon, {
|
|
6445
|
+
taxon: doc
|
|
6446
|
+
}, idx))
|
|
6447
|
+
]
|
|
6448
|
+
});
|
|
6449
|
+
};
|
|
6450
|
+
const $2fec4872fbf7ebd2$var$ResultList = ({ grameneGenes: grameneGenes, grameneDomains: grameneDomains, gramenePathways: gramenePathways, grameneTaxonomy: grameneTaxonomy, searchUI: searchUI, searchUpdated: searchUpdated, doChangeQuantity: doChangeQuantity })=>{
|
|
6451
|
+
if (searchUI.Gramene) return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
6452
|
+
id: "gramene",
|
|
6453
|
+
className: "row",
|
|
6454
|
+
children: [
|
|
6455
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6456
|
+
className: "fancy-title pt50",
|
|
6457
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("h3", {
|
|
6458
|
+
children: "Gramene search results"
|
|
6459
|
+
})
|
|
6460
|
+
}),
|
|
6461
|
+
searchUI.Genes && $2fec4872fbf7ebd2$var$Genes(grameneGenes, searchUI.rows.Genes, doChangeQuantity),
|
|
6462
|
+
searchUI.Domains && $2fec4872fbf7ebd2$var$Domains(grameneDomains),
|
|
6463
|
+
searchUI.Pathways && $2fec4872fbf7ebd2$var$Pathways(gramenePathways),
|
|
6464
|
+
searchUI.Species && $2fec4872fbf7ebd2$var$Species(grameneTaxonomy)
|
|
5289
6465
|
]
|
|
5290
6466
|
});
|
|
5291
6467
|
else return null;
|
|
@@ -5582,6 +6758,225 @@ var $541b8b0d8c5501d2$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.conn
|
|
|
5582
6758
|
|
|
5583
6759
|
|
|
5584
6760
|
|
|
6761
|
+
// eFP Browser embed — was its own package (gramene-efp-browser@1.0.11),
|
|
6762
|
+
// absorbed here on 2026-05-29 to drop the unmaintained nwb/webpack-4 build
|
|
6763
|
+
// pipeline and remove a dependency that needed --openssl-legacy-provider.
|
|
6764
|
+
// All upstream URLs and species-specific id formatters preserved as-is.
|
|
6765
|
+
|
|
6766
|
+
|
|
6767
|
+
const $59a241f3912e631e$var$urls = {
|
|
6768
|
+
image: (genome, study, gene)=>`https://bar.utoronto.ca/api/efp_image/efp_${genome}/${study}/Absolute/${gene}`,
|
|
6769
|
+
app: (genome, study, gene)=>`https://bar.utoronto.ca/efp_${genome}/cgi-bin/efpWeb.cgi?dataSource=${study}&mode=Absolute&primaryGene=${gene}`,
|
|
6770
|
+
studies: (genome)=>`https://bar.utoronto.ca/api/efp_image/get_efp_data_source/${genome}`,
|
|
6771
|
+
logo: 'https://bar.utoronto.ca/bbc_logo_small.gif',
|
|
6772
|
+
spinner: 'https://www.sorghumbase.org/static/images/dna_spinner.svg'
|
|
6773
|
+
};
|
|
6774
|
+
const $59a241f3912e631e$var$zmv4_re = /Zm00001d/;
|
|
6775
|
+
const $59a241f3912e631e$var$browsers = {
|
|
6776
|
+
sorghum_bicolor: {
|
|
6777
|
+
formatGene: (gene)=>gene._id.replace('SORBI_3', 'Sobic.'),
|
|
6778
|
+
genome: 'sorghum'
|
|
6779
|
+
},
|
|
6780
|
+
vitis_vinifera: {
|
|
6781
|
+
formatGene: (gene)=>gene._id,
|
|
6782
|
+
genome: 'grape'
|
|
6783
|
+
},
|
|
6784
|
+
arabidopsis_thaliana: {
|
|
6785
|
+
formatGene: (gene)=>gene._id,
|
|
6786
|
+
fixStudies: (studies)=>{
|
|
6787
|
+
studies = studies.filter((s)=>s.value !== 'Klepikova_Atlas');
|
|
6788
|
+
studies.unshift({
|
|
6789
|
+
value: 'Klepikova_Atlas',
|
|
6790
|
+
label: 'Klepikova Atlas'
|
|
6791
|
+
});
|
|
6792
|
+
return studies;
|
|
6793
|
+
},
|
|
6794
|
+
genome: 'arabidopsis'
|
|
6795
|
+
},
|
|
6796
|
+
zea_mays: {
|
|
6797
|
+
formatGene: (gene)=>{
|
|
6798
|
+
let id = gene._id;
|
|
6799
|
+
if (gene.synonyms) gene.synonyms.forEach((syn)=>{
|
|
6800
|
+
if ($59a241f3912e631e$var$zmv4_re.test(syn)) id = syn;
|
|
6801
|
+
});
|
|
6802
|
+
return id;
|
|
6803
|
+
},
|
|
6804
|
+
fixStudies: (studies)=>{
|
|
6805
|
+
studies = studies.filter((s)=>s.value !== 'Hoopes_et_al_Atlas' && s.value !== 'Hoopes_et_al_Stress');
|
|
6806
|
+
studies.unshift({
|
|
6807
|
+
value: 'Hoopes_et_al_Stress',
|
|
6808
|
+
label: 'Hoopes et. al., Stress'
|
|
6809
|
+
});
|
|
6810
|
+
studies.unshift({
|
|
6811
|
+
value: 'Hoopes_et_al_Atlas',
|
|
6812
|
+
label: 'Hoopes et. al., Atlas'
|
|
6813
|
+
});
|
|
6814
|
+
return studies;
|
|
6815
|
+
},
|
|
6816
|
+
genome: 'maize'
|
|
6817
|
+
},
|
|
6818
|
+
glycine_max: {
|
|
6819
|
+
formatGene: (gene)=>gene._id.replace('GLYMA_', 'Glyma.'),
|
|
6820
|
+
fixStudies: (studies)=>studies.filter((s)=>s.value !== 'soybean_senescence'),
|
|
6821
|
+
genome: 'soybean'
|
|
6822
|
+
},
|
|
6823
|
+
oryza_sativa: {
|
|
6824
|
+
genome: 'rice',
|
|
6825
|
+
formatGene: (gene)=>gene.MSU_id,
|
|
6826
|
+
fixStudies: (studies)=>{
|
|
6827
|
+
studies = studies.filter((s)=>s.value !== 'rice_rma' && s.value !== 'rice_mas');
|
|
6828
|
+
studies.unshift({
|
|
6829
|
+
value: 'rice_rma',
|
|
6830
|
+
label: 'rice rma'
|
|
6831
|
+
});
|
|
6832
|
+
studies.unshift({
|
|
6833
|
+
value: 'rice_mas',
|
|
6834
|
+
label: 'rice mas'
|
|
6835
|
+
});
|
|
6836
|
+
return studies;
|
|
6837
|
+
}
|
|
6838
|
+
}
|
|
6839
|
+
};
|
|
6840
|
+
// Subsites that refer to maize b73 as `zea_maysb73`.
|
|
6841
|
+
$59a241f3912e631e$var$browsers.zea_maysb73 = $59a241f3912e631e$var$browsers.zea_mays;
|
|
6842
|
+
const $59a241f3912e631e$var$ImageLoader = ({ url: url })=>{
|
|
6843
|
+
const [loading, setLoading] = (0, $gXNCa$react.useState)(true);
|
|
6844
|
+
const [error, setError] = (0, $gXNCa$react.useState)(false);
|
|
6845
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
6846
|
+
setLoading(true);
|
|
6847
|
+
setError(false);
|
|
6848
|
+
const image = new Image();
|
|
6849
|
+
image.src = url;
|
|
6850
|
+
image.onload = ()=>setLoading(false);
|
|
6851
|
+
image.onerror = ()=>{
|
|
6852
|
+
setLoading(false);
|
|
6853
|
+
setError(true);
|
|
6854
|
+
};
|
|
6855
|
+
}, [
|
|
6856
|
+
url
|
|
6857
|
+
]);
|
|
6858
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
6859
|
+
style: {
|
|
6860
|
+
padding: 20
|
|
6861
|
+
},
|
|
6862
|
+
children: [
|
|
6863
|
+
loading && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("img", {
|
|
6864
|
+
src: $59a241f3912e631e$var$urls.spinner,
|
|
6865
|
+
alt: "Loading\u2026"
|
|
6866
|
+
}),
|
|
6867
|
+
!loading && !error && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("img", {
|
|
6868
|
+
style: {
|
|
6869
|
+
maxWidth: '100%'
|
|
6870
|
+
},
|
|
6871
|
+
src: url,
|
|
6872
|
+
alt: "eFP browser output"
|
|
6873
|
+
}),
|
|
6874
|
+
error && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("p", {
|
|
6875
|
+
children: "Error: Failed to load image"
|
|
6876
|
+
})
|
|
6877
|
+
]
|
|
6878
|
+
});
|
|
6879
|
+
};
|
|
6880
|
+
class $59a241f3912e631e$export$2e2bcd8739ae039 extends (0, $gXNCa$react.Component) {
|
|
6881
|
+
constructor(props){
|
|
6882
|
+
super(props);
|
|
6883
|
+
this.state = {
|
|
6884
|
+
currentStudy: null
|
|
6885
|
+
};
|
|
6886
|
+
}
|
|
6887
|
+
getStudies(browser) {
|
|
6888
|
+
fetch($59a241f3912e631e$var$urls.studies(browser.genome)).then((res)=>res.json()).then((res)=>{
|
|
6889
|
+
if (res.wasSuccessful) {
|
|
6890
|
+
let studies = res.data.sort().map((v)=>({
|
|
6891
|
+
value: v,
|
|
6892
|
+
label: v.replace(/_/g, ' ')
|
|
6893
|
+
}));
|
|
6894
|
+
if (browser.fixStudies) studies = browser.fixStudies(studies);
|
|
6895
|
+
this.setState({
|
|
6896
|
+
studies: studies
|
|
6897
|
+
});
|
|
6898
|
+
}
|
|
6899
|
+
}).catch(console.error);
|
|
6900
|
+
}
|
|
6901
|
+
render() {
|
|
6902
|
+
const gene = this.props.gene;
|
|
6903
|
+
const browser = $59a241f3912e631e$var$browsers[gene.system_name];
|
|
6904
|
+
if (!browser) return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6905
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("h2", {
|
|
6906
|
+
children: [
|
|
6907
|
+
"Can't find eFP browser for ",
|
|
6908
|
+
gene.system_name
|
|
6909
|
+
]
|
|
6910
|
+
})
|
|
6911
|
+
});
|
|
6912
|
+
if (!this.state.studies) {
|
|
6913
|
+
this.getStudies(browser);
|
|
6914
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("img", {
|
|
6915
|
+
src: $59a241f3912e631e$var$urls.spinner,
|
|
6916
|
+
alt: "Loading\u2026"
|
|
6917
|
+
});
|
|
6918
|
+
}
|
|
6919
|
+
const efp_gene = browser.formatGene(gene);
|
|
6920
|
+
// `study`/`onStudyChange` let a host drive the selection (so a saved view
|
|
6921
|
+
// can restore it); fall back to local state when uncontrolled.
|
|
6922
|
+
const study = this.props.study || this.state.currentStudy || this.state.studies[0].value;
|
|
6923
|
+
const onSelect = (e)=>{
|
|
6924
|
+
if (this.props.onStudyChange) this.props.onStudyChange(e.target.value);
|
|
6925
|
+
else this.setState({
|
|
6926
|
+
currentStudy: e.target.value
|
|
6927
|
+
});
|
|
6928
|
+
};
|
|
6929
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
6930
|
+
style: {
|
|
6931
|
+
paddingTop: 10
|
|
6932
|
+
},
|
|
6933
|
+
children: [
|
|
6934
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("label", {
|
|
6935
|
+
style: {
|
|
6936
|
+
paddingLeft: 20,
|
|
6937
|
+
paddingRight: 10
|
|
6938
|
+
},
|
|
6939
|
+
children: "Select a study:"
|
|
6940
|
+
}),
|
|
6941
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("select", {
|
|
6942
|
+
value: study,
|
|
6943
|
+
onChange: onSelect,
|
|
6944
|
+
children: this.state.studies.map((s, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("option", {
|
|
6945
|
+
value: s.value,
|
|
6946
|
+
children: s.label
|
|
6947
|
+
}, idx))
|
|
6948
|
+
}),
|
|
6949
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("br", {}),
|
|
6950
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($59a241f3912e631e$var$ImageLoader, {
|
|
6951
|
+
url: $59a241f3912e631e$var$urls.image(browser.genome, study, efp_gene)
|
|
6952
|
+
}),
|
|
6953
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("a", {
|
|
6954
|
+
style: {
|
|
6955
|
+
paddingLeft: 100
|
|
6956
|
+
},
|
|
6957
|
+
href: $59a241f3912e631e$var$urls.app(browser.genome, study, efp_gene),
|
|
6958
|
+
target: "_blank",
|
|
6959
|
+
rel: "noreferrer",
|
|
6960
|
+
children: [
|
|
6961
|
+
"Powered by ",
|
|
6962
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("img", {
|
|
6963
|
+
src: $59a241f3912e631e$var$urls.logo,
|
|
6964
|
+
style: {
|
|
6965
|
+
maxWidth: 40
|
|
6966
|
+
},
|
|
6967
|
+
alt: "BBC"
|
|
6968
|
+
}),
|
|
6969
|
+
" Webservices"
|
|
6970
|
+
]
|
|
6971
|
+
})
|
|
6972
|
+
]
|
|
6973
|
+
});
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6976
|
+
function $59a241f3912e631e$export$1bc0f3596ef2efe9(gene) {
|
|
6977
|
+
return $59a241f3912e631e$var$browsers.hasOwnProperty(gene.system_name);
|
|
6978
|
+
}
|
|
6979
|
+
|
|
5585
6980
|
|
|
5586
6981
|
function $9e29a4f60318db7a$var$DynamicIframe(props) {
|
|
5587
6982
|
// Create a ref for the iframe element
|
|
@@ -5606,15 +7001,43 @@ function $9e29a4f60318db7a$var$DynamicIframe(props) {
|
|
|
5606
7001
|
});
|
|
5607
7002
|
}
|
|
5608
7003
|
const $9e29a4f60318db7a$var$Detail = (props)=>{
|
|
5609
|
-
const
|
|
5610
|
-
const
|
|
7004
|
+
const geneId = props.searchResult.id;
|
|
7005
|
+
const gene = props.geneDocs[geneId];
|
|
7006
|
+
// User-selected view state (active sub-tab, chosen GXA experiment, chosen eFP
|
|
7007
|
+
// study) lives in the uiViewState bundle keyed by geneId, so the shareable-
|
|
7008
|
+
// views snapshot can round-trip it. Fetched data + the dev-only Local API
|
|
7009
|
+
// toggle stay in local state. Defaults match the old local-state initials.
|
|
7010
|
+
const expr = props.uiViewState && props.uiViewState.byGene[geneId] && props.uiViewState.byGene[geneId].expression || {};
|
|
7011
|
+
const activeTab = expr.activeTab || 'gene';
|
|
7012
|
+
const atlasExperiment = expr.atlasExperiment || null;
|
|
7013
|
+
const setActiveTab = (k)=>props.doSetExpressionState({
|
|
7014
|
+
geneId: geneId,
|
|
7015
|
+
patch: {
|
|
7016
|
+
activeTab: k
|
|
7017
|
+
}
|
|
7018
|
+
});
|
|
7019
|
+
const setAtlasExperiment = (v)=>props.doSetExpressionState({
|
|
7020
|
+
geneId: geneId,
|
|
7021
|
+
patch: {
|
|
7022
|
+
atlasExperiment: v
|
|
7023
|
+
}
|
|
7024
|
+
});
|
|
5611
7025
|
const [atlasExperimentList, setAtlasExperimentList] = (0, $gXNCa$react.useState)([]);
|
|
5612
7026
|
const [atlasFacets, setAtlasFacets] = (0, $gXNCa$react.useState)(null);
|
|
5613
7027
|
const [isLocal, setIsLocal] = (0, $gXNCa$react.useState)(false);
|
|
5614
|
-
const [activeTab, setActiveTab] = (0, $gXNCa$react.useState)('gene');
|
|
5615
7028
|
const handleLocalAPIChange = (event)=>{
|
|
5616
7029
|
setIsLocal(event.target.checked);
|
|
5617
7030
|
};
|
|
7031
|
+
// The expressionStudies resource is otherwise fetched only when a top-level
|
|
7032
|
+
// expression view (exprViz/expression/export) is on — but this per-gene
|
|
7033
|
+
// Expression detail also needs it (the Paralogs sub-tab's experiment list and
|
|
7034
|
+
// the atlasExperiment selection both derive from it). Self-fetch on mount so
|
|
7035
|
+
// opening the detail populates the studies even when no such view is enabled.
|
|
7036
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
7037
|
+
if (!props.expressionStudies && props.doFetchExpressionStudies) props.doFetchExpressionStudies();
|
|
7038
|
+
}, [
|
|
7039
|
+
props.expressionStudies
|
|
7040
|
+
]);
|
|
5618
7041
|
(0, $gXNCa$react.useEffect)(()=>{
|
|
5619
7042
|
if (!props.expressionStudies) return;
|
|
5620
7043
|
const tid = Math.floor(gene.taxon_id / 1000);
|
|
@@ -5637,10 +7060,15 @@ const $9e29a4f60318db7a$var$Detail = (props)=>{
|
|
|
5637
7060
|
});
|
|
5638
7061
|
setAtlasExperimentList(eList);
|
|
5639
7062
|
setAtlasFacets(facets);
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
7063
|
+
// Only pick a default experiment when the user (or a restored snapshot)
|
|
7064
|
+
// hasn't already chosen one — otherwise we'd clobber a saved selection
|
|
7065
|
+
// the moment the studies list loads.
|
|
7066
|
+
if (!atlasExperiment) {
|
|
7067
|
+
let refExp = eList.filter((e)=>e.isRef);
|
|
7068
|
+
if (refExp.length === 1) setAtlasExperiment(refExp[0]._id);
|
|
7069
|
+
else // no reference experiment - choose first
|
|
7070
|
+
setAtlasExperiment(eList[0]._id);
|
|
7071
|
+
}
|
|
5644
7072
|
}
|
|
5645
7073
|
}, [
|
|
5646
7074
|
props.expressionStudies
|
|
@@ -5648,8 +7076,14 @@ const $9e29a4f60318db7a$var$Detail = (props)=>{
|
|
|
5648
7076
|
let paralogs_url;
|
|
5649
7077
|
let gene_url = `https://dev.gramene.org/static/atlasWidget.html?genes=${gene.atlas_id || gene._id}&localAPI=${isLocal}`;
|
|
5650
7078
|
let paralogs = [];
|
|
5651
|
-
|
|
5652
|
-
|
|
7079
|
+
const haveParalogs = props.grameneParalogs && props.grameneParalogs[gene._id];
|
|
7080
|
+
if (haveParalogs) paralogs = props.grameneParalogs[gene._id];
|
|
7081
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
7082
|
+
if (!haveParalogs && gene.homology) props.doRequestParalogs(gene._id, gene.homology.supertree, gene.taxon_id);
|
|
7083
|
+
}, [
|
|
7084
|
+
gene._id,
|
|
7085
|
+
haveParalogs
|
|
7086
|
+
]);
|
|
5653
7087
|
// if (gene.homology && gene.homology.homologous_genes && gene.homology.homologous_genes.within_species_paralog) {
|
|
5654
7088
|
// paralogs = gene.homology.homologous_genes.within_species_paralog;
|
|
5655
7089
|
// }
|
|
@@ -5666,6 +7100,7 @@ const $9e29a4f60318db7a$var$Detail = (props)=>{
|
|
|
5666
7100
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Select, {
|
|
5667
7101
|
"aria-label": "experiment selector",
|
|
5668
7102
|
placeholder: "Select experiment",
|
|
7103
|
+
value: atlasExperiment || '',
|
|
5669
7104
|
onChange: (e)=>setAtlasExperiment(e.target.value),
|
|
5670
7105
|
children: atlasExperimentList.map((e, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("option", {
|
|
5671
7106
|
value: e._id,
|
|
@@ -5689,18 +7124,25 @@ const $9e29a4f60318db7a$var$Detail = (props)=>{
|
|
|
5689
7124
|
url: gene_url
|
|
5690
7125
|
})
|
|
5691
7126
|
}, "gxa"),
|
|
5692
|
-
(0, $
|
|
7127
|
+
(0, $59a241f3912e631e$export$1bc0f3596ef2efe9)(gene) && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Tab), {
|
|
5693
7128
|
tabClassName: "eFP",
|
|
5694
7129
|
eventKey: "eFP",
|
|
5695
7130
|
title: "eFP Browser",
|
|
5696
|
-
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0,
|
|
5697
|
-
gene: gene
|
|
7131
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $59a241f3912e631e$export$2e2bcd8739ae039), {
|
|
7132
|
+
gene: gene,
|
|
7133
|
+
study: expr.barStudy,
|
|
7134
|
+
onStudyChange: (v)=>props.doSetExpressionState({
|
|
7135
|
+
geneId: geneId,
|
|
7136
|
+
patch: {
|
|
7137
|
+
barStudy: v
|
|
7138
|
+
}
|
|
7139
|
+
})
|
|
5698
7140
|
})
|
|
5699
7141
|
}, "bar")
|
|
5700
7142
|
]
|
|
5701
7143
|
});
|
|
5702
7144
|
};
|
|
5703
|
-
var $9e29a4f60318db7a$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectGrameneParalogs', 'selectExpressionStudies', 'doRequestParalogs', //'doRequestParalogExpression',
|
|
7145
|
+
var $9e29a4f60318db7a$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectGrameneParalogs', 'selectExpressionStudies', 'selectUiViewState', 'doRequestParalogs', 'doFetchExpressionStudies', 'doSetExpressionState', //'doRequestParalogExpression',
|
|
5704
7146
|
$9e29a4f60318db7a$var$Detail);
|
|
5705
7147
|
|
|
5706
7148
|
|
|
@@ -5719,6 +7161,7 @@ $9e29a4f60318db7a$var$Detail);
|
|
|
5719
7161
|
|
|
5720
7162
|
|
|
5721
7163
|
|
|
7164
|
+
|
|
5722
7165
|
const $5c2c79352d3d7b81$export$ec6b54b688be1708 = ({ fullscreen: fullscreen, onExitFullscreen: onExitFullscreen, title: title, className: className, children: children })=>{
|
|
5723
7166
|
const [stableNode] = (0, $gXNCa$react.useState)(()=>typeof document !== 'undefined' ? document.createElement('div') : null);
|
|
5724
7167
|
const inlineRef = (0, $gXNCa$react.useRef)(null);
|
|
@@ -5936,20 +7379,140 @@ const $64fad37f770d2bfe$var$TBROWSE_ZONES = [
|
|
|
5936
7379
|
class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$react))).Component {
|
|
5937
7380
|
constructor(props){
|
|
5938
7381
|
super(props);
|
|
7382
|
+
// Async data caches stay in local state (per mount, per tree). The
|
|
7383
|
+
// user-controlled view state (viewer toggle, resize height, tbrowse
|
|
7384
|
+
// ViewState) is lifted into the uiViewState bundle, keyed by geneId.
|
|
7385
|
+
//
|
|
7386
|
+
// `*Status` fields track per-zone load lifecycle so the TBrowse
|
|
7387
|
+
// toolbar can render loading/error affordances via its zoneStatus
|
|
7388
|
+
// prop. They start `undefined` (no signal); flipped to 'loading'
|
|
7389
|
+
// when the fetch kicks off and 'ready' / 'error' on settle.
|
|
5939
7390
|
this.state = {
|
|
5940
|
-
viewer: 'treevis',
|
|
5941
7391
|
neighborhood: null,
|
|
5942
7392
|
neighborhoodTreeId: null,
|
|
7393
|
+
neighborhoodStatus: undefined,
|
|
5943
7394
|
geneStructures: null,
|
|
5944
7395
|
geneStructuresTreeId: null,
|
|
5945
|
-
|
|
7396
|
+
geneStructuresStatus: undefined
|
|
5946
7397
|
};
|
|
5947
7398
|
if (!props.geneDocs.hasOwnProperty(props.searchResult.id)) props.requestGene(props.searchResult.id);
|
|
5948
|
-
this.taxonomy = (0, ($parcel$interopDefault($gXNCa$
|
|
7399
|
+
this.taxonomy = (0, ($parcel$interopDefault($gXNCa$gramenetreesclientsrctaxonomy))).tree(Object.values(props.grameneTaxonomy));
|
|
7400
|
+
}
|
|
7401
|
+
// ----- uiViewState accessors (with sensible defaults) -----
|
|
7402
|
+
getGeneId() {
|
|
7403
|
+
return this.props.searchResult.id;
|
|
7404
|
+
}
|
|
7405
|
+
getHomologySlice() {
|
|
7406
|
+
const slice = this.props.uiViewState && this.props.uiViewState.byGene[this.getGeneId()];
|
|
7407
|
+
return slice && slice.homology || {};
|
|
7408
|
+
}
|
|
7409
|
+
getViewer() {
|
|
7410
|
+
return this.getHomologySlice().viewer || 'treevis';
|
|
7411
|
+
}
|
|
7412
|
+
getHeight() {
|
|
7413
|
+
const h = this.getHomologySlice().height;
|
|
7414
|
+
return typeof h === 'number' ? h : 600;
|
|
7415
|
+
}
|
|
7416
|
+
setViewer(viewer) {
|
|
7417
|
+
this.props.doSetHomologyViewer({
|
|
7418
|
+
geneId: this.getGeneId(),
|
|
7419
|
+
viewer: viewer
|
|
7420
|
+
});
|
|
7421
|
+
}
|
|
7422
|
+
setHeight(height) {
|
|
7423
|
+
this.props.doSetHomologyHeight({
|
|
7424
|
+
geneId: this.getGeneId(),
|
|
7425
|
+
height: height
|
|
7426
|
+
});
|
|
7427
|
+
}
|
|
7428
|
+
setTbrowseViewState(viewState) {
|
|
7429
|
+
this.props.doSetHomologyTbrowseViewState({
|
|
7430
|
+
geneId: this.getGeneId(),
|
|
7431
|
+
tbrowse: viewState
|
|
7432
|
+
});
|
|
7433
|
+
}
|
|
7434
|
+
// Seed the bundle with a pivot-computed initial tbrowse view state once the
|
|
7435
|
+
// tree data is available and the user is actually looking at the tbrowse
|
|
7436
|
+
// viewer. Called from lifecycle (not render) to avoid dispatching mid-render.
|
|
7437
|
+
maybeSeedTbrowseViewState() {
|
|
7438
|
+
if (this.getViewer() !== 'tbrowse') return;
|
|
7439
|
+
if (this.getHomologySlice().tbrowse) return;
|
|
7440
|
+
const id = this.getGeneId();
|
|
7441
|
+
if (!this.props.geneDocs.hasOwnProperty(id)) return;
|
|
7442
|
+
const gene = this.props.geneDocs[id];
|
|
7443
|
+
if (!gene.homology) return;
|
|
7444
|
+
const treeId = gene.homology.gene_tree.id;
|
|
7445
|
+
const raw = this.props.grameneTrees[treeId];
|
|
7446
|
+
if (!raw || !raw.taxon_id) return;
|
|
7447
|
+
const adapted = (0, $gXNCa$tbrowse.fromGrameneGenetree)([
|
|
7448
|
+
raw
|
|
7449
|
+
]);
|
|
7450
|
+
const pivot = (0, $gXNCa$tbrowse.computePivotState)(adapted.tree, gene._id);
|
|
7451
|
+
this.setTbrowseViewState({
|
|
7452
|
+
selectedNodeId: null,
|
|
7453
|
+
collapsedNodeIds: pivot ? pivot.collapsedNodeIds : [],
|
|
7454
|
+
prunedNodeIds: [],
|
|
7455
|
+
swappedNodeIds: pivot ? pivot.swappedNodeIds : [],
|
|
7456
|
+
compressedNodeIds: [],
|
|
7457
|
+
nodeOfInterestId: pivot ? pivot.targetId : null,
|
|
7458
|
+
// Use each zone's intended fr-share + initial visibility from its
|
|
7459
|
+
// definition (tree:30 / labels:20 / msa:50 / neighborhood:30 /
|
|
7460
|
+
// genome:32) instead of uniform 25 across the board. Gives the MSA
|
|
7461
|
+
// a wider initial pane and the tree a narrower one.
|
|
7462
|
+
//
|
|
7463
|
+
// `defaultVisible ?? true` matches tbrowse's own buildInitialViewState
|
|
7464
|
+
// logic — zones default to visible unless their definition opts out
|
|
7465
|
+
// (e.g. neighborhood and genome opt out so they only appear once
|
|
7466
|
+
// their async data lands; tbrowse's Layout auto-flips them on then).
|
|
7467
|
+
zones: $64fad37f770d2bfe$var$TBROWSE_ZONES.map((z)=>({
|
|
7468
|
+
id: z.id,
|
|
7469
|
+
width: z.defaultWidth,
|
|
7470
|
+
visible: z.defaultVisible ?? true
|
|
7471
|
+
})),
|
|
7472
|
+
zoneStates: {},
|
|
7473
|
+
search: null
|
|
7474
|
+
});
|
|
7475
|
+
}
|
|
7476
|
+
componentDidMount() {
|
|
7477
|
+
this.maybeSeedTbrowseViewState();
|
|
7478
|
+
this.maybeFetchTbrowseData();
|
|
7479
|
+
}
|
|
7480
|
+
componentDidUpdate() {
|
|
7481
|
+
this.maybeSeedTbrowseViewState();
|
|
7482
|
+
this.maybeFetchTbrowseData();
|
|
7483
|
+
}
|
|
7484
|
+
// Kick off the neighborhood + gene-structure fetches from lifecycle,
|
|
7485
|
+
// not render. Each fetch internally dedupes on `_*FetchedFor === treeId`
|
|
7486
|
+
// so calling on every commit is cheap. Gated on the user actually being
|
|
7487
|
+
// on the tbrowse viewer + the tree data being loaded — otherwise we'd
|
|
7488
|
+
// pay network cost for genes the user never looks at.
|
|
7489
|
+
maybeFetchTbrowseData() {
|
|
7490
|
+
if (this.getViewer() !== 'tbrowse') return;
|
|
7491
|
+
const id = this.getGeneId();
|
|
7492
|
+
if (!this.props.geneDocs.hasOwnProperty(id)) return;
|
|
7493
|
+
const gene = this.props.geneDocs[id];
|
|
7494
|
+
if (!gene.homology) return;
|
|
7495
|
+
const treeId = gene.homology.gene_tree.id;
|
|
7496
|
+
const raw = this.props.grameneTrees[treeId];
|
|
7497
|
+
if (!raw || !raw.taxon_id) return;
|
|
7498
|
+
// Lazily compute the adapted tbrowse data the same way renderTBrowse
|
|
7499
|
+
// does, so fetchGeneStructures has the leaf ids without rerunning
|
|
7500
|
+
// fromGrameneGenetree on every commit.
|
|
7501
|
+
if (this._tbrowseTreeId !== treeId) {
|
|
7502
|
+
this._tbrowseTreeId = treeId;
|
|
7503
|
+
this._tbrowseData = (0, $gXNCa$tbrowse.fromGrameneGenetree)([
|
|
7504
|
+
raw
|
|
7505
|
+
]);
|
|
7506
|
+
}
|
|
7507
|
+
this.fetchNeighborhood(treeId);
|
|
7508
|
+
this.fetchGeneStructures(treeId, this._tbrowseData.tree);
|
|
5949
7509
|
}
|
|
5950
7510
|
fetchNeighborhood(treeId) {
|
|
5951
7511
|
if (this._neighborhoodFetchedFor === treeId) return;
|
|
5952
7512
|
this._neighborhoodFetchedFor = treeId;
|
|
7513
|
+
this.setState({
|
|
7514
|
+
neighborhoodStatus: 'loading'
|
|
7515
|
+
});
|
|
5953
7516
|
const api = this.props.grameneAPI;
|
|
5954
7517
|
const url = new URL(`${api}/search`);
|
|
5955
7518
|
url.searchParams.set('fl', 'id,name,gene_tree,gene_idx,region,start,end,strand,biotype,system_name,description');
|
|
@@ -5967,10 +7530,16 @@ class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$
|
|
|
5967
7530
|
if (this._neighborhoodFetchedFor !== treeId) return;
|
|
5968
7531
|
this.setState({
|
|
5969
7532
|
neighborhood: (0, $gXNCa$tbrowse.fromGrameneNeighborhood)(json),
|
|
5970
|
-
neighborhoodTreeId: treeId
|
|
7533
|
+
neighborhoodTreeId: treeId,
|
|
7534
|
+
neighborhoodStatus: 'ready'
|
|
5971
7535
|
});
|
|
5972
7536
|
}).catch((err)=>{
|
|
5973
7537
|
console.warn('tbrowse neighborhood fetch failed:', err);
|
|
7538
|
+
// Surface the failure via the zoneStatus prop. Reset the
|
|
7539
|
+
// dedupe key so a re-mount or tree-change can retry.
|
|
7540
|
+
if (this._neighborhoodFetchedFor === treeId) this.setState({
|
|
7541
|
+
neighborhoodStatus: 'error'
|
|
7542
|
+
});
|
|
5974
7543
|
this._neighborhoodFetchedFor = null;
|
|
5975
7544
|
});
|
|
5976
7545
|
}
|
|
@@ -5979,6 +7548,9 @@ class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$
|
|
|
5979
7548
|
this._geneStructuresFetchedFor = treeId;
|
|
5980
7549
|
const ids = Object.values(tree.nodes).filter((n)=>n.isLeaf && n.geneId).map((n)=>n.geneId);
|
|
5981
7550
|
if (ids.length === 0) return;
|
|
7551
|
+
this.setState({
|
|
7552
|
+
geneStructuresStatus: 'loading'
|
|
7553
|
+
});
|
|
5982
7554
|
const api = this.props.grameneAPI;
|
|
5983
7555
|
const BATCH_SIZE = 50;
|
|
5984
7556
|
const batches = [];
|
|
@@ -6001,22 +7573,24 @@ class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$
|
|
|
6001
7573
|
const combined = [].concat(...results.map((r)=>Array.isArray(r) ? r : []));
|
|
6002
7574
|
this.setState({
|
|
6003
7575
|
geneStructures: (0, $gXNCa$tbrowse.fromGrameneGeneStructures)(combined),
|
|
6004
|
-
geneStructuresTreeId: treeId
|
|
7576
|
+
geneStructuresTreeId: treeId,
|
|
7577
|
+
geneStructuresStatus: 'ready'
|
|
6005
7578
|
});
|
|
6006
7579
|
}).catch((err)=>{
|
|
6007
7580
|
console.warn('tbrowse gene-structures fetch failed:', err);
|
|
7581
|
+
if (this._geneStructuresFetchedFor === treeId) this.setState({
|
|
7582
|
+
geneStructuresStatus: 'error'
|
|
7583
|
+
});
|
|
6008
7584
|
this._geneStructuresFetchedFor = null;
|
|
6009
7585
|
});
|
|
6010
7586
|
}
|
|
6011
7587
|
startResize(e) {
|
|
6012
7588
|
e.preventDefault();
|
|
6013
7589
|
const startY = e.clientY;
|
|
6014
|
-
const startHeight = this.
|
|
7590
|
+
const startHeight = this.getHeight();
|
|
6015
7591
|
const onMouseMove = (moveEvent)=>{
|
|
6016
7592
|
const newHeight = Math.max(200, startHeight + (moveEvent.clientY - startY));
|
|
6017
|
-
this.
|
|
6018
|
-
height: newHeight
|
|
6019
|
-
});
|
|
7593
|
+
this.setHeight(newHeight);
|
|
6020
7594
|
};
|
|
6021
7595
|
const onMouseUp = ()=>{
|
|
6022
7596
|
window.removeEventListener('mousemove', onMouseMove);
|
|
@@ -6039,7 +7613,7 @@ class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$
|
|
|
6039
7613
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6040
7614
|
className: "gene-genetree",
|
|
6041
7615
|
style: {
|
|
6042
|
-
height: this.
|
|
7616
|
+
height: this.getHeight(),
|
|
6043
7617
|
width: '100%'
|
|
6044
7618
|
},
|
|
6045
7619
|
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, ($parcel$interopDefault($gXNCa$gramenegenetreevis))), {
|
|
@@ -6061,41 +7635,36 @@ class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$
|
|
|
6061
7635
|
}
|
|
6062
7636
|
renderTBrowse() {
|
|
6063
7637
|
const treeId = this.gene.homology.gene_tree.id;
|
|
7638
|
+
// Adapted tbrowse data is computed lazily in maybeFetchTbrowseData
|
|
7639
|
+
// (called from lifecycle). If the user just flipped to tbrowse and
|
|
7640
|
+
// the lifecycle hasn't fired yet, compute it once here without
|
|
7641
|
+
// calling any fetches — those will fire from componentDidUpdate.
|
|
6064
7642
|
if (this._tbrowseTreeId !== treeId) {
|
|
6065
|
-
const raw = this.props.grameneTrees[treeId];
|
|
6066
|
-
const adapted = (0, $gXNCa$tbrowse.fromGrameneGenetree)([
|
|
6067
|
-
raw
|
|
6068
|
-
]);
|
|
6069
7643
|
this._tbrowseTreeId = treeId;
|
|
6070
|
-
this._tbrowseData =
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
this._tbrowseInitialViewState = {
|
|
6074
|
-
selectedNodeId: null,
|
|
6075
|
-
collapsedNodeIds: pivot ? pivot.collapsedNodeIds : [],
|
|
6076
|
-
prunedNodeIds: [],
|
|
6077
|
-
swappedNodeIds: pivot ? pivot.swappedNodeIds : [],
|
|
6078
|
-
compressedNodeIds: [],
|
|
6079
|
-
nodeOfInterestId: pivot ? pivot.targetId : null,
|
|
6080
|
-
zones: zoneIds.map((id)=>({
|
|
6081
|
-
id: id,
|
|
6082
|
-
width: 25,
|
|
6083
|
-
visible: true
|
|
6084
|
-
})),
|
|
6085
|
-
zoneStates: {},
|
|
6086
|
-
search: null
|
|
6087
|
-
};
|
|
7644
|
+
this._tbrowseData = (0, $gXNCa$tbrowse.fromGrameneGenetree)([
|
|
7645
|
+
this.props.grameneTrees[treeId]
|
|
7646
|
+
]);
|
|
6088
7647
|
}
|
|
6089
|
-
this.fetchNeighborhood(treeId);
|
|
6090
|
-
this.fetchGeneStructures(treeId, this._tbrowseData.tree);
|
|
6091
7648
|
const neighborhood = this.state.neighborhoodTreeId === treeId ? this.state.neighborhood : undefined;
|
|
6092
7649
|
const geneStructures = this.state.geneStructuresTreeId === treeId ? this.state.geneStructures : undefined;
|
|
7650
|
+
// Per-zone status for the TBrowse toolbar — pulses while a fetch
|
|
7651
|
+
// is in flight, turns red on failure. Tracked per-tree so a
|
|
7652
|
+
// tree-change resets any stale 'ready'/'error' from the prior gene.
|
|
7653
|
+
const zoneStatus = {
|
|
7654
|
+
neighborhood: this.state.neighborhoodTreeId === treeId ? this.state.neighborhoodStatus : this.state.neighborhoodStatus === 'loading' ? 'loading' : undefined,
|
|
7655
|
+
genome: this.state.geneStructuresTreeId === treeId ? this.state.geneStructuresStatus : this.state.geneStructuresStatus === 'loading' ? 'loading' : undefined
|
|
7656
|
+
};
|
|
7657
|
+
// Bundle-driven (controlled) view state. If we're rendering tbrowse before
|
|
7658
|
+
// componentDidMount/Update has seeded the bundle slice, skip this turn and
|
|
7659
|
+
// let the re-render with the seeded state do the work.
|
|
7660
|
+
const tbrowseVS = this.getHomologySlice().tbrowse;
|
|
7661
|
+
if (!tbrowseVS) return null;
|
|
6093
7662
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
6094
7663
|
children: [
|
|
6095
7664
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
6096
7665
|
className: "gene-genetree",
|
|
6097
7666
|
style: {
|
|
6098
|
-
height: this.
|
|
7667
|
+
height: this.getHeight(),
|
|
6099
7668
|
width: '100%'
|
|
6100
7669
|
},
|
|
6101
7670
|
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$tbrowse.TBrowse), {
|
|
@@ -6109,7 +7678,13 @@ class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$
|
|
|
6109
7678
|
geneStructures: geneStructures,
|
|
6110
7679
|
zones: $64fad37f770d2bfe$var$TBROWSE_ZONES,
|
|
6111
7680
|
nodeOfInterest: this.gene._id,
|
|
6112
|
-
|
|
7681
|
+
viewState: tbrowseVS,
|
|
7682
|
+
onViewStateChange: (next)=>this.setTbrowseViewState(next),
|
|
7683
|
+
defaultOpenSections: {
|
|
7684
|
+
zones: true,
|
|
7685
|
+
search: true
|
|
7686
|
+
},
|
|
7687
|
+
zoneStatus: zoneStatus
|
|
6113
7688
|
})
|
|
6114
7689
|
}),
|
|
6115
7690
|
this.renderResizeHandle()
|
|
@@ -6117,12 +7692,10 @@ class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$
|
|
|
6117
7692
|
});
|
|
6118
7693
|
}
|
|
6119
7694
|
renderViewerToggle() {
|
|
6120
|
-
const
|
|
7695
|
+
const viewer = this.getViewer();
|
|
6121
7696
|
const btn = (id, label)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("button", {
|
|
6122
7697
|
type: "button",
|
|
6123
|
-
onClick: ()=>this.
|
|
6124
|
-
viewer: id
|
|
6125
|
-
}),
|
|
7698
|
+
onClick: ()=>this.setViewer(id),
|
|
6126
7699
|
style: {
|
|
6127
7700
|
padding: '4px 10px',
|
|
6128
7701
|
marginRight: 4,
|
|
@@ -6251,36 +7824,22 @@ class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$
|
|
|
6251
7824
|
else {
|
|
6252
7825
|
const tree = this.props.grameneTrees[treeId];
|
|
6253
7826
|
if (tree.hasOwnProperty('taxon_id')) {
|
|
6254
|
-
this.tree = (0, ($parcel$interopDefault($gXNCa$
|
|
7827
|
+
this.tree = (0, ($parcel$interopDefault($gXNCa$gramenetreesclientsrcgenetree))).tree([
|
|
6255
7828
|
this.props.grameneTrees[treeId]
|
|
6256
7829
|
]);
|
|
6257
7830
|
this.orthologs = this.orthologList();
|
|
6258
7831
|
this.paralogs = this.paralogList();
|
|
6259
7832
|
}
|
|
6260
7833
|
}
|
|
6261
|
-
let flagged = 0;
|
|
6262
|
-
// if (this.props.curation && this.props.curation.taxa.hasOwnProperty(this.gene.taxon_id)) {
|
|
6263
|
-
// flagged = this.props.curatedGenes && this.props.curatedGenes[id] ? this.props.curatedGenes[id].flagged : 0;
|
|
6264
|
-
// }
|
|
6265
7834
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $5c2c79352d3d7b81$export$47eb42ff093406d4), {
|
|
6266
7835
|
children: [
|
|
6267
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.
|
|
6268
|
-
children:
|
|
6269
|
-
"This phylogram shows the relationships between this gene and others similar to it, as determined by Ensembl Compara.",
|
|
6270
|
-
flagged > 1 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Alert), {
|
|
6271
|
-
variant: 'warning',
|
|
6272
|
-
children: [
|
|
6273
|
-
"This gene was flagged for potential gene structural annotation issues by ",
|
|
6274
|
-
flagged,
|
|
6275
|
-
" curators"
|
|
6276
|
-
]
|
|
6277
|
-
})
|
|
6278
|
-
]
|
|
7836
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $5c2c79352d3d7b81$export$393edc798c47379d), {
|
|
7837
|
+
children: "This phylogram shows the relationships between this gene and others similar to it, as determined by Ensembl Compara."
|
|
6279
7838
|
}, "description"),
|
|
6280
7839
|
this.tree && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $5c2c79352d3d7b81$export$7c6e2c02157bb7d2), {
|
|
6281
7840
|
children: [
|
|
6282
7841
|
this.renderViewerToggle(),
|
|
6283
|
-
this.
|
|
7842
|
+
this.getViewer() === 'tbrowse' ? this.renderTBrowse() : this.renderTreeVis()
|
|
6284
7843
|
]
|
|
6285
7844
|
}, "content"),
|
|
6286
7845
|
this.tree && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $5c2c79352d3d7b81$export$900f1bfcd1cb6476), {
|
|
@@ -6293,7 +7852,7 @@ class $64fad37f770d2bfe$var$Homology extends (0, ($parcel$interopDefault($gXNCa$
|
|
|
6293
7852
|
});
|
|
6294
7853
|
}
|
|
6295
7854
|
}
|
|
6296
|
-
var $64fad37f770d2bfe$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectGrameneTaxonomy', 'selectGrameneTrees', 'selectGrameneGenomes', 'selectGrameneAPI', 'selectConfiguration', 'selectCuration', '
|
|
7855
|
+
var $64fad37f770d2bfe$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectGrameneTaxonomy', 'selectGrameneTrees', 'selectGrameneGenomes', 'selectGrameneAPI', 'selectConfiguration', 'selectCuration', 'selectUiViewState', 'doRequestGrameneTree', 'doSetHomologyViewer', 'doSetHomologyHeight', 'doSetHomologyTbrowseViewState', 'doAcceptGrameneSuggestion', 'doReplaceGrameneFilters', $64fad37f770d2bfe$var$Homology);
|
|
6297
7856
|
|
|
6298
7857
|
|
|
6299
7858
|
|
|
@@ -6784,7 +8343,7 @@ function $54c74a4689d5a778$var$buildIframeSrcDoc() {
|
|
|
6784
8343
|
class $54c74a4689d5a778$var$Pathways extends (0, ($parcel$interopDefault($gXNCa$react))).Component {
|
|
6785
8344
|
constructor(props){
|
|
6786
8345
|
super(props);
|
|
6787
|
-
this.taxonomy = (0, ($parcel$interopDefault($gXNCa$
|
|
8346
|
+
this.taxonomy = (0, ($parcel$interopDefault($gXNCa$gramenetreesclientsrctaxonomy))).tree(Object.values(props.grameneTaxonomy));
|
|
6788
8347
|
this.gene = props.geneDocs[props.searchResult.id];
|
|
6789
8348
|
this.iframeRef = /*#__PURE__*/ (0, ($parcel$interopDefault($gXNCa$react))).createRef();
|
|
6790
8349
|
// srcDoc is constant — the per-instance pathway/reaction state is
|
|
@@ -7195,25 +8754,45 @@ function $283508ffcf8a47c4$var$compareGermplasm(a, b) {
|
|
|
7195
8754
|
}
|
|
7196
8755
|
function $283508ffcf8a47c4$var$group_germplasm(gene, germplasmLUT, vep_obj) {
|
|
7197
8756
|
let accessionTable = [];
|
|
8757
|
+
let missingMetadata = 0;
|
|
7198
8758
|
Object.entries(vep_obj).forEach(([key, accessions])=>{
|
|
7199
8759
|
const parts = key.split("__");
|
|
7200
8760
|
if (parts[0] === "VEP") {
|
|
7201
|
-
if (parts[1] !== "merged")
|
|
7202
|
-
const
|
|
7203
|
-
const pop =
|
|
8761
|
+
if (parts[1] !== "merged") {
|
|
8762
|
+
const studyForSystem = (0, $49d5cbca2ec74b2f$export$428c2f647a2a7545)[parts[3]];
|
|
8763
|
+
const pop = studyForSystem && studyForSystem[parts[4]] || {
|
|
8764
|
+
label: `${parts[3]}/${parts[4]}`,
|
|
8765
|
+
type: parts[4]
|
|
8766
|
+
};
|
|
7204
8767
|
const conseq = parts[1].replaceAll("_", " ");
|
|
7205
8768
|
const status = parts[2] === "het" ? "heterozygous" : "homozygous";
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
8769
|
+
accessions.forEach((ens_id)=>{
|
|
8770
|
+
let germplasm;
|
|
8771
|
+
if (germplasmLUT && germplasmLUT.hasOwnProperty(ens_id)) germplasm = germplasmLUT[ens_id][0];
|
|
8772
|
+
else {
|
|
8773
|
+
// Fall back to a minimal record so the row is still rendered with
|
|
8774
|
+
// whatever info we have from the VEP fields alone.
|
|
8775
|
+
missingMetadata++;
|
|
8776
|
+
germplasm = {
|
|
8777
|
+
ens_id: ens_id,
|
|
8778
|
+
pub_id: ens_id,
|
|
8779
|
+
subpop: '?',
|
|
8780
|
+
stock_center: null,
|
|
8781
|
+
germplasm_dbid: null,
|
|
8782
|
+
pop_id: null
|
|
8783
|
+
};
|
|
8784
|
+
}
|
|
8785
|
+
accessionTable.push({
|
|
8786
|
+
key: [
|
|
8787
|
+
pop.label,
|
|
8788
|
+
conseq,
|
|
8789
|
+
status
|
|
8790
|
+
].join('%%%'),
|
|
8791
|
+
germplasm: germplasm,
|
|
8792
|
+
pop: pop
|
|
8793
|
+
});
|
|
8794
|
+
});
|
|
8795
|
+
}
|
|
7217
8796
|
}
|
|
7218
8797
|
});
|
|
7219
8798
|
// group accessionTable by key field
|
|
@@ -7247,6 +8826,8 @@ function $283508ffcf8a47c4$var$group_germplasm(gene, germplasmLUT, vep_obj) {
|
|
|
7247
8826
|
});
|
|
7248
8827
|
});
|
|
7249
8828
|
});
|
|
8829
|
+
grouped.missingMetadata = missingMetadata;
|
|
8830
|
+
grouped.totalAccessions = accessionTable.length;
|
|
7250
8831
|
return grouped;
|
|
7251
8832
|
}
|
|
7252
8833
|
const $283508ffcf8a47c4$var$THRESHOLD = 5;
|
|
@@ -7431,37 +9012,70 @@ const $283508ffcf8a47c4$var$GridWithGroups = ({ groups: groups, gene_id: gene_id
|
|
|
7431
9012
|
};
|
|
7432
9013
|
const $283508ffcf8a47c4$var$Detail = (props)=>{
|
|
7433
9014
|
const gene = props.geneDocs[props.searchResult.id];
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
}
|
|
7464
|
-
|
|
9015
|
+
const haveConsequences = props.grameneConsequences && props.grameneConsequences[gene._id];
|
|
9016
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
9017
|
+
if (!haveConsequences) props.doRequestVEP(gene._id);
|
|
9018
|
+
}, [
|
|
9019
|
+
gene._id,
|
|
9020
|
+
haveConsequences
|
|
9021
|
+
]);
|
|
9022
|
+
if (!haveConsequences) return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("pre", {
|
|
9023
|
+
children: "loading"
|
|
9024
|
+
});
|
|
9025
|
+
// Render even if grameneGermplasm hasn't loaded — we'll fall back to
|
|
9026
|
+
// VEP-only rows so the user sees something instead of a silent empty table.
|
|
9027
|
+
const germplasmLUT = props.grameneGermplasm || {};
|
|
9028
|
+
const groups = $283508ffcf8a47c4$var$group_germplasm(gene, germplasmLUT, props.grameneConsequences[gene._id]);
|
|
9029
|
+
const { missingMetadata: missingMetadata, totalAccessions: totalAccessions } = groups;
|
|
9030
|
+
let notice = null;
|
|
9031
|
+
if (totalAccessions === 0) notice = /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
9032
|
+
className: "alert alert-warning",
|
|
9033
|
+
style: {
|
|
9034
|
+
padding: '8px',
|
|
9035
|
+
marginTop: '8px'
|
|
9036
|
+
},
|
|
9037
|
+
children: "VEP results were found for this gene but could not be grouped into accession-level rows."
|
|
9038
|
+
});
|
|
9039
|
+
else if (missingMetadata > 0) notice = /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
9040
|
+
className: "alert alert-info",
|
|
9041
|
+
style: {
|
|
9042
|
+
padding: '8px',
|
|
9043
|
+
marginTop: '8px'
|
|
9044
|
+
},
|
|
9045
|
+
children: [
|
|
9046
|
+
"Germplasm metadata could not be found for ",
|
|
9047
|
+
missingMetadata,
|
|
9048
|
+
" of ",
|
|
9049
|
+
totalAccessions,
|
|
9050
|
+
" accession",
|
|
9051
|
+
totalAccessions === 1 ? '' : 's',
|
|
9052
|
+
". Affected rows show the raw accession id without stock-center links or subpopulation info."
|
|
9053
|
+
]
|
|
9054
|
+
});
|
|
9055
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
9056
|
+
children: [
|
|
9057
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("h5", {
|
|
9058
|
+
children: "Predicted loss-of-function alleles were detected in these germplasm."
|
|
9059
|
+
}),
|
|
9060
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
9061
|
+
children: [
|
|
9062
|
+
"Explore other variants within this gene in the ",
|
|
9063
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("a", {
|
|
9064
|
+
target: "_blank",
|
|
9065
|
+
href: `${props.configuration.ensemblURL}/${gene.system_name}/Gene/Variation_Gene/Image?db=core;g=${props.searchResult.id}`,
|
|
9066
|
+
children: "Variant image"
|
|
9067
|
+
}),
|
|
9068
|
+
" page in the Ensembl genome browser."
|
|
9069
|
+
]
|
|
9070
|
+
}),
|
|
9071
|
+
notice,
|
|
9072
|
+
totalAccessions > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($283508ffcf8a47c4$var$GridWithGroups, {
|
|
9073
|
+
groups: groups,
|
|
9074
|
+
gene_id: gene._id,
|
|
9075
|
+
doGrin: !props.configuration.hasOwnProperty('noGRIN')
|
|
9076
|
+
})
|
|
9077
|
+
]
|
|
9078
|
+
});
|
|
7465
9079
|
};
|
|
7466
9080
|
var $283508ffcf8a47c4$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectConfiguration', 'selectGrameneConsequences', 'selectGrameneGermplasm', 'doRequestVEP', $283508ffcf8a47c4$var$Detail);
|
|
7467
9081
|
|
|
@@ -7984,22 +9598,55 @@ const $527ebc19dc92444d$var$buildId = (gene, geneSeq, up, down)=>{
|
|
|
7984
9598
|
return `${geneSeq.genome}|${gene._id}|${gene.location.region}:${gs}..${ge} ${extras.join('|')}`;
|
|
7985
9599
|
};
|
|
7986
9600
|
const $527ebc19dc92444d$var$Detail = (props)=>{
|
|
7987
|
-
const
|
|
7988
|
-
const
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
9601
|
+
const geneId = props.searchResult.id;
|
|
9602
|
+
const gene = props.geneDocs[geneId];
|
|
9603
|
+
// Sub-tab + selected isoform + flanking lengths live in the uiViewState
|
|
9604
|
+
// bundle (keyed by geneId) so the shareable-views snapshot can round-trip
|
|
9605
|
+
// them. Defaults match the old local-state initial values.
|
|
9606
|
+
const slice = props.uiViewState && props.uiViewState.byGene[geneId] && props.uiViewState.byGene[geneId].sequences || {};
|
|
9607
|
+
const tab = slice.tab || 'dna';
|
|
9608
|
+
const tid = slice.tid || gene.gene_structure.canonical_transcript;
|
|
9609
|
+
const upstream = slice.upstream || 0;
|
|
9610
|
+
const downstream = slice.downstream || 0;
|
|
9611
|
+
const setTab = (k)=>props.doSetSequencesState({
|
|
9612
|
+
geneId: geneId,
|
|
9613
|
+
patch: {
|
|
9614
|
+
tab: k
|
|
9615
|
+
}
|
|
9616
|
+
});
|
|
9617
|
+
const setTid = (v)=>props.doSetSequencesState({
|
|
9618
|
+
geneId: geneId,
|
|
9619
|
+
patch: {
|
|
9620
|
+
tid: v
|
|
9621
|
+
}
|
|
9622
|
+
});
|
|
9623
|
+
const setUpstream = (v)=>props.doSetSequencesState({
|
|
9624
|
+
geneId: geneId,
|
|
9625
|
+
patch: {
|
|
9626
|
+
upstream: +v
|
|
9627
|
+
}
|
|
9628
|
+
});
|
|
9629
|
+
const setDownstream = (v)=>props.doSetSequencesState({
|
|
9630
|
+
geneId: geneId,
|
|
9631
|
+
patch: {
|
|
9632
|
+
downstream: +v
|
|
9633
|
+
}
|
|
9634
|
+
});
|
|
7992
9635
|
let geneSeq;
|
|
7993
9636
|
let rnaSeq;
|
|
7994
9637
|
let pepSeq;
|
|
7995
|
-
|
|
9638
|
+
// The *_SEQUENCE_REQUESTED reducer inserts a `{}` placeholder before the
|
|
9639
|
+
// fetch resolves, so a key-only existence check passes while the body is
|
|
9640
|
+
// still empty — leading to `seq.substring` of undefined downstream. Gate
|
|
9641
|
+
// on the actual payload field instead.
|
|
9642
|
+
if (props.geneSequences && props.geneSequences[gene._id] && props.geneSequences[gene._id].seq) geneSeq = props.geneSequences[gene._id];
|
|
7996
9643
|
else {
|
|
7997
9644
|
props.doRequestGeneSequence(gene);
|
|
7998
9645
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("pre", {
|
|
7999
9646
|
children: "loading"
|
|
8000
9647
|
});
|
|
8001
9648
|
}
|
|
8002
|
-
if (props.rnaSequences && props.rnaSequences[tid]) rnaSeq = props.rnaSequences[tid];
|
|
9649
|
+
if (props.rnaSequences && props.rnaSequences[tid] && props.rnaSequences[tid].seq) rnaSeq = props.rnaSequences[tid];
|
|
8003
9650
|
else {
|
|
8004
9651
|
props.doRequestRnaSequence(tid, gene);
|
|
8005
9652
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("pre", {
|
|
@@ -8012,7 +9659,7 @@ const $527ebc19dc92444d$var$Detail = (props)=>{
|
|
|
8012
9659
|
let tl_id;
|
|
8013
9660
|
if (transcript.translation) {
|
|
8014
9661
|
tl_id = transcript.translation.id;
|
|
8015
|
-
if (props.pepSequences && props.pepSequences[tl_id]) pepSeq = props.pepSequences[tl_id];
|
|
9662
|
+
if (props.pepSequences && props.pepSequences[tl_id] && props.pepSequences[tl_id].seq) pepSeq = props.pepSequences[tl_id];
|
|
8016
9663
|
else {
|
|
8017
9664
|
props.doRequestPepSequence(tl_id, gene);
|
|
8018
9665
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("pre", {
|
|
@@ -8229,7 +9876,7 @@ const $527ebc19dc92444d$var$Detail = (props)=>{
|
|
|
8229
9876
|
]
|
|
8230
9877
|
});
|
|
8231
9878
|
};
|
|
8232
|
-
var $527ebc19dc92444d$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectConfiguration', 'selectGeneSequences', 'selectRnaSequences', 'selectPepSequences', 'doRequestGeneSequence', 'doRequestRnaSequence', 'doRequestPepSequence', $527ebc19dc92444d$var$Detail);
|
|
9879
|
+
var $527ebc19dc92444d$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectConfiguration', 'selectGeneSequences', 'selectRnaSequences', 'selectPepSequences', 'selectUiViewState', 'doRequestGeneSequence', 'doRequestRnaSequence', 'doRequestPepSequence', 'doSetSequencesState', $527ebc19dc92444d$var$Detail);
|
|
8233
9880
|
|
|
8234
9881
|
|
|
8235
9882
|
|
|
@@ -8299,73 +9946,6 @@ const $6c5c4f90059875bf$var$PanLink = (props)=>{
|
|
|
8299
9946
|
})
|
|
8300
9947
|
});
|
|
8301
9948
|
};
|
|
8302
|
-
const $6c5c4f90059875bf$var$CurationComponent1 = ({ curation: curation })=>{
|
|
8303
|
-
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
8304
|
-
className: "gene-curation",
|
|
8305
|
-
children: [
|
|
8306
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
8307
|
-
children: "Curated gene structure"
|
|
8308
|
-
}),
|
|
8309
|
-
curation.okay > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
8310
|
-
className: "status",
|
|
8311
|
-
children: [
|
|
8312
|
-
"\u2714\uFE0F ",
|
|
8313
|
-
curation.okay
|
|
8314
|
-
]
|
|
8315
|
-
}),
|
|
8316
|
-
curation.flagged > -10 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
8317
|
-
className: "status",
|
|
8318
|
-
children: [
|
|
8319
|
-
"\u26A0\uFE0F ",
|
|
8320
|
-
curation.flagged
|
|
8321
|
-
]
|
|
8322
|
-
})
|
|
8323
|
-
]
|
|
8324
|
-
});
|
|
8325
|
-
};
|
|
8326
|
-
const $6c5c4f90059875bf$var$CurationComponent = ({ curation: curation, gene: gene })=>{
|
|
8327
|
-
const total = curation.okay + curation.flagged;
|
|
8328
|
-
const okayRatio = curation.okay / total;
|
|
8329
|
-
const flaggedRatio = curation.flagged / total;
|
|
8330
|
-
const tooltip = /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Tooltip), {
|
|
8331
|
-
id: "tooltip",
|
|
8332
|
-
children: [
|
|
8333
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
8334
|
-
children: "Gene structure"
|
|
8335
|
-
}),
|
|
8336
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("br", {}),
|
|
8337
|
-
"Okay: ",
|
|
8338
|
-
curation.okay,
|
|
8339
|
-
" \xa0 Flagged: ",
|
|
8340
|
-
curation.flagged
|
|
8341
|
-
]
|
|
8342
|
-
});
|
|
8343
|
-
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.OverlayTrigger), {
|
|
8344
|
-
placement: "left",
|
|
8345
|
-
overlay: tooltip,
|
|
8346
|
-
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("a", {
|
|
8347
|
-
className: "gene-curation",
|
|
8348
|
-
target: "_blank",
|
|
8349
|
-
href: `https://devdata.gramene.org/curation/curations?idList=${gene.id}`,
|
|
8350
|
-
children: [
|
|
8351
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
8352
|
-
className: "okay",
|
|
8353
|
-
style: {
|
|
8354
|
-
opacity: okayRatio
|
|
8355
|
-
},
|
|
8356
|
-
children: "\u2611"
|
|
8357
|
-
}),
|
|
8358
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
8359
|
-
className: "flagged",
|
|
8360
|
-
style: {
|
|
8361
|
-
opacity: flaggedRatio
|
|
8362
|
-
},
|
|
8363
|
-
children: "\u26A0"
|
|
8364
|
-
})
|
|
8365
|
-
]
|
|
8366
|
-
})
|
|
8367
|
-
});
|
|
8368
|
-
};
|
|
8369
9949
|
const $6c5c4f90059875bf$var$ClosestOrthologCmp = (props)=>{
|
|
8370
9950
|
let id, taxon_id, name, desc, species, className, identity;
|
|
8371
9951
|
const gene = props.gene;
|
|
@@ -8486,12 +10066,12 @@ const $6c5c4f90059875bf$var$allDetails = [
|
|
|
8486
10066
|
class $6c5c4f90059875bf$var$Gene extends (0, ($parcel$interopDefault($gXNCa$react))).Component {
|
|
8487
10067
|
constructor(props){
|
|
8488
10068
|
super(props);
|
|
10069
|
+
// `details` is derived per-render config (which tabs the site enables,
|
|
10070
|
+
// crossed with this row's `capabilities`) — keep it local.
|
|
8489
10071
|
this.state = {
|
|
8490
10072
|
details: $6c5c4f90059875bf$var$allDetails.map((o)=>({
|
|
8491
10073
|
...o
|
|
8492
|
-
})).filter((d)=>props.config.details[d.id])
|
|
8493
|
-
expandedDetail: props.expandedDetail,
|
|
8494
|
-
fullscreen: false
|
|
10074
|
+
})).filter((d)=>props.config.details[d.id])
|
|
8495
10075
|
};
|
|
8496
10076
|
let hasData = {};
|
|
8497
10077
|
props.searchResult.capabilities.forEach((c)=>{
|
|
@@ -8500,28 +10080,46 @@ class $6c5c4f90059875bf$var$Gene extends (0, ($parcel$interopDefault($gXNCa$reac
|
|
|
8500
10080
|
});
|
|
8501
10081
|
this.state.details.forEach((d)=>d.available |= hasData.hasOwnProperty(d.id));
|
|
8502
10082
|
}
|
|
10083
|
+
componentDidMount() {
|
|
10084
|
+
// Seed the auto-open-homology intent (from GeneList when numFound===1) the
|
|
10085
|
+
// first time this row mounts, only if the user hasn't already opened/closed
|
|
10086
|
+
// a tab here. Without this, the bundle would have no entry and the prior
|
|
10087
|
+
// "auto-expand homology on single-result" behavior would be lost.
|
|
10088
|
+
if (this.props.expandedDetail && this.expandedDetail === undefined) this.props.doExpandGeneDetail({
|
|
10089
|
+
geneId: this.props.searchResult.id,
|
|
10090
|
+
detail: this.props.expandedDetail
|
|
10091
|
+
});
|
|
10092
|
+
}
|
|
10093
|
+
get expandedDetail() {
|
|
10094
|
+
const slice = this.props.uiViewState && this.props.uiViewState.byGene[this.props.searchResult.id];
|
|
10095
|
+
return slice ? slice.expandedDetail : undefined;
|
|
10096
|
+
}
|
|
10097
|
+
get fullscreen() {
|
|
10098
|
+
const slice = this.props.uiViewState && this.props.uiViewState.byGene[this.props.searchResult.id];
|
|
10099
|
+
return !!(slice && slice.fullscreen);
|
|
10100
|
+
}
|
|
8503
10101
|
getDetailStatus(d) {
|
|
8504
|
-
if (this.
|
|
10102
|
+
if (this.expandedDetail === d.id) return 'expanded';
|
|
8505
10103
|
if (d.available) return 'closed';
|
|
8506
10104
|
return d.id === "pubs" ? 'empty' : 'disabled';
|
|
8507
10105
|
}
|
|
8508
10106
|
setExpanded(d) {
|
|
8509
10107
|
if (d.available || d.id === "pubs") {
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
10108
|
+
const geneId = this.props.searchResult.id;
|
|
10109
|
+
if (this.expandedDetail === d.id) this.props.doExpandGeneDetail({
|
|
10110
|
+
geneId: geneId,
|
|
10111
|
+
detail: null
|
|
8513
10112
|
});
|
|
8514
10113
|
else {
|
|
8515
|
-
const geneId = this.props.searchResult.id;
|
|
8516
10114
|
if (!(this.props.geneDocs && this.props.geneDocs.hasOwnProperty(geneId))) this.props.requestGene(geneId);
|
|
8517
10115
|
(0, ($parcel$interopDefault($gXNCa$reactga4))).event({
|
|
8518
10116
|
category: 'Search',
|
|
8519
10117
|
action: 'Details',
|
|
8520
10118
|
label: d.label
|
|
8521
10119
|
});
|
|
8522
|
-
this.
|
|
8523
|
-
|
|
8524
|
-
|
|
10120
|
+
this.props.doExpandGeneDetail({
|
|
10121
|
+
geneId: geneId,
|
|
10122
|
+
detail: d.id
|
|
8525
10123
|
});
|
|
8526
10124
|
}
|
|
8527
10125
|
}
|
|
@@ -8543,7 +10141,6 @@ class $6c5c4f90059875bf$var$Gene extends (0, ($parcel$interopDefault($gXNCa$reac
|
|
|
8543
10141
|
const panSite = this.props.config.panSite;
|
|
8544
10142
|
const searchResult = this.props.searchResult;
|
|
8545
10143
|
const taxName = this.props.taxName;
|
|
8546
|
-
const curation = this.props.curation;
|
|
8547
10144
|
// let orthologs='';
|
|
8548
10145
|
// if (this.props.orthologs && this.props.orthologs.hasOwnProperty(searchResult.id)) {
|
|
8549
10146
|
// orthologs = this.props.orthologs[searchResult.id].join(', ');
|
|
@@ -8562,10 +10159,6 @@ class $6c5c4f90059875bf$var$Gene extends (0, ($parcel$interopDefault($gXNCa$reac
|
|
|
8562
10159
|
pan: panSite[searchResult.system_name],
|
|
8563
10160
|
gene: searchResult
|
|
8564
10161
|
}),
|
|
8565
|
-
curation && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($6c5c4f90059875bf$var$CurationComponent, {
|
|
8566
|
-
curation: curation,
|
|
8567
|
-
gene: searchResult
|
|
8568
|
-
}),
|
|
8569
10162
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
8570
10163
|
className: "gene-title",
|
|
8571
10164
|
children: [
|
|
@@ -8604,7 +10197,7 @@ class $6c5c4f90059875bf$var$Gene extends (0, ($parcel$interopDefault($gXNCa$reac
|
|
|
8604
10197
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
8605
10198
|
className: "gene-detail-tabs",
|
|
8606
10199
|
children: this.state.details.map((d, idx)=>{
|
|
8607
|
-
const isExpanded = this.
|
|
10200
|
+
const isExpanded = this.expandedDetail === d.id;
|
|
8608
10201
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.OverlayTrigger), {
|
|
8609
10202
|
placement: 'bottom',
|
|
8610
10203
|
overlay: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Tooltip), {
|
|
@@ -8630,7 +10223,8 @@ class $6c5c4f90059875bf$var$Gene extends (0, ($parcel$interopDefault($gXNCa$reac
|
|
|
8630
10223
|
title: "View full screen",
|
|
8631
10224
|
onClick: (e)=>{
|
|
8632
10225
|
e.stopPropagation();
|
|
8633
|
-
this.
|
|
10226
|
+
this.props.doSetGeneFullscreen({
|
|
10227
|
+
geneId: searchResult.id,
|
|
8634
10228
|
fullscreen: true
|
|
8635
10229
|
});
|
|
8636
10230
|
}
|
|
@@ -8640,19 +10234,24 @@ class $6c5c4f90059875bf$var$Gene extends (0, ($parcel$interopDefault($gXNCa$reac
|
|
|
8640
10234
|
}, idx);
|
|
8641
10235
|
})
|
|
8642
10236
|
}),
|
|
8643
|
-
this.
|
|
10237
|
+
this.expandedDetail && this.ensureGene(searchResult.id) && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $5c2c79352d3d7b81$export$ec6b54b688be1708), {
|
|
8644
10238
|
className: "visible-detail",
|
|
8645
|
-
fullscreen: this.
|
|
8646
|
-
onExitFullscreen: ()=>this.
|
|
10239
|
+
fullscreen: this.fullscreen,
|
|
10240
|
+
onExitFullscreen: ()=>this.props.doSetGeneFullscreen({
|
|
10241
|
+
geneId: searchResult.id,
|
|
8647
10242
|
fullscreen: false
|
|
8648
10243
|
}),
|
|
8649
|
-
title: (this.state.details.find((d)=>d.id === this.
|
|
8650
|
-
children: /*#__PURE__*/ (0, ($parcel$interopDefault($gXNCa$react))).createElement($6c5c4f90059875bf$var$inventory[this.
|
|
10244
|
+
title: (this.state.details.find((d)=>d.id === this.expandedDetail) || {}).label,
|
|
10245
|
+
children: /*#__PURE__*/ (0, ($parcel$interopDefault($gXNCa$react))).createElement($6c5c4f90059875bf$var$inventory[this.expandedDetail], this.props)
|
|
8651
10246
|
})
|
|
8652
10247
|
]
|
|
8653
10248
|
});
|
|
8654
10249
|
}
|
|
8655
10250
|
}
|
|
10251
|
+
// Wrap Gene so each row gets uiViewState + dispatchers. (We don't pull these
|
|
10252
|
+
// in at the GeneList level because the slice we care about is per-geneId; the
|
|
10253
|
+
// component-side getters read by row id.)
|
|
10254
|
+
const $6c5c4f90059875bf$var$ConnectedGene = (0, $gXNCa$reduxbundlerreact.connect)('selectUiViewState', 'doExpandGeneDetail', 'doSetGeneFullscreen', $6c5c4f90059875bf$var$Gene);
|
|
8656
10255
|
const $6c5c4f90059875bf$var$GeneList = (props)=>{
|
|
8657
10256
|
if (props.grameneSearch && props.grameneSearch.response && props.grameneTaxonomy) {
|
|
8658
10257
|
let prev, page, next;
|
|
@@ -8694,7 +10293,7 @@ const $6c5c4f90059875bf$var$GeneList = (props)=>{
|
|
|
8694
10293
|
next
|
|
8695
10294
|
]
|
|
8696
10295
|
}),
|
|
8697
|
-
props.grameneSearch.response.docs.map((g, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($6c5c4f90059875bf$var$
|
|
10296
|
+
props.grameneSearch.response.docs.map((g, idx)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($6c5c4f90059875bf$var$ConnectedGene, {
|
|
8698
10297
|
searchResult: g,
|
|
8699
10298
|
config: props.configuration,
|
|
8700
10299
|
taxName: props.grameneTaxonomy[g.taxon_id].name,
|
|
@@ -8703,7 +10302,6 @@ const $6c5c4f90059875bf$var$GeneList = (props)=>{
|
|
|
8703
10302
|
requestOrthologs: props.doRequestOrthologs,
|
|
8704
10303
|
orthologs: props.grameneOrthologs,
|
|
8705
10304
|
taxLut: props.grameneTaxonomy,
|
|
8706
|
-
curation: props.curatedGenes ? props.curatedGenes[g.id] : null,
|
|
8707
10305
|
expandedDetail: props.grameneSearch.response.numFound === 1 && g.can_show.homology ? 'homology' : null
|
|
8708
10306
|
}, idx)),
|
|
8709
10307
|
prev,
|
|
@@ -8714,7 +10312,7 @@ const $6c5c4f90059875bf$var$GeneList = (props)=>{
|
|
|
8714
10312
|
}
|
|
8715
10313
|
return null;
|
|
8716
10314
|
};
|
|
8717
|
-
var $6c5c4f90059875bf$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectConfiguration', 'selectGrameneSearch', 'selectGrameneTaxonomy', 'selectGrameneGenes', 'selectGrameneOrthologs', 'selectGrameneSearchOffset', 'selectGrameneSearchRows', '
|
|
10315
|
+
var $6c5c4f90059875bf$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectConfiguration', 'selectGrameneSearch', 'selectGrameneTaxonomy', 'selectGrameneGenes', 'selectGrameneOrthologs', 'selectGrameneSearchOffset', 'selectGrameneSearchRows', 'doRequestGrameneGene', 'doRequestOrthologs', 'doRequestResultsPage', $6c5c4f90059875bf$var$GeneList);
|
|
8718
10316
|
|
|
8719
10317
|
|
|
8720
10318
|
|
|
@@ -8849,6 +10447,15 @@ class $a67cad486021eb32$var$TaxDist extends (0, ($parcel$interopDefault($gXNCa$r
|
|
|
8849
10447
|
if (this.state.showCompara && this.state.comparaOnly && this.props.grameneMaps) Object.keys(selectedTaxa).forEach((tid)=>{
|
|
8850
10448
|
if (!this.props.grameneMaps[tid].in_compara) delete selectedTaxa[tid];
|
|
8851
10449
|
});
|
|
10450
|
+
// Drop any tid the taxonomy tree doesn't know about — gramene-search-vis
|
|
10451
|
+
// throws when it encounters one (nodeIndex[tid].getPath() on undefined),
|
|
10452
|
+
// which silently leaves every branch expanded.
|
|
10453
|
+
if (this.props.grameneTaxDist && this.props.grameneTaxDist.indices && this.props.grameneTaxDist.indices.id) {
|
|
10454
|
+
const nodeIndex = this.props.grameneTaxDist.indices.id;
|
|
10455
|
+
Object.keys(selectedTaxa).forEach((tid)=>{
|
|
10456
|
+
if (!nodeIndex[tid]) delete selectedTaxa[tid];
|
|
10457
|
+
});
|
|
10458
|
+
}
|
|
8852
10459
|
}
|
|
8853
10460
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
8854
10461
|
className: "results-vis big-vis",
|
|
@@ -12900,12 +14507,19 @@ function $caf32827df861c4e$var$logTickFormat(v) {
|
|
|
12900
14507
|
if (a >= 0.01 && a < 10000) return $gXNCa$d3.format('~g')(v);
|
|
12901
14508
|
return $gXNCa$d3.format('.0e')(v);
|
|
12902
14509
|
}
|
|
12903
|
-
const $caf32827df861c4e$var$ParallelCoordsPlot = ({ rows: rows, fields: fields, scale: scale = 'linear', onBrushChange: onBrushChange, onReorder: onReorder, clearVersion: clearVersion = 0, hoveredId: hoveredId = null, axisLabels: axisLabels = null })=>{
|
|
14510
|
+
const $caf32827df861c4e$var$ParallelCoordsPlot = ({ rows: rows, fields: fields, scale: scale = 'linear', initialSelections: initialSelections = null, onBrushChange: onBrushChange, onReorder: onReorder, clearVersion: clearVersion = 0, hoveredId: hoveredId = null, axisLabels: axisLabels = null })=>{
|
|
12904
14511
|
const svgRef = (0, $gXNCa$react.useRef)(null);
|
|
12905
14512
|
const containerRef = (0, $gXNCa$react.useRef)(null);
|
|
12906
14513
|
// selections in data domain: { [field]: [lo, hi] }
|
|
12907
14514
|
const selectionsRef = (0, $gXNCa$react.useRef)({});
|
|
12908
14515
|
const lastClearRef = (0, $gXNCa$react.useRef)(0);
|
|
14516
|
+
// Seed brushes from a restored saved view exactly once (the first render
|
|
14517
|
+
// where `initialSelections` is non-empty). Read via a ref so a change to the
|
|
14518
|
+
// prop — which happens on every brush as it round-trips through the store —
|
|
14519
|
+
// doesn't force an SVG rebuild; the deps below already rerun on data changes.
|
|
14520
|
+
const initialSelectionsRef = (0, $gXNCa$react.useRef)(initialSelections);
|
|
14521
|
+
initialSelectionsRef.current = initialSelections;
|
|
14522
|
+
const seededRef = (0, $gXNCa$react.useRef)(false);
|
|
12909
14523
|
// Track container size so the d3 render reruns when the user drags the
|
|
12910
14524
|
// pane resizer (or when the window is resized). The values themselves
|
|
12911
14525
|
// aren't read inside the effect — the effect always reads clientWidth/
|
|
@@ -12936,6 +14550,15 @@ const $caf32827df861c4e$var$ParallelCoordsPlot = ({ rows: rows, fields: fields,
|
|
|
12936
14550
|
return ()=>ro.disconnect();
|
|
12937
14551
|
}, []);
|
|
12938
14552
|
(0, $gXNCa$react.useEffect)(()=>{
|
|
14553
|
+
if (!seededRef.current) {
|
|
14554
|
+
const init = initialSelectionsRef.current;
|
|
14555
|
+
if (init && Object.keys(init).length) {
|
|
14556
|
+
selectionsRef.current = {
|
|
14557
|
+
...init
|
|
14558
|
+
};
|
|
14559
|
+
seededRef.current = true;
|
|
14560
|
+
}
|
|
14561
|
+
}
|
|
12939
14562
|
if (clearVersion !== lastClearRef.current) {
|
|
12940
14563
|
selectionsRef.current = {};
|
|
12941
14564
|
lastClearRef.current = clearVersion;
|
|
@@ -13803,7 +15426,10 @@ const $1fd2507769d5bd00$var$ExprVizViewCmp = (props)=>{
|
|
|
13803
15426
|
onOpenFields: ()=>doToggleExprVizFieldsModal(tid, true),
|
|
13804
15427
|
onLoad: ()=>doFetchExprVizData(tid),
|
|
13805
15428
|
onReorder: (next)=>props.doReorderExprVizFields(tid, next),
|
|
13806
|
-
onAddRangeQuery: props.doAddGrameneRangeQuery
|
|
15429
|
+
onAddRangeQuery: props.doAddGrameneRangeQuery,
|
|
15430
|
+
onSetVizMode: (m)=>props.doSetExprVizVizMode(tid, m),
|
|
15431
|
+
onSetScale: (s)=>props.doSetExprVizScale(tid, s),
|
|
15432
|
+
onSetBrushes: (b)=>props.doSetExprVizBrushes(tid, b)
|
|
13807
15433
|
})
|
|
13808
15434
|
}, tid);
|
|
13809
15435
|
})
|
|
@@ -13963,16 +15589,21 @@ function $1fd2507769d5bd00$var$downloadTsv(filename, rows, fields, studies, expr
|
|
|
13963
15589
|
document.body.removeChild(a);
|
|
13964
15590
|
URL.revokeObjectURL(url);
|
|
13965
15591
|
}
|
|
13966
|
-
const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expressionSamples: expressionSamples, tabState: tabState, onOpenFields: onOpenFields, onLoad: onLoad, onReorder: onReorder, onAddRangeQuery: onAddRangeQuery })=>{
|
|
15592
|
+
const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expressionSamples: expressionSamples, tabState: tabState, onOpenFields: onOpenFields, onLoad: onLoad, onReorder: onReorder, onAddRangeQuery: onAddRangeQuery, onSetVizMode: onSetVizMode, onSetScale: onSetScale, onSetBrushes: onSetBrushes })=>{
|
|
13967
15593
|
const selected = tabState && tabState.selectedFields || [];
|
|
13968
15594
|
const rows = tabState && tabState.rows || [];
|
|
13969
15595
|
const fetchInfo = tabState && tabState.fetch || {
|
|
13970
15596
|
status: 'idle',
|
|
13971
15597
|
total: 0
|
|
13972
15598
|
};
|
|
13973
|
-
|
|
13974
|
-
|
|
13975
|
-
const
|
|
15599
|
+
// View config (sub-tab, scale, brushes) is persisted in the exprViz bundle
|
|
15600
|
+
// per taxon so a saved view can round-trip it. Driven controlled from here.
|
|
15601
|
+
const scale = tabState && tabState.scale || 'linear';
|
|
15602
|
+
const vizMode = tabState && tabState.vizMode || 'heatmap'; // 'parallel' | 'heatmap'
|
|
15603
|
+
const selections = tabState && tabState.brushes || {};
|
|
15604
|
+
const setScale = (s)=>onSetScale && onSetScale(s);
|
|
15605
|
+
const setVizMode = (m)=>onSetVizMode && onSetVizMode(m);
|
|
15606
|
+
const setSelections = (b)=>onSetBrushes && onSetBrushes(b);
|
|
13976
15607
|
const [clearVersion, setClearVersion] = (0, $gXNCa$react.useState)(0);
|
|
13977
15608
|
const [hoveredId, setHoveredId] = (0, $gXNCa$react.useState)(null);
|
|
13978
15609
|
const [plotHeight, setPlotHeight] = (0, $gXNCa$react.useState)(320);
|
|
@@ -14048,15 +15679,6 @@ const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expr
|
|
|
14048
15679
|
studies,
|
|
14049
15680
|
expressionSamples
|
|
14050
15681
|
]);
|
|
14051
|
-
(0, $gXNCa$react.useEffect)(()=>{
|
|
14052
|
-
if (rows.length === 0 && hasBrush) {
|
|
14053
|
-
setSelections({});
|
|
14054
|
-
setClearVersion((v)=>v + 1);
|
|
14055
|
-
}
|
|
14056
|
-
}, [
|
|
14057
|
-
rows.length,
|
|
14058
|
-
hasBrush
|
|
14059
|
-
]);
|
|
14060
15682
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
14061
15683
|
className: "exprviz-tab-panel",
|
|
14062
15684
|
children: [
|
|
@@ -14197,6 +15819,7 @@ const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expr
|
|
|
14197
15819
|
rows: rows,
|
|
14198
15820
|
fields: visibleFields,
|
|
14199
15821
|
scale: scale,
|
|
15822
|
+
initialSelections: selections,
|
|
14200
15823
|
onBrushChange: setSelections,
|
|
14201
15824
|
onReorder: handleReorder,
|
|
14202
15825
|
clearVersion: clearVersion,
|
|
@@ -14229,7 +15852,7 @@ const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expr
|
|
|
14229
15852
|
]
|
|
14230
15853
|
});
|
|
14231
15854
|
};
|
|
14232
|
-
var $1fd2507769d5bd00$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectExprViz', 'selectExprVizPivot', 'selectExprVizActiveTaxon', 'selectGrameneMaps', 'selectExpressionStudies', 'selectExpressionSamples', 'doSetExprVizActiveTaxon', 'doToggleExprVizFieldsModal', 'doFetchExprVizData', 'doReorderExprVizFields', 'doAddGrameneRangeQuery', $1fd2507769d5bd00$var$ExprVizViewCmp);
|
|
15855
|
+
var $1fd2507769d5bd00$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectExprViz', 'selectExprVizPivot', 'selectExprVizActiveTaxon', 'selectGrameneMaps', 'selectExpressionStudies', 'selectExpressionSamples', 'doSetExprVizActiveTaxon', 'doToggleExprVizFieldsModal', 'doFetchExprVizData', 'doReorderExprVizFields', 'doSetExprVizVizMode', 'doSetExprVizScale', 'doSetExprVizBrushes', 'doAddGrameneRangeQuery', $1fd2507769d5bd00$var$ExprVizViewCmp);
|
|
14233
15856
|
|
|
14234
15857
|
|
|
14235
15858
|
|
|
@@ -14718,7 +16341,7 @@ const $597fe213417ee6ca$var$SortableTh = ({ label: label, sortKey: sortKey, acti
|
|
|
14718
16341
|
}) : inner
|
|
14719
16342
|
});
|
|
14720
16343
|
};
|
|
14721
|
-
const $597fe213417ee6ca$var$OntologySection = ({ block: block, search: search, onAddFilter: onAddFilter })=>{
|
|
16344
|
+
const $597fe213417ee6ca$var$OntologySection = ({ block: block, search: search, sort: sort, onSortChange: onSortChange, onAddFilter: onAddFilter })=>{
|
|
14722
16345
|
const filtered = (0, $gXNCa$react.useMemo)(()=>{
|
|
14723
16346
|
if (!search) return block.rows;
|
|
14724
16347
|
const needle = search.toLowerCase();
|
|
@@ -14728,14 +16351,13 @@ const $597fe213417ee6ca$var$OntologySection = ({ block: block, search: search, o
|
|
|
14728
16351
|
search
|
|
14729
16352
|
]);
|
|
14730
16353
|
const showType = $597fe213417ee6ca$var$ONTS_WITH_TYPE_COLUMN.has(block.ontology);
|
|
14731
|
-
|
|
14732
|
-
|
|
16354
|
+
// Sort state is persisted per section in the bundle ui so a saved view
|
|
16355
|
+
// restores it. Defaults match the historical local-state initials.
|
|
16356
|
+
const sortKey = sort && sort.key || 'pAdj';
|
|
16357
|
+
const sortDir = sort && sort.dir || 'asc';
|
|
14733
16358
|
const handleSort = (key)=>{
|
|
14734
|
-
if (key === sortKey)
|
|
14735
|
-
else
|
|
14736
|
-
setSortKey(key);
|
|
14737
|
-
setSortDir($597fe213417ee6ca$var$SORT_DEFAULT_DIR[key] || 'asc');
|
|
14738
|
-
}
|
|
16359
|
+
if (key === sortKey) onSortChange(block.ontology, sortKey, sortDir === 'asc' ? 'desc' : 'asc');
|
|
16360
|
+
else onSortChange(block.ontology, key, $597fe213417ee6ca$var$SORT_DEFAULT_DIR[key] || 'asc');
|
|
14739
16361
|
};
|
|
14740
16362
|
const sorted = (0, $gXNCa$react.useMemo)(()=>{
|
|
14741
16363
|
const accessor = $597fe213417ee6ca$var$SORT_ACCESSORS[sortKey];
|
|
@@ -14932,6 +16554,17 @@ const $597fe213417ee6ca$var$TaxonPanel = ({ taxon: taxon, ontologyEnrichment: on
|
|
|
14932
16554
|
] : [];
|
|
14933
16555
|
// Hide ontologies that aren't used in this species at all.
|
|
14934
16556
|
const blocks = allBlocks.filter((b)=>b.tested > 0);
|
|
16557
|
+
const handleSortChange = (sectionKey, key, dir)=>{
|
|
16558
|
+
onUiChange({
|
|
16559
|
+
sort: {
|
|
16560
|
+
...ui.sort || {},
|
|
16561
|
+
[sectionKey]: {
|
|
16562
|
+
key: key,
|
|
16563
|
+
dir: dir
|
|
16564
|
+
}
|
|
16565
|
+
}
|
|
16566
|
+
});
|
|
16567
|
+
};
|
|
14935
16568
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
14936
16569
|
className: "oe-panel",
|
|
14937
16570
|
children: [
|
|
@@ -14959,6 +16592,8 @@ const $597fe213417ee6ca$var$TaxonPanel = ({ taxon: taxon, ontologyEnrichment: on
|
|
|
14959
16592
|
children: blocks.map((b)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($597fe213417ee6ca$var$OntologySection, {
|
|
14960
16593
|
block: b,
|
|
14961
16594
|
search: ui.search,
|
|
16595
|
+
sort: ui.sort && ui.sort[b.ontology],
|
|
16596
|
+
onSortChange: handleSortChange,
|
|
14962
16597
|
onAddFilter: onAddFilter
|
|
14963
16598
|
}, b.ontology))
|
|
14964
16599
|
})
|
|
@@ -15097,6 +16732,223 @@ var $597fe213417ee6ca$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.conn
|
|
|
15097
16732
|
|
|
15098
16733
|
|
|
15099
16734
|
|
|
16735
|
+
// "Save this view" UI — sits inside the Auth sidebar panel.
|
|
16736
|
+
//
|
|
16737
|
+
// Sign-in-gated by Auth.js (we only mount this when `user` is truthy). Opens
|
|
16738
|
+
// a modal with label + description + visibility toggle; on save, swaps to a
|
|
16739
|
+
// post-save state that shows the shareable URL with a copy button.
|
|
16740
|
+
//
|
|
16741
|
+
// Talks to the savedViews bundle. The snapshot itself is built inside
|
|
16742
|
+
// doSaveView via selectViewSnapshot — this component doesn't know or care
|
|
16743
|
+
// what the snapshot looks like.
|
|
16744
|
+
|
|
16745
|
+
|
|
16746
|
+
|
|
16747
|
+
|
|
16748
|
+
|
|
16749
|
+
const $e63a3bee79bd1c7a$var$SaveViewButtonCmp = ({ user: user, savedViews: savedViews, doSaveView: doSaveView, doResetSavedViewState: doResetSavedViewState })=>{
|
|
16750
|
+
const [showModal, setShowModal] = (0, $gXNCa$react.useState)(false);
|
|
16751
|
+
const [label, setLabel] = (0, $gXNCa$react.useState)('');
|
|
16752
|
+
const [description, setDescription] = (0, $gXNCa$react.useState)('');
|
|
16753
|
+
const [isPublic, setIsPublic] = (0, $gXNCa$react.useState)(false);
|
|
16754
|
+
const [shareUrl, setShareUrl] = (0, $gXNCa$react.useState)(null);
|
|
16755
|
+
const [copied, setCopied] = (0, $gXNCa$react.useState)(false);
|
|
16756
|
+
const reset = (0, $gXNCa$react.useCallback)(()=>{
|
|
16757
|
+
setLabel('');
|
|
16758
|
+
setDescription('');
|
|
16759
|
+
setIsPublic(false);
|
|
16760
|
+
setShareUrl(null);
|
|
16761
|
+
setCopied(false);
|
|
16762
|
+
doResetSavedViewState();
|
|
16763
|
+
}, [
|
|
16764
|
+
doResetSavedViewState
|
|
16765
|
+
]);
|
|
16766
|
+
const handleOpen = ()=>{
|
|
16767
|
+
reset();
|
|
16768
|
+
setShowModal(true);
|
|
16769
|
+
};
|
|
16770
|
+
const handleClose = ()=>{
|
|
16771
|
+
setShowModal(false);
|
|
16772
|
+
reset();
|
|
16773
|
+
};
|
|
16774
|
+
const handleSave = async (e)=>{
|
|
16775
|
+
e.preventDefault();
|
|
16776
|
+
if (!label.trim()) return;
|
|
16777
|
+
try {
|
|
16778
|
+
const { shareUrl: shareUrl } = await doSaveView({
|
|
16779
|
+
user: user,
|
|
16780
|
+
label: label.trim(),
|
|
16781
|
+
description: description.trim(),
|
|
16782
|
+
isPublic: isPublic
|
|
16783
|
+
});
|
|
16784
|
+
setShareUrl(shareUrl);
|
|
16785
|
+
} catch (_) {
|
|
16786
|
+
// savedViews.saveError is already set; modal renders it
|
|
16787
|
+
}
|
|
16788
|
+
};
|
|
16789
|
+
const handleCopy = async ()=>{
|
|
16790
|
+
if (!shareUrl) return;
|
|
16791
|
+
try {
|
|
16792
|
+
await navigator.clipboard.writeText(shareUrl);
|
|
16793
|
+
setCopied(true);
|
|
16794
|
+
setTimeout(()=>setCopied(false), 1500);
|
|
16795
|
+
} catch (_) {}
|
|
16796
|
+
};
|
|
16797
|
+
const saving = savedViews && savedViews.saving;
|
|
16798
|
+
const saveError = savedViews && savedViews.saveError;
|
|
16799
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
16800
|
+
children: [
|
|
16801
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
16802
|
+
size: "sm",
|
|
16803
|
+
variant: "outline-primary",
|
|
16804
|
+
style: {
|
|
16805
|
+
marginTop: 8
|
|
16806
|
+
},
|
|
16807
|
+
onClick: handleOpen,
|
|
16808
|
+
title: "Save the current filters, views, and detail tabs as a shareable link",
|
|
16809
|
+
children: "Save this view"
|
|
16810
|
+
}),
|
|
16811
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Modal), {
|
|
16812
|
+
show: showModal,
|
|
16813
|
+
onHide: handleClose,
|
|
16814
|
+
centered: true,
|
|
16815
|
+
children: [
|
|
16816
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Modal).Header, {
|
|
16817
|
+
closeButton: true,
|
|
16818
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Modal).Title, {
|
|
16819
|
+
children: shareUrl ? 'View saved' : 'Save this view'
|
|
16820
|
+
})
|
|
16821
|
+
}),
|
|
16822
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Modal).Body, {
|
|
16823
|
+
children: [
|
|
16824
|
+
!shareUrl && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Form), {
|
|
16825
|
+
onSubmit: handleSave,
|
|
16826
|
+
children: [
|
|
16827
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Form).Group, {
|
|
16828
|
+
className: "mb-3",
|
|
16829
|
+
children: [
|
|
16830
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Label, {
|
|
16831
|
+
children: "Name"
|
|
16832
|
+
}),
|
|
16833
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
|
|
16834
|
+
autoFocus: true,
|
|
16835
|
+
type: "text",
|
|
16836
|
+
placeholder: "e.g. TAIR loci with TF binding sites",
|
|
16837
|
+
value: label,
|
|
16838
|
+
onChange: (e)=>setLabel(e.target.value),
|
|
16839
|
+
disabled: saving,
|
|
16840
|
+
required: true
|
|
16841
|
+
})
|
|
16842
|
+
]
|
|
16843
|
+
}),
|
|
16844
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Form).Group, {
|
|
16845
|
+
className: "mb-3",
|
|
16846
|
+
children: [
|
|
16847
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Label, {
|
|
16848
|
+
children: "Description (optional)"
|
|
16849
|
+
}),
|
|
16850
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
|
|
16851
|
+
as: "textarea",
|
|
16852
|
+
rows: 2,
|
|
16853
|
+
value: description,
|
|
16854
|
+
onChange: (e)=>setDescription(e.target.value),
|
|
16855
|
+
disabled: saving
|
|
16856
|
+
})
|
|
16857
|
+
]
|
|
16858
|
+
}),
|
|
16859
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Group, {
|
|
16860
|
+
className: "mb-3",
|
|
16861
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Check, {
|
|
16862
|
+
type: "switch",
|
|
16863
|
+
id: "save-view-public",
|
|
16864
|
+
label: "Make this view public (visible to anyone with the link)",
|
|
16865
|
+
checked: isPublic,
|
|
16866
|
+
onChange: (e)=>setIsPublic(e.target.checked),
|
|
16867
|
+
disabled: saving
|
|
16868
|
+
})
|
|
16869
|
+
}),
|
|
16870
|
+
saveError && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Alert), {
|
|
16871
|
+
variant: "danger",
|
|
16872
|
+
children: saveError
|
|
16873
|
+
}),
|
|
16874
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
16875
|
+
className: "d-flex justify-content-end gap-2",
|
|
16876
|
+
children: [
|
|
16877
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
16878
|
+
variant: "secondary",
|
|
16879
|
+
onClick: handleClose,
|
|
16880
|
+
disabled: saving,
|
|
16881
|
+
children: "Cancel"
|
|
16882
|
+
}),
|
|
16883
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
16884
|
+
type: "submit",
|
|
16885
|
+
variant: "primary",
|
|
16886
|
+
disabled: saving || !label.trim(),
|
|
16887
|
+
children: saving ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
16888
|
+
children: [
|
|
16889
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Spinner), {
|
|
16890
|
+
as: "span",
|
|
16891
|
+
size: "sm",
|
|
16892
|
+
animation: "border"
|
|
16893
|
+
}),
|
|
16894
|
+
" Saving\u2026"
|
|
16895
|
+
]
|
|
16896
|
+
}) : 'Save'
|
|
16897
|
+
})
|
|
16898
|
+
]
|
|
16899
|
+
})
|
|
16900
|
+
]
|
|
16901
|
+
}),
|
|
16902
|
+
shareUrl && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
16903
|
+
children: [
|
|
16904
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("p", {
|
|
16905
|
+
children: "Share this link to let others open the same search and detail state:"
|
|
16906
|
+
}),
|
|
16907
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.InputGroup), {
|
|
16908
|
+
children: [
|
|
16909
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
|
|
16910
|
+
type: "text",
|
|
16911
|
+
value: shareUrl,
|
|
16912
|
+
readOnly: true,
|
|
16913
|
+
onFocus: (e)=>e.target.select()
|
|
16914
|
+
}),
|
|
16915
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
16916
|
+
variant: copied ? 'success' : 'outline-secondary',
|
|
16917
|
+
onClick: handleCopy,
|
|
16918
|
+
children: copied ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
16919
|
+
children: [
|
|
16920
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsCheck2), {}),
|
|
16921
|
+
" Copied"
|
|
16922
|
+
]
|
|
16923
|
+
}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
16924
|
+
children: [
|
|
16925
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsClipboard), {}),
|
|
16926
|
+
" Copy"
|
|
16927
|
+
]
|
|
16928
|
+
})
|
|
16929
|
+
})
|
|
16930
|
+
]
|
|
16931
|
+
}),
|
|
16932
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
16933
|
+
className: "mt-3 d-flex justify-content-end",
|
|
16934
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
16935
|
+
variant: "primary",
|
|
16936
|
+
onClick: handleClose,
|
|
16937
|
+
children: "Done"
|
|
16938
|
+
})
|
|
16939
|
+
})
|
|
16940
|
+
]
|
|
16941
|
+
})
|
|
16942
|
+
]
|
|
16943
|
+
})
|
|
16944
|
+
]
|
|
16945
|
+
})
|
|
16946
|
+
]
|
|
16947
|
+
});
|
|
16948
|
+
};
|
|
16949
|
+
var $e63a3bee79bd1c7a$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectSavedViews', 'doSaveView', 'doResetSavedViewState', $e63a3bee79bd1c7a$var$SaveViewButtonCmp);
|
|
16950
|
+
|
|
16951
|
+
|
|
15100
16952
|
const $c5d403787de8b05f$var$provider = new (0, $gXNCa$firebaseauth.GoogleAuthProvider)();
|
|
15101
16953
|
const $c5d403787de8b05f$var$Auth = (props)=>{
|
|
15102
16954
|
const firebaseConfig = props.configuration && props.configuration.firebaseConfig;
|
|
@@ -15106,10 +16958,25 @@ const $c5d403787de8b05f$var$Auth = (props)=>{
|
|
|
15106
16958
|
}, [
|
|
15107
16959
|
firebaseConfig
|
|
15108
16960
|
]);
|
|
15109
|
-
const [user, setUser] = (0, $gXNCa$react.useState)(
|
|
16961
|
+
const [user, setUser] = (0, $gXNCa$react.useState)(null);
|
|
15110
16962
|
const [open, setOpen] = (0, $gXNCa$react.useState)(true);
|
|
16963
|
+
// Subscribe once, unsubscribe on unmount. On each auth-state emission,
|
|
16964
|
+
// also poke the shared-view boot path so a `?view=<hash>` for a private
|
|
16965
|
+
// view gets retried with the now-available Bearer token. bootViewFromUrl
|
|
16966
|
+
// no-ops when the param isn't present, so this is cheap.
|
|
16967
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
16968
|
+
if (!auth) return undefined;
|
|
16969
|
+
const unsub = (0, $gXNCa$firebaseauth.onAuthStateChanged)(auth, (u)=>{
|
|
16970
|
+
setUser(u);
|
|
16971
|
+
props.doBootSharedView({
|
|
16972
|
+
user: u
|
|
16973
|
+
});
|
|
16974
|
+
});
|
|
16975
|
+
return unsub;
|
|
16976
|
+
}, [
|
|
16977
|
+
auth
|
|
16978
|
+
]);
|
|
15111
16979
|
if (!auth) return null;
|
|
15112
|
-
(0, $gXNCa$firebaseauth.onAuthStateChanged)(auth, (user)=>setUser(user));
|
|
15113
16980
|
function handleLogin() {
|
|
15114
16981
|
(0, $gXNCa$firebaseauth.signInWithPopup)(auth, $c5d403787de8b05f$var$provider).then((result)=>{
|
|
15115
16982
|
setUser(result.user);
|
|
@@ -15145,27 +17012,34 @@ const $c5d403787de8b05f$var$Auth = (props)=>{
|
|
|
15145
17012
|
})
|
|
15146
17013
|
]
|
|
15147
17014
|
}),
|
|
15148
|
-
open && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.
|
|
17015
|
+
open && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
15149
17016
|
className: "sidebar-section-body",
|
|
15150
|
-
children:
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
|
|
15160
|
-
|
|
17017
|
+
children: [
|
|
17018
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
17019
|
+
children: user ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
17020
|
+
size: "sm",
|
|
17021
|
+
variant: "success",
|
|
17022
|
+
onClick: handleLogout,
|
|
17023
|
+
children: user.displayName
|
|
17024
|
+
}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
17025
|
+
size: "sm",
|
|
17026
|
+
variant: "success",
|
|
17027
|
+
onClick: handleLogin,
|
|
17028
|
+
children: "Login"
|
|
17029
|
+
})
|
|
17030
|
+
}),
|
|
17031
|
+
user && user.uid && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
17032
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $e63a3bee79bd1c7a$export$2e2bcd8739ae039), {
|
|
17033
|
+
user: user
|
|
17034
|
+
})
|
|
15161
17035
|
})
|
|
15162
|
-
|
|
17036
|
+
]
|
|
15163
17037
|
})
|
|
15164
17038
|
]
|
|
15165
17039
|
})
|
|
15166
17040
|
});
|
|
15167
17041
|
};
|
|
15168
|
-
var $c5d403787de8b05f$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectConfiguration', $c5d403787de8b05f$var$Auth);
|
|
17042
|
+
var $c5d403787de8b05f$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectConfiguration', 'doBootSharedView', $c5d403787de8b05f$var$Auth);
|
|
15169
17043
|
|
|
15170
17044
|
|
|
15171
17045
|
|
|
@@ -15541,4 +17415,5 @@ const $693dd8c7a5607c3a$export$5cb791131c501f6a = (0, $gXNCa$reduxbundlerreact.c
|
|
|
15541
17415
|
|
|
15542
17416
|
|
|
15543
17417
|
|
|
17418
|
+
|
|
15544
17419
|
//# sourceMappingURL=index.js.map
|