Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | 2x 2x 2x 2x | /**
* Which genome of a metagenome to edit (SPECS §27.10).
*
* The stage is on one genome at a time: a metagenome of twenty thousand
* contigs has no picture of the stage's kind — a cell never holds two sequences
* — and a curator scaffolds a MAG, not a community. So the stage asks, where
* nothing in view says which: the genomes as the file lists them, longest
* first, each with its size, its contigs and what has been done to it, and a
* filter for a file of hundreds. `__unbinned__` is not one of them (§7.4): a
* contig of it is brought into a genome from that genome's stage.
*/
import { fmt } from '../units'
import { el } from '../ui/dom'
import { Modal } from '../ui/Modal'
/** A genome the picker offers. */
export interface PickableGenome {
name: string
/** Its length in base pairs, and how many contigs it is in. */
length: number
contigs: number
/** Steps of its edit applied, where it has one. */
steps: number
}
/**
* Ask which genome to edit.
*
* @returns The genome picked, or `null` where the dialog was closed.
*/
export function pickGenome(file: string, genomes: readonly PickableGenome[]): Promise<string | null> {
return new Promise((resolve) => {
let picked: string | null = null
const sorted = [...genomes].sort((a, b) => b.length - a.length || a.name.localeCompare(b.name))
new Modal({
title: `Edit a genome · ${file}`,
titleIsName: true,
variant: 'modal-genome-picker',
body: (close) => {
const filter = el('input', {
class: 'input genome-picker-filter',
attrs: { type: 'search', placeholder: 'Filter genomes', 'aria-label': 'Filter genomes', spellcheck: 'false' },
})
const list = el('ul', { class: 'genome-picker-list', attrs: { role: 'listbox', 'aria-label': 'Genomes' } })
const paint = (): void => {
const needle = filter.value.trim().toLowerCase()
const shown = needle === '' ? sorted : sorted.filter((genome) => genome.name.toLowerCase().includes(needle))
list.replaceChildren(
...shown.map((genome) =>
el(
'li',
{},
el(
'button',
{
class: 'genome-picker-row',
attrs: { type: 'button', role: 'option' },
title: `Edit ${genome.name}: its contigs alone, scaffolded, turned, split or moved to another genome`,
on: {
click: () => {
picked = genome.name
close()
},
},
},
el('span', { class: 'genome-picker-name', text: genome.name }),
el('span', { class: 'genome-picker-size', text: fmt.bp(genome.length) }),
el('span', { class: 'genome-picker-contigs', text: `${genome.contigs.toLocaleString('en-US')} contig${genome.contigs === 1 ? '' : 's'}` }),
el('span', {
class: 'genome-picker-steps',
text: genome.steps > 0 ? `edited · ${genome.steps} step${genome.steps === 1 ? '' : 's'}` : '',
}),
),
),
),
...(shown.length === 0 ? [el('li', { class: 'genome-picker-none', text: 'No genome by that name.' })] : []),
)
}
filter.addEventListener('input', paint)
paint()
requestAnimationFrame(() => filter.focus())
return el(
'div',
{ class: 'genome-picker' },
el('p', {
class: 'genome-picker-note',
text: 'The stage edits one genome at a time: its contigs alone, with contigs of other genomes brought in or sent out.',
}),
filter,
list,
)
},
onClose: () => resolve(picked),
})
})
}
|