mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
feat(settings): clock format setting (Auto / 24h / 12h) (#742)
* feat(settings): clock format setting (Auto / 24h / 12h) Reported on the Psysonic Discord — the Queue side panel's ETA label and the sleep-timer preview both render via `formatClockTime`, which just calls `toLocaleTimeString` and so follows the user's system locale. On en-US that means AM/PM, with no in-app way out. Add a tri-state **Clock Format** setting under **Settings → System → App Behavior**: * `auto` (default) — keep the existing locale-driven behaviour, so bestehende installs are unaffected on first launch. * `24h` — force 24-hour wall-clock output everywhere `formatClockTime` is used. * `12h` — force AM/PM output. Wired through `authStore` (`clockFormat`, `setClockFormat`), exposed via `CustomSelect` in `SystemTab`, and threaded into the two consumers (`QueueHeader`, `PlaybackDelayModal`) so they re-render on change. `formatClockTime` itself stays a pure helper — it accepts the setting as an optional second argument and maps it to `hour12`. Locale coverage: all nine bundled locales (en, de, es, fr, nl, nb, ru, zh, ro) get the four new settings strings. Pin tests added for the `setClockFormat` setter and the `hour12` mapping in `formatClockTime`. * docs(changelog): clock format setting + contributors (PR #742)
This commit is contained in:
committed by
GitHub
parent
02e23b5755
commit
606a150e01
@@ -3,6 +3,7 @@ import { createPortal } from 'react-dom';
|
||||
import { X, Moon, Sunrise } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
@@ -153,9 +154,10 @@ export default function PlaybackDelayModal({ open, onClose, anchorRef }: Playbac
|
||||
|
||||
// Live preview: seconds that would be applied right now if the user clicked.
|
||||
// Priority: hovered chip → typed custom minutes → nothing.
|
||||
const clockFormat = useAuthStore(s => s.clockFormat);
|
||||
const previewSeconds = hoverSeconds ?? customSeconds;
|
||||
const previewAtMs = previewSeconds != null ? nowTick + previewSeconds * 1000 : null;
|
||||
const previewClock = previewAtMs != null ? formatClockTime(previewAtMs) : null;
|
||||
const previewClock = previewAtMs != null ? formatClockTime(previewAtMs, clockFormat) : null;
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useMemo } from 'react';
|
||||
import { ChevronDown, ListMusic } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { usePlayerStore } from '../../store/playerStore';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import type { Track } from '../../store/playerStoreTypes';
|
||||
import type { DurationMode } from '../../utils/componentHelpers/queuePanelHelpers';
|
||||
import { formatLongDuration } from '../../utils/format/formatDuration';
|
||||
@@ -24,6 +25,7 @@ export function QueueHeader({
|
||||
}: Props) {
|
||||
const currentTime = usePlayerStore((s) => Math.floor(s.currentTime / 30) * 30);
|
||||
const isPlaying = usePlayerStore((s) => s.isPlaying);
|
||||
const clockFormat = useAuthStore((s) => s.clockFormat);
|
||||
|
||||
const totalSecs = useMemo(() =>
|
||||
queue.reduce((acc: number, track: Track) => acc + (track.duration || 0), 0),
|
||||
@@ -40,7 +42,7 @@ export function QueueHeader({
|
||||
if (queue.length > 0) {
|
||||
if (durationMode === 'total') dur = formatLongDuration(Math.floor(totalSecs));
|
||||
else if (durationMode === 'remaining') dur = `-${formatLongDuration(Math.floor(remainingSecs))}`;
|
||||
else dur = formatClockTime(Date.now() + remainingSecs * 1000);
|
||||
else dur = formatClockTime(Date.now() + remainingSecs * 1000, clockFormat);
|
||||
}
|
||||
|
||||
const nextMode: DurationMode =
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AppWindow, ChevronDown, Download, ExternalLink, Globe, HardDrive, Info,
|
||||
import { version as appVersion } from '../../../package.json';
|
||||
import i18n from '../../i18n';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import type { LoggingMode } from '../../store/authStoreTypes';
|
||||
import type { ClockFormat, LoggingMode } from '../../store/authStoreTypes';
|
||||
import { IS_LINUX } from '../../utils/platform';
|
||||
import { showToast } from '../../utils/ui/toast';
|
||||
import { AboutPsysonicBrandHeader } from '../AboutPsysonicLol';
|
||||
@@ -112,6 +112,24 @@ export function SystemTab() {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="settings-section-divider" />
|
||||
<div className="settings-toggle-row">
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontWeight: 500 }}>{t('settings.clockFormat')}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{t('settings.clockFormatDesc')}</div>
|
||||
</div>
|
||||
<div style={{ minWidth: 160 }}>
|
||||
<CustomSelect
|
||||
value={auth.clockFormat}
|
||||
onChange={(v) => auth.setClockFormat(v as ClockFormat)}
|
||||
options={[
|
||||
{ value: 'auto', label: t('settings.clockFormatAuto') },
|
||||
{ value: '24h', label: t('settings.clockFormatTwentyFour') },
|
||||
{ value: '12h', label: t('settings.clockFormatTwelve') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSubSection>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user