mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 15:51:00 +00:00
feat(mobile): phone styling for tours list, trip settings and admin cards
The tours list in the places browser now uses the same rows as places: a grade or type tile, the day plan's figure badges and a day badge or plus on the right. A shared "Planned on desktop" badge replaces the grey hint box there and sits under the road trip's empty stage. Trip settings and trip edit open phone sheets instead of the desktop dialogs; the edit sheet gains the day count of a dateless trip and the reminder. Questions are asked in phone sheets, not the browser. Admin on the phone: the paddle regions and school holiday cards are phone cards on their shared hooks, long addon descriptions fold to two lines, the addon header keeps the wordmark inside its sentence, the group bands drop the dash and the permissions actions get a row of their own. The Tours addon gets its catalog name and description in every locale. The journey map on the phone shows every layer without the switch bar.
This commit is contained in:
@@ -49,14 +49,16 @@ export function isPhotosAddon(addon: Addon): boolean {
|
||||
|
||||
/** The catalog name and description for an addon, falling back to what the server sent. */
|
||||
export function getAddonLabel(t: (key: string) => string, addon: Addon): { name: string; description: string } {
|
||||
const nameKey = `admin.addons.catalog.${addon.id}.name`;
|
||||
const descKey = `admin.addons.catalog.${addon.id}.description`;
|
||||
const translatedName = t(nameKey);
|
||||
const translatedDescription = t(descKey);
|
||||
// Spelled out inside t() so the key check sees which catalog keys this reaches.
|
||||
const translatedName = t(`admin.addons.catalog.${addon.id}.name`);
|
||||
const translatedDescription = t(`admin.addons.catalog.${addon.id}.description`);
|
||||
|
||||
return {
|
||||
name: translatedName !== nameKey ? translatedName : addon.name,
|
||||
description: translatedDescription !== descKey ? translatedDescription : addon.description,
|
||||
name: translatedName !== `admin.addons.catalog.${addon.id}.name` ? translatedName : addon.name,
|
||||
description:
|
||||
translatedDescription !== `admin.addons.catalog.${addon.id}.description`
|
||||
? translatedDescription
|
||||
: addon.description,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,24 +5,17 @@ import { ChoiceChips, SettingRows, SETTINGS_BUTTON, SettingsHint, StatusPill } f
|
||||
import { LABEL, Segmented } from '../../shared/dialogParts';
|
||||
import { fs } from '../../shared/DialogShell';
|
||||
import { Tooltip } from '../../shared/Tooltip';
|
||||
import { AUTO_LOAD_MODES, autoLoadSizes, formatMegabytes } from './paddleRegionsModel';
|
||||
import type { usePaddleRegionsAdmin } from './usePaddleRegionsAdmin';
|
||||
|
||||
type Admin = ReturnType<typeof usePaddleRegionsAdmin>;
|
||||
|
||||
const MODES: readonly PaddleRegionAutoMode[] = ['auto', 'ask', 'off'];
|
||||
const MODE_ICONS = { auto: CloudDownload, ask: Hand, off: Power } as const;
|
||||
/** The largest region TREK loads by itself, in megabytes: a district, a state, a small country. */
|
||||
const SIZES = [100, 250, 500, 1000, 2000] as const;
|
||||
|
||||
/** A size in megabytes as the reader's language writes it, from 1000 on in gigabytes. */
|
||||
/** `formatMegabytes` in the reader's language. */
|
||||
function useMegabytes() {
|
||||
const { language } = useTranslation();
|
||||
return (mb: number) =>
|
||||
new Intl.NumberFormat(language, {
|
||||
style: 'unit',
|
||||
unit: mb >= 1000 ? 'gigabyte' : 'megabyte',
|
||||
maximumFractionDigits: mb >= 1000 ? 1 : 0,
|
||||
}).format(mb >= 1000 ? mb / 1000 : mb);
|
||||
return (mb: number) => formatMegabytes(mb, language);
|
||||
}
|
||||
|
||||
const MB = 1024 * 1024;
|
||||
@@ -36,9 +29,7 @@ export function AutoLoadSettings({ admin }: { admin: Admin }) {
|
||||
const megabytes = useMegabytes();
|
||||
const { settings, saving, save } = admin.autoLoad;
|
||||
if (!settings) return null;
|
||||
const sizes = SIZES.includes(settings.max_mb as (typeof SIZES)[number])
|
||||
? SIZES
|
||||
: [...SIZES, settings.max_mb].sort((a, b) => a - b);
|
||||
const sizes = autoLoadSizes(settings.max_mb);
|
||||
return (
|
||||
<div data-testid="paddle-auto-load" className="flex min-w-0 flex-col gap-2.5">
|
||||
<div className="min-w-0">
|
||||
@@ -49,7 +40,7 @@ export function AutoLoadSettings({ admin }: { admin: Admin }) {
|
||||
onChange={(mode) => void save({ mode })}
|
||||
label={t('admin.paddleRegions.auto.title')}
|
||||
fill
|
||||
options={MODES.map((mode) => {
|
||||
options={AUTO_LOAD_MODES.map((mode) => {
|
||||
const Icon = MODE_ICONS[mode];
|
||||
return {
|
||||
value: mode,
|
||||
|
||||
@@ -25,8 +25,8 @@ type Admin = ReturnType<typeof usePaddleRegionsAdmin>;
|
||||
* PAD-05A in the admin area: the regions TREK reads paddle obstacles for,
|
||||
* picked from Geofabrik's list, and (PAD-11C) how TREK loads the region of a
|
||||
* place a paddler plans in by itself, with the regions that wait for an admin.
|
||||
* Both the desktop and the phone admin show this one card under the addons,
|
||||
* while the Tours addon is on.
|
||||
* The desktop admin shows this card under the addons while the Tours addon
|
||||
* is on; the phone has MAdminPaddleRegionsPanel on the same hook.
|
||||
*/
|
||||
export default function PaddleRegionsPanel() {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,25 @@
|
||||
import type { PaddleRegion } from '@trek/shared';
|
||||
import type { PaddleRegion, PaddleRegionAutoMode } from '@trek/shared';
|
||||
|
||||
/** How TREK loads a paddler's region by itself, in the order the switch shows them. */
|
||||
export const AUTO_LOAD_MODES: readonly PaddleRegionAutoMode[] = ['auto', 'ask', 'off'];
|
||||
/** The largest region TREK loads by itself, in megabytes: a district, a state, a small country. */
|
||||
export const AUTO_LOAD_SIZES = [100, 250, 500, 1000, 2000] as const;
|
||||
|
||||
/** The size choices, with a stored size outside the presets kept among them in its place. */
|
||||
export function autoLoadSizes(current: number): number[] {
|
||||
return (AUTO_LOAD_SIZES as readonly number[]).includes(current)
|
||||
? [...AUTO_LOAD_SIZES]
|
||||
: [...AUTO_LOAD_SIZES, current].sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
/** A size in megabytes as the reader's language writes it, from 1000 on in gigabytes. */
|
||||
export function formatMegabytes(mb: number, language: string): string {
|
||||
return new Intl.NumberFormat(language, {
|
||||
style: 'unit',
|
||||
unit: mb >= 1000 ? 'gigabyte' : 'megabyte',
|
||||
maximumFractionDigits: mb >= 1000 ? 1 : 0,
|
||||
}).format(mb >= 1000 ? mb / 1000 : mb);
|
||||
}
|
||||
|
||||
/** A region waiting for its read or being read: the list asks again until none is. */
|
||||
export function isReading(region: Pick<PaddleRegion, 'status'>): boolean {
|
||||
|
||||
Reference in New Issue
Block a user