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 | 2x 2111x 2111x 2029x 1412x 1412x 176x 176x 2x 2x 2111x 2111x 2111x 2111x 2111x 2111x 2111x 2111x 2111x 2111x 2111x 2111x 2111x 2092x 2092x 2092x 2092x 2092x 2092x 2111x 2111x | /**
* A store's mark: a square with a short word in it, in the store's colour, and
* an image over the word for the stores that have one.
*
* All four shipped stores have one — RSG's drawn, the other three their own
* projects' (`ui/stores/registry.ts` on `StoreMark.image`) — so on those the
* word is what shows while the image loads and what is left if it fails. A
* store connected by URL has no image and is the coloured square with its
* initials in it, which is all a mark has to do anyway: be recognised in a row
* of five, and again smaller in the drawer's head.
*
* The word is scaled by its length so *ENCODE* and *IGV* fill the same square:
* six letters at the size three would take spill over the edge.
*/
import { el } from '../dom'
import type { StoreMark } from './registry'
/** How large the letters are, as a fraction of the square's edge, by length. */
function scale(text: string): number {
if (text.length <= 1) return 0.62
if (text.length <= 3) return 0.42
if (text.length <= 4) return 0.32
if (text.length <= 5) return 0.26
return 0.22
}
/** Below this edge the short form is drawn, where there is one. */
const SHORT_BELOW = 40
/**
* The mark, at `size` CSS pixels a side.
*
* `aria-hidden`: every mark stands beside its store's name, and a screen reader
* announcing "RSG, RSG lab data" would be reading it twice.
*/
export function storeMark(mark: StoreMark, size: number): HTMLElement {
const text = size < SHORT_BELOW && mark.short !== undefined ? mark.short : mark.text
const node = el('span', {
class: 'store-mark',
attrs: { 'aria-hidden': 'true' },
style: {
width: `${size}px`,
height: `${size}px`,
background: mark.colour,
fontSize: `${Math.round(size * scale(text))}px`,
},
})
node.textContent = text
if (mark.image !== undefined) {
const image = el('img', { class: 'store-mark-image', attrs: { src: mark.image, alt: '' } })
// The letters stay underneath; an image that will not load leaves them.
image.addEventListener('error', () => image.remove())
// And the colour goes once one has, because otherwise it shows *through*
// the image. Both boxes are rounded by the same radius and both corners
// are antialiased, so along each of the four arcs the image covers its
// pixel only partly and the square underneath is what fills the rest — a
// thin band of the store's colour round every corner, which on the three
// marks with pale corners reads as a border the tile does not have. The
// colour is the fallback, so nothing is lost by dropping it the moment
// there is something in front of it.
image.addEventListener('load', () => {
node.style.background = 'transparent'
})
node.appendChild(image)
}
return node
}
|