All files / owid-grapher/grapher/header Header.tsx

98.4% Statements 185/188
76% Branches 19/25
100% Functions 15/15
98.4% Lines 185/188

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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 1601x 1x 1x 1x 1x 1x 1x 1x 1x 1x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x     36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x   36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 18x 18x 18x 18x 18x 18x 18x 36x 36x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 36x 36x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 18x 36x 1x
import * as React from "react"
import { TextWrap } from "../text/TextWrap"
import { computed } from "mobx"
import { observer } from "mobx-react"
import { Logo } from "../captionedChart/Logos"
import { HeaderManager } from "./HeaderManager"
import { BASE_FONT_SIZE } from "../core/GrapherConstants"
import { DEFAULT_BOUNDS } from "../../clientUtils/Bounds"
 
@observer
export class Header extends React.Component<{
    manager: HeaderManager
    maxWidth?: number
}> {
    @computed private get manager(): HeaderManager {
        return this.props.manager
    }
 
    @computed private get fontSize(): number {
        return this.manager.fontSize ?? BASE_FONT_SIZE
    }
 
    @computed private get maxWidth(): number {
        return this.props.maxWidth ?? DEFAULT_BOUNDS.width
    }
 
    @computed private get titleText(): string {
        return this.manager.currentTitle ?? ""
    }
 
    @computed private get subtitleText(): string {
        return this.manager.subtitle ?? ""
    }
 
    @computed get logo(): Logo | undefined {
        const { manager } = this
        if (manager.hideLogo) return undefined
 
        return new Logo({
            logo: manager.logo as any,
            isLink: !!manager.shouldLinkToOwid,
            fontSize: this.fontSize,
        })
    }
 
    @computed private get logoWidth(): number {
        return this.logo ? this.logo.width : 0
    }
    @computed private get logoHeight(): number {
        return this.logo ? this.logo.height : 0
    }
 
    @computed get title(): TextWrap {
        const { logoWidth } = this
        const maxWidth = this.maxWidth - logoWidth - 20
 
        // Try to fit the title into a single line if possible-- but not if it would make the text super small
        let title: TextWrap
        let fontScale = 1.4
        while (true) {
            title = new TextWrap({
                maxWidth,
                fontSize: fontScale * this.fontSize,
                text: this.titleText,
                lineHeight: 1,
            })
            if (fontScale <= 1.2 || title.lines.length <= 1) break
            fontScale -= 0.05
        }
 
        return new TextWrap({
            maxWidth,
            fontSize: fontScale * this.fontSize,
            text: this.titleText,
            lineHeight: 1,
        })
    }
 
    titleMarginBottom = 4
 
    @computed get subtitleWidth(): number {
        // If the subtitle is entirely below the logo, we can go underneath it
        return this.title.height > this.logoHeight
            ? this.maxWidth
            : this.maxWidth - this.logoWidth - 10
    }
 
    @computed get subtitle(): TextWrap {
        return new TextWrap({
            maxWidth: this.subtitleWidth,
            fontSize: 0.8 * this.fontSize,
            text: this.subtitleText,
            lineHeight: 1.2,
            linkifyText: true,
        })
    }
 
    @computed get height(): number {
        if (this.manager.isMediaCard) return 0
 
        return Math.max(
            this.title.height + this.subtitle.height + this.titleMarginBottom,
            this.logoHeight
        )
    }
 
    renderStatic(x: number, y: number): JSX.Element | null {
        const { title, logo, subtitle, manager, maxWidth } = this
 
        if (manager.isMediaCard) return null
 
        return (
            <g className="HeaderView">
                {logo &&
                    logo.height > 0 &&
                    logo.renderSVG(x + maxWidth - logo.width, y)}
                <a
                    href={manager.canonicalUrl}
                    style={{
                        fontFamily:
                            '"Playfair Display", Georgia, "Times New Roman", serif',
                    }}
                    target="_blank"
                    rel="noopener"
                >
                    {title.render(x, y, { fill: "#555" })}
                </a>
                {subtitle.render(x, y + title.height + this.titleMarginBottom, {
                    fill: "#666",
                })}
            </g>
        )
    }
 
    render(): JSX.Element {
        const { manager } = this
 
        const titleStyle = {
            ...this.title.htmlStyle,
            marginBottom: this.titleMarginBottom,
        }
 
        const subtitleStyle = {
            ...this.subtitle.htmlStyle,
            // make sure there are no scrollbars on subtitle
            overflowY: "hidden",
        }
 
        return (
            <div className="HeaderHTML">
                {this.logo && this.logo.renderHTML()}
                <a href={manager.canonicalUrl} target="_blank">
                    <h1 style={titleStyle}>{this.title.renderHTML()}</h1>
                </a>
                <p style={subtitleStyle}>{this.subtitle.renderHTML()}</p>
            </div>
        )
    }
}