Optically center weather font glyphs
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Component, createSignal, For, Show, Signal } from "solid-js";
|
||||
import { weatherIcons } from "./iconConsts";
|
||||
import "../../css/weatherIcons.css";
|
||||
import { WeatherGlyph } from "./WeatherGlyph";
|
||||
|
||||
export const IconInputs: Component<{cities: string[]; weatherIconsSignal: Signal<Record<string, string>>}> = (props) => {
|
||||
const [icons, setIcons] = props.weatherIconsSignal;
|
||||
@@ -10,9 +11,9 @@ export const IconInputs: Component<{cities: string[]; weatherIconsSignal: Signal
|
||||
const applyAll = () => setIcons(Object.fromEntries(props.cities.map(city => [city, selected()])));
|
||||
|
||||
return <section class="symbolEditor">
|
||||
<div class="symbolEditorHeading"><div><strong>Weather symbols</strong><p class="fieldHint">Choose one symbol, apply it broadly, then update the exceptions below.</p></div><div class="bulkSymbol"><span class="selectedPreview">{selected()}</span><button type="button" onClick={applyAll}>Apply to all cities</button></div></div>
|
||||
<div class="symbolPalette" role="radiogroup" aria-label="Choose weather symbol"><For each={weatherIcons}>{icon => <button type="button" role="radio" aria-checked={selected() === icon} aria-label={`Weather symbol ${icon}`} class="weatherIconButton" classList={{selected: selected() === icon}} onClick={() => setSelected(icon)}>{icon}</button>}</For></div>
|
||||
<div class="symbolEditorHeading"><div><strong>Weather symbols</strong><p class="fieldHint">Choose one symbol, apply it broadly, then update the exceptions below.</p></div><div class="bulkSymbol"><span class="selectedPreview"><WeatherGlyph glyph={selected()} size={40} tone="inverse"/></span><button type="button" onClick={applyAll}>Apply to all cities</button></div></div>
|
||||
<div class="symbolPalette" role="radiogroup" aria-label="Choose weather symbol"><For each={weatherIcons}>{icon => <button type="button" role="radio" aria-checked={selected() === icon} aria-label={`Weather symbol ${icon}`} class="weatherIconButton" classList={{selected: selected() === icon}} onClick={() => setSelected(icon)}><WeatherGlyph glyph={icon} size={40} tone={selected() === icon ? "inverse" : "default"}/></button>}</For></div>
|
||||
<div class="assignmentHeading"><strong>City assignments</strong><span>Use the selected symbol for each exception.</span></div>
|
||||
<div class="citySymbols"><For each={props.cities}>{city => <div class="citySymbolRow"><span class="cityName">{city}</span><span class="currentSymbol" classList={{empty: !icons()[city]}}><Show when={icons()[city]} fallback="—">{icons()[city]}</Show></span><button type="button" class="assignAction" onClick={() => assign(city)}>Use selected</button><button type="button" class="clearSymbol" disabled={!icons()[city]} onClick={() => clear(city)} aria-label={`Clear ${city} symbol`}>×</button></div>}</For></div>
|
||||
<div class="citySymbols"><For each={props.cities}>{city => <div class="citySymbolRow"><span class="cityName">{city}</span><span class="currentSymbol" classList={{empty: !icons()[city]}}><Show when={icons()[city]} fallback="—">{icon => <WeatherGlyph glyph={icon()} size={40}/>}</Show></span><button type="button" class="assignAction" onClick={() => assign(city)}>Use selected</button><button type="button" class="clearSymbol" disabled={!icons()[city]} onClick={() => clear(city)} aria-label={`Clear ${city} symbol`}>×</button></div>}</For></div>
|
||||
</section>;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Component, createEffect, onMount } from "solid-js";
|
||||
|
||||
export const WeatherGlyph: Component<{glyph: string; size?: number; tone?: "default" | "inverse"}> = (props) => {
|
||||
let canvas!: HTMLCanvasElement;
|
||||
const boxSize = () => props.size ?? 40;
|
||||
const draw = async () => {
|
||||
const size = boxSize();
|
||||
await document.fonts.load(`${Math.round(size * 0.82)}px "Daira by LTV grafika"`);
|
||||
canvas.width = size; canvas.height = size;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.clearRect(0, 0, size, size);
|
||||
ctx.font = `normal ${Math.round(size * 0.82)}px "Daira by LTV grafika"`;
|
||||
ctx.fillStyle = props.tone === "inverse" ? "#ffffff" : "#17212d";
|
||||
ctx.textAlign = "left"; ctx.textBaseline = "alphabetic";
|
||||
const metrics = ctx.measureText(props.glyph);
|
||||
const x = size / 2 + (metrics.actualBoundingBoxLeft - metrics.actualBoundingBoxRight) / 2;
|
||||
const y = size / 2 + (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2;
|
||||
ctx.fillText(props.glyph, x, y);
|
||||
};
|
||||
onMount(draw);
|
||||
createEffect(() => { props.glyph; props.tone; boxSize(); if (canvas) void draw(); });
|
||||
return <canvas ref={canvas} class="weatherGlyph" style={{width: `${boxSize()}px`, height: `${boxSize()}px`}} aria-hidden="true"/>;
|
||||
};
|
||||
Reference in New Issue
Block a user