gramene-search 2.18.2 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +233 -0
- package/dist/index.css.map +1 -1
- package/dist/index.js +669 -86
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -13176,7 +13176,540 @@ var $261baeb81c4d4d8a$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.conn
|
|
|
13176
13176
|
|
|
13177
13177
|
|
|
13178
13178
|
|
|
13179
|
-
|
|
13179
|
+
|
|
13180
|
+
|
|
13181
|
+
|
|
13182
|
+
|
|
13183
|
+
|
|
13184
|
+
// Conflict rows rendered before the "show N more" cap, mirroring the Refine
|
|
13185
|
+
// sidebar's INITIAL_VISIBLE. A large paste can produce hundreds of conflicts.
|
|
13186
|
+
const $3b525b9a0ec89bcb$var$INITIAL_VISIBLE = 25;
|
|
13187
|
+
// Candidates shown per row before collapsing. Nothing guarantees 15 is the ceiling.
|
|
13188
|
+
const $3b525b9a0ec89bcb$var$MAX_CANDIDATES = 50;
|
|
13189
|
+
// Genome bulk buttons offered. More than this and the toolbar is worse than the rows.
|
|
13190
|
+
const $3b525b9a0ec89bcb$var$MAX_GENOME_ACTIONS = 6;
|
|
13191
|
+
// A collapsible bucket with a count, following facets/FacetCounts.js FacetGroup.
|
|
13192
|
+
// An empty bucket renders nothing rather than an empty heading.
|
|
13193
|
+
const $3b525b9a0ec89bcb$var$Bucket = ({ title: title, count: count, tone: tone, defaultOpen: defaultOpen, actions: actions, children: children })=>{
|
|
13194
|
+
const [open, setOpen] = (0, $gXNCa$react.useState)(!!defaultOpen);
|
|
13195
|
+
if (!count) return null;
|
|
13196
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
13197
|
+
className: `glr-bucket glr-bucket-${tone}`,
|
|
13198
|
+
children: [
|
|
13199
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
13200
|
+
className: "glr-bucket-header",
|
|
13201
|
+
onClick: ()=>setOpen(!open),
|
|
13202
|
+
children: [
|
|
13203
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13204
|
+
className: "glr-caret",
|
|
13205
|
+
children: open ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsChevronDown), {}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsChevronRight), {})
|
|
13206
|
+
}),
|
|
13207
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13208
|
+
className: "glr-bucket-title",
|
|
13209
|
+
children: title
|
|
13210
|
+
}),
|
|
13211
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13212
|
+
className: "glr-bucket-count",
|
|
13213
|
+
children: count.toLocaleString()
|
|
13214
|
+
}),
|
|
13215
|
+
actions && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13216
|
+
className: "glr-bucket-actions",
|
|
13217
|
+
children: actions
|
|
13218
|
+
})
|
|
13219
|
+
]
|
|
13220
|
+
}),
|
|
13221
|
+
open && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
13222
|
+
className: "glr-bucket-body",
|
|
13223
|
+
children: children
|
|
13224
|
+
})
|
|
13225
|
+
]
|
|
13226
|
+
});
|
|
13227
|
+
};
|
|
13228
|
+
/** "sorghum_bicolort2tcas" -> "Sorghum bicolort2tcas" */ function $3b525b9a0ec89bcb$var$speciesLabel(systemName) {
|
|
13229
|
+
if (!systemName) return null;
|
|
13230
|
+
const s = systemName.replace(/_/g, ' ');
|
|
13231
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
13232
|
+
}
|
|
13233
|
+
/**
|
|
13234
|
+
* Pure resolver for a validation response.
|
|
13235
|
+
*
|
|
13236
|
+
* `choices` is keyed by INDEX into `ambiguous`, not by the input string: a pasted
|
|
13237
|
+
* token can be anything, and `obj['__proto__'] = [...]` on a plain object sets the
|
|
13238
|
+
* prototype instead of a key.
|
|
13239
|
+
*
|
|
13240
|
+
* An index with NO entry keeps *all* of that input's matches — the "nothing is
|
|
13241
|
+
* silently dropped" default. An index mapped to `[]` means the user explicitly
|
|
13242
|
+
* dropped the row, which is why every read tests `undefined` rather than falsiness.
|
|
13243
|
+
*/ const $3b525b9a0ec89bcb$var$GeneListResolver = ({ validation: validation, choices: choices, meta: meta, onChange: onChange })=>{
|
|
13244
|
+
const [query, setQuery] = (0, $gXNCa$react.useState)('');
|
|
13245
|
+
const [showAll, setShowAll] = (0, $gXNCa$react.useState)(false);
|
|
13246
|
+
const [expanded, setExpanded] = (0, $gXNCa$react.useState)({});
|
|
13247
|
+
const { resolved: resolved, ambiguous: ambiguous, unknown: unknown } = validation;
|
|
13248
|
+
const keptFor = (i, a)=>choices[i] === undefined ? a.matches : choices[i];
|
|
13249
|
+
const setKept = (i, a, next)=>{
|
|
13250
|
+
const c = {
|
|
13251
|
+
...choices
|
|
13252
|
+
};
|
|
13253
|
+
// Drop back to the default when everything is kept, so "untouched" and
|
|
13254
|
+
// "explicitly kept all" stay indistinguishable and the state stays small.
|
|
13255
|
+
if (next.length === a.matches.length) delete c[i];
|
|
13256
|
+
else c[i] = next;
|
|
13257
|
+
onChange(c);
|
|
13258
|
+
};
|
|
13259
|
+
const toggle = (i, a, id)=>{
|
|
13260
|
+
const cur = keptFor(i, a);
|
|
13261
|
+
setKept(i, a, cur.indexOf(id) === -1 ? [
|
|
13262
|
+
...cur,
|
|
13263
|
+
id
|
|
13264
|
+
] : cur.filter((x)=>x !== id));
|
|
13265
|
+
};
|
|
13266
|
+
/**
|
|
13267
|
+
* Which genomes would actually narrow something.
|
|
13268
|
+
*
|
|
13269
|
+
* A plain "prefer sorghum" button is useless on this index: 94 of its ~100
|
|
13270
|
+
* genomes are sorghum accessions, so a conflict like Sobic.001G000200 — whose
|
|
13271
|
+
* candidates are sorghum_bicolor and sorghum_bicolort2tcas — is entirely
|
|
13272
|
+
* sorghum on both sides. The useful axis is the individual genome, and only
|
|
13273
|
+
* those that would change a row are worth offering.
|
|
13274
|
+
*/ const genomeActions = (0, $gXNCa$react.useMemo)(()=>{
|
|
13275
|
+
const tally = new Map();
|
|
13276
|
+
ambiguous.forEach((a)=>{
|
|
13277
|
+
const names = new Set();
|
|
13278
|
+
a.matches.forEach((id)=>{
|
|
13279
|
+
const d = meta[id];
|
|
13280
|
+
if (d && d.system_name) names.add(d.system_name);
|
|
13281
|
+
});
|
|
13282
|
+
names.forEach((sn)=>{
|
|
13283
|
+
const kept = a.matches.filter((id)=>meta[id] && meta[id].system_name === sn);
|
|
13284
|
+
// Only counts if it narrows: keeping every candidate is a no-op.
|
|
13285
|
+
if (kept.length && kept.length < a.matches.length) tally.set(sn, (tally.get(sn) || 0) + 1);
|
|
13286
|
+
});
|
|
13287
|
+
});
|
|
13288
|
+
return [
|
|
13289
|
+
...tally.entries()
|
|
13290
|
+
].sort((x, y)=>y[1] - x[1]).slice(0, $3b525b9a0ec89bcb$var$MAX_GENOME_ACTIONS);
|
|
13291
|
+
}, [
|
|
13292
|
+
ambiguous,
|
|
13293
|
+
meta
|
|
13294
|
+
]);
|
|
13295
|
+
const applyGenome = (systemName)=>(e)=>{
|
|
13296
|
+
e.stopPropagation();
|
|
13297
|
+
const c = {
|
|
13298
|
+
...choices
|
|
13299
|
+
};
|
|
13300
|
+
ambiguous.forEach((a, i)=>{
|
|
13301
|
+
const kept = a.matches.filter((id)=>meta[id] && meta[id].system_name === systemName);
|
|
13302
|
+
// Rows with no candidate in this genome are left exactly as they are,
|
|
13303
|
+
// rather than emptied.
|
|
13304
|
+
if (kept.length && kept.length < a.matches.length) c[i] = kept;
|
|
13305
|
+
});
|
|
13306
|
+
onChange(c);
|
|
13307
|
+
};
|
|
13308
|
+
const bulk = (mode)=>(e)=>{
|
|
13309
|
+
// These buttons sit inside the clickable header; don't collapse the section.
|
|
13310
|
+
e.stopPropagation();
|
|
13311
|
+
if (mode === 'all') return onChange({});
|
|
13312
|
+
const c = {};
|
|
13313
|
+
ambiguous.forEach((a, i)=>{
|
|
13314
|
+
if (mode === 'first' && a.matches.length > 1) c[i] = [
|
|
13315
|
+
a.matches[0]
|
|
13316
|
+
];
|
|
13317
|
+
if (mode === 'none') c[i] = [];
|
|
13318
|
+
});
|
|
13319
|
+
onChange(c);
|
|
13320
|
+
};
|
|
13321
|
+
const filtered = (0, $gXNCa$react.useMemo)(()=>{
|
|
13322
|
+
const q = query.trim().toLowerCase();
|
|
13323
|
+
const withIndex = ambiguous.map((a, i)=>({
|
|
13324
|
+
a: a,
|
|
13325
|
+
i: i
|
|
13326
|
+
}));
|
|
13327
|
+
if (!q) return withIndex;
|
|
13328
|
+
return withIndex.filter(({ a: a })=>a.input.toLowerCase().indexOf(q) !== -1 || a.matches.some((id)=>id.toLowerCase().indexOf(q) !== -1));
|
|
13329
|
+
}, [
|
|
13330
|
+
ambiguous,
|
|
13331
|
+
query
|
|
13332
|
+
]);
|
|
13333
|
+
const shown = showAll ? filtered : filtered.slice(0, $3b525b9a0ec89bcb$var$INITIAL_VISIBLE);
|
|
13334
|
+
const candidate = (i, a, id)=>{
|
|
13335
|
+
const doc = meta[id];
|
|
13336
|
+
const kept = keptFor(i, a).indexOf(id) !== -1;
|
|
13337
|
+
const species = $3b525b9a0ec89bcb$var$speciesLabel(doc && doc.system_name);
|
|
13338
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("label", {
|
|
13339
|
+
className: `glr-candidate${kept ? ' glr-candidate-kept' : ''}`,
|
|
13340
|
+
children: [
|
|
13341
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("input", {
|
|
13342
|
+
type: "checkbox",
|
|
13343
|
+
checked: kept,
|
|
13344
|
+
onChange: ()=>toggle(i, a, id)
|
|
13345
|
+
}),
|
|
13346
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13347
|
+
className: "glr-candidate-id",
|
|
13348
|
+
children: id
|
|
13349
|
+
}),
|
|
13350
|
+
species ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13351
|
+
className: "glr-candidate-species",
|
|
13352
|
+
children: species
|
|
13353
|
+
}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13354
|
+
className: "glr-candidate-species glr-muted",
|
|
13355
|
+
children: "\u2026"
|
|
13356
|
+
}),
|
|
13357
|
+
doc && doc.description && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13358
|
+
className: "glr-candidate-desc",
|
|
13359
|
+
title: doc.description,
|
|
13360
|
+
children: doc.description
|
|
13361
|
+
}),
|
|
13362
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("button", {
|
|
13363
|
+
type: "button",
|
|
13364
|
+
className: "glr-only",
|
|
13365
|
+
title: "Keep only this one",
|
|
13366
|
+
onClick: ()=>setKept(i, a, [
|
|
13367
|
+
id
|
|
13368
|
+
]),
|
|
13369
|
+
children: "only"
|
|
13370
|
+
})
|
|
13371
|
+
]
|
|
13372
|
+
}, id);
|
|
13373
|
+
};
|
|
13374
|
+
const row = ({ a: a, i: i })=>{
|
|
13375
|
+
const kept = keptFor(i, a);
|
|
13376
|
+
// Long candidate lists start collapsed so a 15-match row doesn't bury the rest.
|
|
13377
|
+
const isOpen = expanded[i] !== undefined ? expanded[i] : a.matches.length <= 3;
|
|
13378
|
+
const visible = isOpen ? a.matches.slice(0, $3b525b9a0ec89bcb$var$MAX_CANDIDATES) : [];
|
|
13379
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
13380
|
+
className: "glr-row",
|
|
13381
|
+
children: [
|
|
13382
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
13383
|
+
className: "glr-input",
|
|
13384
|
+
children: [
|
|
13385
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("button", {
|
|
13386
|
+
type: "button",
|
|
13387
|
+
className: "glr-row-toggle",
|
|
13388
|
+
onClick: ()=>setExpanded((s)=>({
|
|
13389
|
+
...s,
|
|
13390
|
+
[i]: !isOpen
|
|
13391
|
+
})),
|
|
13392
|
+
children: isOpen ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsChevronDown), {}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reacticonsbs.BsChevronRight), {})
|
|
13393
|
+
}),
|
|
13394
|
+
a.input,
|
|
13395
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Badge), {
|
|
13396
|
+
bg: kept.length === 0 ? 'danger' : 'secondary',
|
|
13397
|
+
className: "glr-input-count",
|
|
13398
|
+
children: [
|
|
13399
|
+
kept.length,
|
|
13400
|
+
"/",
|
|
13401
|
+
a.matches.length
|
|
13402
|
+
]
|
|
13403
|
+
})
|
|
13404
|
+
]
|
|
13405
|
+
}),
|
|
13406
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
13407
|
+
className: "glr-candidates",
|
|
13408
|
+
children: isOpen ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
13409
|
+
children: [
|
|
13410
|
+
visible.map((id)=>candidate(i, a, id)),
|
|
13411
|
+
a.matches.length > $3b525b9a0ec89bcb$var$MAX_CANDIDATES && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
13412
|
+
className: "glr-hint",
|
|
13413
|
+
children: [
|
|
13414
|
+
"\u2026 ",
|
|
13415
|
+
a.matches.length - $3b525b9a0ec89bcb$var$MAX_CANDIDATES,
|
|
13416
|
+
" more. Search to narrow."
|
|
13417
|
+
]
|
|
13418
|
+
}),
|
|
13419
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
13420
|
+
className: "glr-row-bulk",
|
|
13421
|
+
children: [
|
|
13422
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("button", {
|
|
13423
|
+
type: "button",
|
|
13424
|
+
className: "glr-only",
|
|
13425
|
+
onClick: ()=>setKept(i, a, a.matches),
|
|
13426
|
+
children: "all"
|
|
13427
|
+
}),
|
|
13428
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("button", {
|
|
13429
|
+
type: "button",
|
|
13430
|
+
className: "glr-only",
|
|
13431
|
+
onClick: ()=>setKept(i, a, []),
|
|
13432
|
+
children: "none"
|
|
13433
|
+
})
|
|
13434
|
+
]
|
|
13435
|
+
})
|
|
13436
|
+
]
|
|
13437
|
+
}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
13438
|
+
className: "glr-collapsed",
|
|
13439
|
+
children: [
|
|
13440
|
+
a.matches.length,
|
|
13441
|
+
" candidates \u2014 ",
|
|
13442
|
+
kept.length === a.matches.length ? 'all kept' : `${kept.length} kept`
|
|
13443
|
+
]
|
|
13444
|
+
})
|
|
13445
|
+
})
|
|
13446
|
+
]
|
|
13447
|
+
}, i);
|
|
13448
|
+
};
|
|
13449
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
13450
|
+
className: "glr",
|
|
13451
|
+
children: [
|
|
13452
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)($3b525b9a0ec89bcb$var$Bucket, {
|
|
13453
|
+
title: "Conflicts \u2014 one identifier, several genes",
|
|
13454
|
+
count: ambiguous.length,
|
|
13455
|
+
tone: "warn",
|
|
13456
|
+
defaultOpen: true,
|
|
13457
|
+
actions: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
13458
|
+
children: [
|
|
13459
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
13460
|
+
size: "sm",
|
|
13461
|
+
variant: "outline-secondary",
|
|
13462
|
+
onClick: bulk('first'),
|
|
13463
|
+
children: "Keep first"
|
|
13464
|
+
}),
|
|
13465
|
+
' ',
|
|
13466
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
13467
|
+
size: "sm",
|
|
13468
|
+
variant: "outline-secondary",
|
|
13469
|
+
onClick: bulk('none'),
|
|
13470
|
+
children: "Drop all"
|
|
13471
|
+
}),
|
|
13472
|
+
' ',
|
|
13473
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
13474
|
+
size: "sm",
|
|
13475
|
+
variant: "outline-secondary",
|
|
13476
|
+
onClick: bulk('all'),
|
|
13477
|
+
children: "Reset"
|
|
13478
|
+
})
|
|
13479
|
+
]
|
|
13480
|
+
}),
|
|
13481
|
+
children: [
|
|
13482
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("p", {
|
|
13483
|
+
className: "glr-hint",
|
|
13484
|
+
children: [
|
|
13485
|
+
"These identifiers match more than one gene. ",
|
|
13486
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
13487
|
+
children: "All matches are kept unless you narrow them"
|
|
13488
|
+
}),
|
|
13489
|
+
" \u2014 untick what you don't want, or use",
|
|
13490
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
13491
|
+
children: " only"
|
|
13492
|
+
}),
|
|
13493
|
+
" to keep a single gene."
|
|
13494
|
+
]
|
|
13495
|
+
}),
|
|
13496
|
+
genomeActions.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
13497
|
+
className: "glr-genomes",
|
|
13498
|
+
children: [
|
|
13499
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13500
|
+
className: "glr-hint glr-genomes-label",
|
|
13501
|
+
children: "Keep only matches from:"
|
|
13502
|
+
}),
|
|
13503
|
+
genomeActions.map(([sn, n])=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Button), {
|
|
13504
|
+
size: "sm",
|
|
13505
|
+
variant: "outline-primary",
|
|
13506
|
+
className: "glr-genome-btn",
|
|
13507
|
+
onClick: applyGenome(sn),
|
|
13508
|
+
children: [
|
|
13509
|
+
$3b525b9a0ec89bcb$var$speciesLabel(sn),
|
|
13510
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Badge), {
|
|
13511
|
+
bg: "light",
|
|
13512
|
+
text: "dark",
|
|
13513
|
+
className: "glr-genome-count",
|
|
13514
|
+
children: [
|
|
13515
|
+
"narrows ",
|
|
13516
|
+
n.toLocaleString()
|
|
13517
|
+
]
|
|
13518
|
+
})
|
|
13519
|
+
]
|
|
13520
|
+
}, sn))
|
|
13521
|
+
]
|
|
13522
|
+
}),
|
|
13523
|
+
ambiguous.length > $3b525b9a0ec89bcb$var$INITIAL_VISIBLE && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Form).Control, {
|
|
13524
|
+
type: "search",
|
|
13525
|
+
size: "sm",
|
|
13526
|
+
className: "glr-search",
|
|
13527
|
+
placeholder: "Search conflicts\u2026",
|
|
13528
|
+
value: query,
|
|
13529
|
+
onChange: (e)=>{
|
|
13530
|
+
setQuery(e.target.value);
|
|
13531
|
+
setShowAll(false);
|
|
13532
|
+
}
|
|
13533
|
+
}),
|
|
13534
|
+
shown.map(row),
|
|
13535
|
+
filtered.length > shown.length && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("button", {
|
|
13536
|
+
type: "button",
|
|
13537
|
+
className: "glr-more",
|
|
13538
|
+
onClick: ()=>setShowAll(true),
|
|
13539
|
+
children: [
|
|
13540
|
+
"show ",
|
|
13541
|
+
(filtered.length - shown.length).toLocaleString(),
|
|
13542
|
+
" more"
|
|
13543
|
+
]
|
|
13544
|
+
}),
|
|
13545
|
+
query && filtered.length === 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("p", {
|
|
13546
|
+
className: "glr-hint",
|
|
13547
|
+
children: [
|
|
13548
|
+
"No conflicts match \u201C",
|
|
13549
|
+
query,
|
|
13550
|
+
"\u201D."
|
|
13551
|
+
]
|
|
13552
|
+
})
|
|
13553
|
+
]
|
|
13554
|
+
}),
|
|
13555
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($3b525b9a0ec89bcb$var$Bucket, {
|
|
13556
|
+
title: "Resolved",
|
|
13557
|
+
count: resolved.length,
|
|
13558
|
+
tone: "ok",
|
|
13559
|
+
defaultOpen: false,
|
|
13560
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
13561
|
+
className: "glr-resolved",
|
|
13562
|
+
children: resolved.map((r, i)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
13563
|
+
className: "glr-resolved-row",
|
|
13564
|
+
children: [
|
|
13565
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13566
|
+
className: "glr-resolved-input",
|
|
13567
|
+
children: r.input
|
|
13568
|
+
}),
|
|
13569
|
+
r.input !== r.id && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
13570
|
+
children: [
|
|
13571
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13572
|
+
className: "glr-arrow",
|
|
13573
|
+
children: "\u2192"
|
|
13574
|
+
}),
|
|
13575
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
13576
|
+
className: "glr-resolved-id",
|
|
13577
|
+
children: r.id
|
|
13578
|
+
})
|
|
13579
|
+
]
|
|
13580
|
+
})
|
|
13581
|
+
]
|
|
13582
|
+
}, i))
|
|
13583
|
+
})
|
|
13584
|
+
}),
|
|
13585
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)($3b525b9a0ec89bcb$var$Bucket, {
|
|
13586
|
+
title: "Not recognised",
|
|
13587
|
+
count: unknown.length,
|
|
13588
|
+
tone: "bad",
|
|
13589
|
+
defaultOpen: false,
|
|
13590
|
+
actions: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
13591
|
+
size: "sm",
|
|
13592
|
+
variant: "outline-secondary",
|
|
13593
|
+
onClick: (e)=>{
|
|
13594
|
+
e.stopPropagation();
|
|
13595
|
+
navigator.clipboard && navigator.clipboard.writeText(unknown.join('\n'));
|
|
13596
|
+
},
|
|
13597
|
+
children: "Copy"
|
|
13598
|
+
}),
|
|
13599
|
+
children: [
|
|
13600
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("p", {
|
|
13601
|
+
className: "glr-hint",
|
|
13602
|
+
children: "No gene matches these. Matching is case-sensitive for stable IDs, so a lowercased ID may not be found even when the gene exists."
|
|
13603
|
+
}),
|
|
13604
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("pre", {
|
|
13605
|
+
className: "glr-unknown",
|
|
13606
|
+
children: unknown.join('\n')
|
|
13607
|
+
})
|
|
13608
|
+
]
|
|
13609
|
+
})
|
|
13610
|
+
]
|
|
13611
|
+
});
|
|
13612
|
+
};
|
|
13613
|
+
var $3b525b9a0ec89bcb$export$2e2bcd8739ae039 = $3b525b9a0ec89bcb$var$GeneListResolver;
|
|
13614
|
+
|
|
13615
|
+
|
|
13616
|
+
// Client for the gene-lists API ({api}/gene_lists*).
|
|
13617
|
+
//
|
|
13618
|
+
// Mirrors the shape of bundles/savedViews.js: Firebase Bearer token, one
|
|
13619
|
+
// status -> message mapper so every call site reports failures the same way, and
|
|
13620
|
+
// JSON request bodies. Deliberately a plain module rather than a redux bundle —
|
|
13621
|
+
// the feature is self-contained in UserGeneLists and shares no state.
|
|
13622
|
+
// Server-side limits, mirrored so we can say something useful instead of
|
|
13623
|
+
// surfacing a bare 400.
|
|
13624
|
+
const $4abf453405c18681$export$7fadd81f3363fc57 = 6000;
|
|
13625
|
+
const $4abf453405c18681$export$7f2835aa6d834dde = 255;
|
|
13626
|
+
// Candidate hydration passes ids through the URL. ~500 ids is a 414 (the URL
|
|
13627
|
+
// crosses ~8.5KB), so batch well under that.
|
|
13628
|
+
const $4abf453405c18681$var$HYDRATE_BATCH = 150;
|
|
13629
|
+
function $4abf453405c18681$export$cbb0be6dabd4295(action, status) {
|
|
13630
|
+
if (status === 401) return new Error(`${action} failed \u{2014} please sign in again.`);
|
|
13631
|
+
if (status === 404) return new Error(`${action} failed \u{2014} that list no longer exists.`);
|
|
13632
|
+
if (status === 405) return new Error('Gene lists are not available on this server yet.');
|
|
13633
|
+
if (status === 502 || status === 503) return new Error(`${action} failed \u{2014} the gene index is unreachable. Try again shortly.`);
|
|
13634
|
+
return new Error(`${action} failed (${status})`);
|
|
13635
|
+
}
|
|
13636
|
+
// The API returns {message} on most 4xx; prefer it, since it names the offending
|
|
13637
|
+
// field (e.g. which limit was exceeded).
|
|
13638
|
+
async function $4abf453405c18681$var$readError(res, action) {
|
|
13639
|
+
try {
|
|
13640
|
+
const body = await res.json();
|
|
13641
|
+
if (body && body.message) return new Error(body.message);
|
|
13642
|
+
} catch (e) {
|
|
13643
|
+
// not JSON — fall through to the status mapper
|
|
13644
|
+
}
|
|
13645
|
+
return $4abf453405c18681$export$cbb0be6dabd4295(action, res.status);
|
|
13646
|
+
}
|
|
13647
|
+
async function $4abf453405c18681$export$6c06d9a9d837c44e(api, ids) {
|
|
13648
|
+
const res = await fetch(`${api}/gene_lists/validate`, {
|
|
13649
|
+
method: 'POST',
|
|
13650
|
+
headers: {
|
|
13651
|
+
'Content-Type': 'application/json'
|
|
13652
|
+
},
|
|
13653
|
+
body: JSON.stringify(ids)
|
|
13654
|
+
});
|
|
13655
|
+
if (!res.ok) throw await $4abf453405c18681$var$readError(res, 'Validation');
|
|
13656
|
+
const body = await res.json();
|
|
13657
|
+
// Be strict about the contract. A server still returning the old
|
|
13658
|
+
// {ids, missing, hash} shape must fail here and say so, rather than quietly
|
|
13659
|
+
// validating nothing — which is exactly how the previous client broke.
|
|
13660
|
+
if (!body || !Array.isArray(body.resolved)) throw new Error('Unexpected response from the validation service (is the server up to date?).');
|
|
13661
|
+
return {
|
|
13662
|
+
resolved: body.resolved || [],
|
|
13663
|
+
ambiguous: body.ambiguous || [],
|
|
13664
|
+
unknown: body.unknown || []
|
|
13665
|
+
};
|
|
13666
|
+
}
|
|
13667
|
+
async function $4abf453405c18681$export$41928ff990e251ce(api, ids) {
|
|
13668
|
+
const unique = [
|
|
13669
|
+
...new Set(ids)
|
|
13670
|
+
].filter(Boolean);
|
|
13671
|
+
const out = {};
|
|
13672
|
+
for(let i = 0; i < unique.length; i += $4abf453405c18681$var$HYDRATE_BATCH){
|
|
13673
|
+
const batch = unique.slice(i, i + $4abf453405c18681$var$HYDRATE_BATCH);
|
|
13674
|
+
// {!terms} takes its values literally, so no id needs escaping, and it sits in
|
|
13675
|
+
// `fq`, where Solr local params are reliable.
|
|
13676
|
+
const fq = encodeURIComponent(`{!terms f=id}${batch.join(',')}`);
|
|
13677
|
+
// `rows` is mandatory. Without it the endpoint answers with its default of 20
|
|
13678
|
+
// and the rest of the batch silently disappears.
|
|
13679
|
+
const url = `${api}/search?q=*:*&fq=${fq}&rows=${batch.length}` + `&fl=id,name,system_name,description`;
|
|
13680
|
+
try {
|
|
13681
|
+
const res = await fetch(url);
|
|
13682
|
+
if (!res.ok) continue;
|
|
13683
|
+
const body = await res.json();
|
|
13684
|
+
const docs = body && body.response && body.response.docs || [];
|
|
13685
|
+
docs.forEach((d)=>{
|
|
13686
|
+
if (d && d.id) out[d.id] = d;
|
|
13687
|
+
});
|
|
13688
|
+
} catch (e) {
|
|
13689
|
+
// leave this batch un-hydrated
|
|
13690
|
+
}
|
|
13691
|
+
}
|
|
13692
|
+
return out;
|
|
13693
|
+
}
|
|
13694
|
+
async function $4abf453405c18681$export$87f42b3480ee6c49(api, token, { label: label, site: site, isPublic: isPublic, ids: ids }) {
|
|
13695
|
+
const res = await fetch(`${api}/gene_lists`, {
|
|
13696
|
+
method: 'POST',
|
|
13697
|
+
headers: {
|
|
13698
|
+
'Content-Type': 'application/json',
|
|
13699
|
+
Authorization: `Bearer ${token}`
|
|
13700
|
+
},
|
|
13701
|
+
body: JSON.stringify({
|
|
13702
|
+
label: label,
|
|
13703
|
+
site: site,
|
|
13704
|
+
isPublic: !!isPublic,
|
|
13705
|
+
ids: ids
|
|
13706
|
+
})
|
|
13707
|
+
});
|
|
13708
|
+
if (!res.ok) throw await $4abf453405c18681$var$readError(res, 'Save');
|
|
13709
|
+
return res.json();
|
|
13710
|
+
}
|
|
13711
|
+
|
|
13712
|
+
|
|
13180
13713
|
const $0f50f369018a42ef$var$formatDate = (value)=>{
|
|
13181
13714
|
if (!value) return '';
|
|
13182
13715
|
const d = new Date(value);
|
|
@@ -13727,14 +14260,20 @@ const $0f50f369018a42ef$var$GeneListDisplayComponent = (props)=>{
|
|
|
13727
14260
|
};
|
|
13728
14261
|
const $0f50f369018a42ef$var$GeneListComponent = (props)=>{
|
|
13729
14262
|
const [geneList, setGeneList] = (0, $gXNCa$react.useState)('');
|
|
13730
|
-
const [listHash, setListHash] = (0, $gXNCa$react.useState)(null);
|
|
13731
|
-
const [validationError, setValidationError] = (0, $gXNCa$react.useState)([]);
|
|
13732
14263
|
const [listName, setListName] = (0, $gXNCa$react.useState)('');
|
|
13733
14264
|
const [listIsPublic, setListIsPublic] = (0, $gXNCa$react.useState)(false);
|
|
13734
|
-
|
|
14265
|
+
// The three-bucket validate response, the user's narrowing decisions, and the
|
|
14266
|
+
// hydrated gene docs used to label candidates.
|
|
14267
|
+
const [validation, setValidation] = (0, $gXNCa$react.useState)(null);
|
|
14268
|
+
const [choices, setChoices] = (0, $gXNCa$react.useState)({});
|
|
14269
|
+
const [meta, setMeta] = (0, $gXNCa$react.useState)({});
|
|
13735
14270
|
const [errorMessage, setErrorMessage] = (0, $gXNCa$react.useState)('');
|
|
13736
|
-
const [
|
|
13737
|
-
const [
|
|
14271
|
+
const [notice, setNotice] = (0, $gXNCa$react.useState)('');
|
|
14272
|
+
const [loading, setLoading] = (0, $gXNCa$react.useState)(false);
|
|
14273
|
+
const [saving, setSaving] = (0, $gXNCa$react.useState)(false);
|
|
14274
|
+
// null, not {}: the initial value is read to decide whether Save is enabled, and
|
|
14275
|
+
// an empty object is truthy, which used to offer Save to a logged-out visitor.
|
|
14276
|
+
const [user, setUser] = (0, $gXNCa$react.useState)(null);
|
|
13738
14277
|
const auth = props.auth;
|
|
13739
14278
|
(0, $gXNCa$react.useEffect)(()=>{
|
|
13740
14279
|
if (!auth) return;
|
|
@@ -13743,6 +14282,38 @@ const $0f50f369018a42ef$var$GeneListComponent = (props)=>{
|
|
|
13743
14282
|
}, [
|
|
13744
14283
|
auth
|
|
13745
14284
|
]);
|
|
14285
|
+
// The single source of truth for what gets saved. An ambiguous input the user
|
|
14286
|
+
// never touched contributes ALL of its matches, so nothing is silently dropped;
|
|
14287
|
+
// narrowing is a choice, not a requirement.
|
|
14288
|
+
// `choices[i] === undefined` means untouched -> keep all. `choices[i] === []`
|
|
14289
|
+
// means the user explicitly dropped the row, so this must NOT be `|| a.matches`:
|
|
14290
|
+
// an empty array is falsy, and that would silently re-add everything they just
|
|
14291
|
+
// removed.
|
|
14292
|
+
const keptFor = (i, a)=>choices[i] === undefined ? a.matches : choices[i];
|
|
14293
|
+
const idsToSave = (0, $gXNCa$react.useMemo)(()=>{
|
|
14294
|
+
if (!validation) return [];
|
|
14295
|
+
const out = validation.resolved.map((r)=>r.id);
|
|
14296
|
+
validation.ambiguous.forEach((a, i)=>out.push(...keptFor(i, a)));
|
|
14297
|
+
// Mandatory, not tidiness: the same gene reaches this list twice routinely —
|
|
14298
|
+
// two inputs can resolve to one id, and a resolved id can also appear among
|
|
14299
|
+
// another input's candidates. Duplicates would inflate n_genes and change the
|
|
14300
|
+
// server's content hash, breaking the save endpoint's idempotency.
|
|
14301
|
+
return [
|
|
14302
|
+
...new Set(out)
|
|
14303
|
+
];
|
|
14304
|
+
}, [
|
|
14305
|
+
validation,
|
|
14306
|
+
choices
|
|
14307
|
+
]);
|
|
14308
|
+
// How much of the total came from conflicts, for the summary line — with
|
|
14309
|
+
// keep-all-by-default a single identifier can contribute 15 genes.
|
|
14310
|
+
const fromConflicts = (0, $gXNCa$react.useMemo)(()=>{
|
|
14311
|
+
if (!validation) return 0;
|
|
14312
|
+
return validation.ambiguous.reduce((n, a, i)=>n + keptFor(i, a).length, 0);
|
|
14313
|
+
}, [
|
|
14314
|
+
validation,
|
|
14315
|
+
choices
|
|
14316
|
+
]);
|
|
13746
14317
|
// Function to handle gene list input
|
|
13747
14318
|
const handleGeneListChange = (event)=>{
|
|
13748
14319
|
setGeneList(event.target.value);
|
|
@@ -13759,65 +14330,66 @@ const $0f50f369018a42ef$var$GeneListComponent = (props)=>{
|
|
|
13759
14330
|
};
|
|
13760
14331
|
// Function to submit gene list for validation
|
|
13761
14332
|
const handleSubmit = async ()=>{
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
14333
|
+
// The server only drops blanks and duplicates; splitting and trimming is ours
|
|
14334
|
+
// to do. Accept commas, tabs and semicolons as well as newlines.
|
|
14335
|
+
const geneArray = [
|
|
14336
|
+
...new Set(geneList.split(/[\s,;]+/).map((s)=>s.trim()).filter(Boolean))
|
|
14337
|
+
];
|
|
14338
|
+
if (geneArray.length === 0) {
|
|
14339
|
+
setErrorMessage('Paste at least one gene identifier.');
|
|
14340
|
+
return;
|
|
14341
|
+
}
|
|
14342
|
+
if (geneArray.length > (0, $4abf453405c18681$export$7fadd81f3363fc57)) {
|
|
14343
|
+
setErrorMessage(`That is ${geneArray.length.toLocaleString()} identifiers; the limit is ${(0, $4abf453405c18681$export$7fadd81f3363fc57).toLocaleString()}.`);
|
|
13765
14344
|
return;
|
|
13766
14345
|
}
|
|
13767
|
-
setErrorMessage('');
|
|
13768
|
-
|
|
14346
|
+
setErrorMessage('');
|
|
14347
|
+
setNotice('');
|
|
14348
|
+
setLoading(true);
|
|
13769
14349
|
try {
|
|
13770
|
-
const
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
setListHash(result.hash);
|
|
13780
|
-
setValidatedList(result.ids);
|
|
13781
|
-
setValidationError(result.missing);
|
|
13782
|
-
} else // Handle errors from validation
|
|
13783
|
-
alert('Error during validation.');
|
|
14350
|
+
const result = await (0, $4abf453405c18681$export$6c06d9a9d837c44e)(props.api, geneArray);
|
|
14351
|
+
setValidation(result);
|
|
14352
|
+
setChoices({});
|
|
14353
|
+
// Candidates are only choosable if the user can see what they are, so pull
|
|
14354
|
+
// species/description for every candidate id. Best-effort and non-blocking:
|
|
14355
|
+
// the resolver renders bare ids until this lands.
|
|
14356
|
+
const candidateIds = result.ambiguous.reduce((acc, a)=>acc.concat(a.matches), []);
|
|
14357
|
+
if (candidateIds.length) (0, $4abf453405c18681$export$41928ff990e251ce)(props.api, candidateIds).then(setMeta).catch(()=>{});
|
|
14358
|
+
else setMeta({});
|
|
13784
14359
|
} catch (error) {
|
|
13785
|
-
|
|
14360
|
+
setValidation(null);
|
|
14361
|
+
setErrorMessage(error.message || 'There was an error with the validation service.');
|
|
13786
14362
|
} finally{
|
|
13787
|
-
setLoading(false);
|
|
14363
|
+
setLoading(false);
|
|
13788
14364
|
}
|
|
13789
14365
|
};
|
|
13790
14366
|
// Function to save the validated gene list
|
|
13791
14367
|
const handleSaveList = async ()=>{
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
|
|
13795
|
-
site: props.site,
|
|
13796
|
-
n_genes: validatedList.length,
|
|
13797
|
-
isPublic: listIsPublic
|
|
13798
|
-
};
|
|
13799
|
-
const queryString = new URLSearchParams(queryParams).toString();
|
|
13800
|
-
const token = await user.getIdToken();
|
|
14368
|
+
setErrorMessage('');
|
|
14369
|
+
setNotice('');
|
|
14370
|
+
setSaving(true);
|
|
13801
14371
|
try {
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
14372
|
+
// Inside the try: getIdToken() rejects when the session has expired, and
|
|
14373
|
+
// that used to escape as an unhandled rejection with no UI feedback.
|
|
14374
|
+
const token = await user.getIdToken();
|
|
14375
|
+
const result = await (0, $4abf453405c18681$export$87f42b3480ee6c49)(props.api, token, {
|
|
14376
|
+
label: listName,
|
|
14377
|
+
site: props.site,
|
|
14378
|
+
isPublic: listIsPublic,
|
|
14379
|
+
ids: idsToSave
|
|
13808
14380
|
});
|
|
13809
|
-
if (!response.ok) throw new Error('Failed to save list');
|
|
13810
|
-
const result = await response.json();
|
|
13811
|
-
console.log(result);
|
|
13812
14381
|
setGeneList('');
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
14382
|
+
setValidation(null);
|
|
14383
|
+
setChoices({});
|
|
14384
|
+
setMeta({});
|
|
13816
14385
|
setListName('');
|
|
13817
14386
|
setListIsPublic(false);
|
|
14387
|
+
setNotice(`Saved \u{201C}${listName}\u{201D} \u{2014} ${(result.n_genes || idsToSave.length).toLocaleString()} genes.`);
|
|
13818
14388
|
if (props.onListSaved) props.onListSaved();
|
|
13819
14389
|
} catch (error) {
|
|
13820
|
-
|
|
14390
|
+
setErrorMessage(error.message || 'Failed to save the gene list.');
|
|
14391
|
+
} finally{
|
|
14392
|
+
setSaving(false);
|
|
13821
14393
|
}
|
|
13822
14394
|
};
|
|
13823
14395
|
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
@@ -13836,7 +14408,7 @@ const $0f50f369018a42ef$var$GeneListComponent = (props)=>{
|
|
|
13836
14408
|
rows: 10,
|
|
13837
14409
|
value: geneList,
|
|
13838
14410
|
onChange: handleGeneListChange,
|
|
13839
|
-
placeholder: `Paste or drop your gene list here (Maximum: ${$
|
|
14411
|
+
placeholder: `Paste or drop your gene list here (Maximum: ${(0, $4abf453405c18681$export$7fadd81f3363fc57)} IDs)`,
|
|
13840
14412
|
onDrop: handleDrop,
|
|
13841
14413
|
onDragOver: handleDragOver
|
|
13842
14414
|
})
|
|
@@ -13844,8 +14416,16 @@ const $0f50f369018a42ef$var$GeneListComponent = (props)=>{
|
|
|
13844
14416
|
}),
|
|
13845
14417
|
errorMessage && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Alert), {
|
|
13846
14418
|
variant: "danger",
|
|
14419
|
+
dismissible: true,
|
|
14420
|
+
onClose: ()=>setErrorMessage(''),
|
|
13847
14421
|
children: errorMessage
|
|
13848
14422
|
}),
|
|
14423
|
+
notice && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Alert), {
|
|
14424
|
+
variant: "success",
|
|
14425
|
+
dismissible: true,
|
|
14426
|
+
onClose: ()=>setNotice(''),
|
|
14427
|
+
children: notice
|
|
14428
|
+
}),
|
|
13849
14429
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
13850
14430
|
variant: "primary",
|
|
13851
14431
|
onClick: handleSubmit,
|
|
@@ -13869,53 +14449,55 @@ const $0f50f369018a42ef$var$GeneListComponent = (props)=>{
|
|
|
13869
14449
|
})
|
|
13870
14450
|
]
|
|
13871
14451
|
}),
|
|
13872
|
-
!loading &&
|
|
13873
|
-
className: "validation-summary mt-3 p-3 border rounded bg-light",
|
|
14452
|
+
!loading && validation && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
13874
14453
|
children: [
|
|
14454
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $3b525b9a0ec89bcb$export$2e2bcd8739ae039), {
|
|
14455
|
+
validation: validation,
|
|
14456
|
+
choices: choices,
|
|
14457
|
+
meta: meta,
|
|
14458
|
+
onChange: setChoices
|
|
14459
|
+
}),
|
|
13875
14460
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
14461
|
+
className: "glr-summary",
|
|
13876
14462
|
children: [
|
|
13877
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.
|
|
13878
|
-
className: "
|
|
13879
|
-
children:
|
|
13880
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
13881
|
-
children: validatedList.length
|
|
13882
|
-
}),
|
|
13883
|
-
" valid"
|
|
13884
|
-
]
|
|
14463
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
14464
|
+
className: "glr-summary-total",
|
|
14465
|
+
children: idsToSave.length.toLocaleString()
|
|
13885
14466
|
}),
|
|
13886
|
-
|
|
13887
|
-
|
|
14467
|
+
' ',
|
|
14468
|
+
"gene",
|
|
14469
|
+
idsToSave.length === 1 ? '' : 's',
|
|
14470
|
+
" will be saved",
|
|
14471
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
14472
|
+
className: "glr-summary-detail",
|
|
13888
14473
|
children: [
|
|
13889
|
-
|
|
13890
|
-
|
|
14474
|
+
validation.resolved.length.toLocaleString(),
|
|
14475
|
+
" identifier",
|
|
14476
|
+
validation.resolved.length === 1 ? '' : 's',
|
|
14477
|
+
" matched exactly one gene",
|
|
14478
|
+
validation.ambiguous.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactjsxruntime.Fragment), {
|
|
14479
|
+
children: [
|
|
14480
|
+
'; ',
|
|
14481
|
+
validation.ambiguous.length.toLocaleString(),
|
|
14482
|
+
" conflicting identifier",
|
|
14483
|
+
validation.ambiguous.length === 1 ? '' : 's',
|
|
14484
|
+
" contributing",
|
|
14485
|
+
' ',
|
|
14486
|
+
fromConflicts.toLocaleString(),
|
|
14487
|
+
" candidate",
|
|
14488
|
+
fromConflicts === 1 ? '' : 's',
|
|
14489
|
+
Object.keys(choices).length > 0 && ` (${Object.keys(choices).length.toLocaleString()} narrowed)`
|
|
14490
|
+
]
|
|
13891
14491
|
}),
|
|
13892
|
-
|
|
14492
|
+
'. ',
|
|
14493
|
+
"Genes matched more than once are counted once."
|
|
13893
14494
|
]
|
|
13894
14495
|
})
|
|
13895
14496
|
]
|
|
13896
|
-
}),
|
|
13897
|
-
validationError.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("details", {
|
|
13898
|
-
className: "mt-2",
|
|
13899
|
-
children: [
|
|
13900
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("summary", {
|
|
13901
|
-
style: {
|
|
13902
|
-
cursor: 'pointer'
|
|
13903
|
-
},
|
|
13904
|
-
children: "Show unrecognized IDs"
|
|
13905
|
-
}),
|
|
13906
|
-
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("pre", {
|
|
13907
|
-
className: "small mt-2 mb-0",
|
|
13908
|
-
style: {
|
|
13909
|
-
maxHeight: 150,
|
|
13910
|
-
overflow: 'auto'
|
|
13911
|
-
},
|
|
13912
|
-
children: validationError.join('\n')
|
|
13913
|
-
})
|
|
13914
|
-
]
|
|
13915
14497
|
})
|
|
13916
14498
|
]
|
|
13917
14499
|
}),
|
|
13918
|
-
!loading &&
|
|
14500
|
+
!loading && idsToSave.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
13919
14501
|
className: "save-list mt-4",
|
|
13920
14502
|
children: [
|
|
13921
14503
|
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Form).Group, {
|
|
@@ -13942,7 +14524,8 @@ const $0f50f369018a42ef$var$GeneListComponent = (props)=>{
|
|
|
13942
14524
|
user ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
13943
14525
|
variant: "primary",
|
|
13944
14526
|
onClick: handleSaveList,
|
|
13945
|
-
|
|
14527
|
+
disabled: saving || !listName.trim(),
|
|
14528
|
+
children: saving ? "Saving\u2026" : 'Save Gene List'
|
|
13946
14529
|
}) : /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
13947
14530
|
variant: "secondary",
|
|
13948
14531
|
disabled: true,
|