evcc-io/assets/js/components/GlobalSettings/UserInterfaceSettings.vue
Alexandre JARDON 37d8aecbb2
UI: Add date format preference in user interface settings (#29321)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Geers <michael@geers.tv>
2026-08-21 15:18:51 +00:00

207 lines
5.4 KiB
Vue

<template>
<div class="container mx-0 px-0">
<FormRow id="settingsDesign" :label="$t('settings.theme.label')">
<SelectGroup
id="settingsDesign"
v-model="theme"
class="w-100"
transparent
:options="
THEMES.map((value) => ({
value,
name: $t(`settings.theme.${value}`),
}))
"
equal-width
/>
</FormRow>
<FormRow id="settingsLanguage" :label="$t('settings.language.label')">
<select
id="settingsLanguage"
v-model="language"
class="form-select form-select-sm w-75"
>
<option value="">{{ $t("settings.language.auto") }}</option>
<option v-for="option in languageOptions" :key="option.value" :value="option.value">
{{ option.name }}
</option>
</select>
</FormRow>
<FormRow id="settingsUnit" :label="$t('settings.unit.label')">
<SelectGroup
id="settingsUnit"
v-model="unit"
class="w-75"
transparent
:options="
UNITS.map((value) => ({
value,
name: $t(`settings.unit.${value}`),
}))
"
:aria-label="$t('settings.unit.label')"
equal-width
/>
</FormRow>
<FormRow id="settingsTimeFormat" :label="$t('settings.time.label')">
<SelectGroup
id="settingsTimeFormat"
v-model="timeFormat"
class="w-75"
transparent
:options="
TIME_FORMATS.map((value) => ({
value,
name: $t(`settings.time.${value}h`),
}))
"
:aria-label="$t('settings.time.label')"
equal-width
/>
</FormRow>
<FormRow id="settingsDateFormat" :label="$t('settings.dateFormat.label')">
<select
id="settingsDateFormat"
v-model="dateFormat"
class="form-select form-select-sm w-75"
>
<option value="">{{ $t("settings.dateFormat.auto") }}</option>
<option value="dmy">{{ $t("settings.dateFormat.dmy") }}</option>
<option value="mdy">{{ $t("settings.dateFormat.mdy") }}</option>
<option value="ymd">{{ $t("settings.dateFormat.ymd") }}</option>
</select>
<div class="form-text evcc-gray">{{ dateFormatExample }}</div>
</FormRow>
<FormRow v-if="loadpoints.length" :label="$t('settings.loadpoints.label')">
<LoadpointOrderSettings :loadpoints="loadpoints" />
</FormRow>
<FormRow v-if="fullscreenAvailable" :label="$t('settings.fullscreen.label')">
<button
v-if="fullscreenActive"
class="btn btn-sm btn-outline-secondary"
@click="exitFullscreen"
>
{{ $t("settings.fullscreen.exit") }}
</button>
<button v-else class="btn btn-sm btn-outline-secondary" @click="enterFullscreen">
{{ $t("settings.fullscreen.enter") }}
</button>
</FormRow>
<div class="small text-muted mb-3">
{{ $t("settings.deviceInfo") }}
</div>
</div>
</template>
<script lang="ts">
import FormRow from "../Helper/FormRow.vue";
import SelectGroup from "../Helper/SelectGroup.vue";
import LoadpointOrderSettings from "./LoadpointOrderSettings.vue";
import {
getLocalePreference,
setLocalePreference,
LOCALES,
removeLocalePreference,
} from "@/i18n.ts";
import { getThemePreference, setThemePreference } from "@/theme.ts";
import {
getUnits,
setUnits,
is12hFormat,
set12hFormat,
getDateFormat,
setDateFormat,
} from "@/units";
import { isApp } from "@/utils/native";
import { defineComponent, type PropType } from "vue";
import { LENGTH_UNIT, THEME, TIME_FORMAT, type UiLoadpoint } from "@/types/evcc";
import formatter from "@/mixins/formatter";
import type { DateFormat } from "@/settings";
export default defineComponent({
name: "UserInterfaceSettings",
components: { FormRow, SelectGroup, LoadpointOrderSettings },
mixins: [formatter],
props: {
loadpoints: { type: Array as PropType<UiLoadpoint[]>, default: () => [] },
},
data() {
return {
theme: getThemePreference(),
language: getLocalePreference() || "",
unit: getUnits(),
timeFormat: is12hFormat() ? TIME_FORMAT.H12 : TIME_FORMAT.H24,
fullscreenActive: false,
THEMES: Object.values(THEME),
UNITS: Object.values(LENGTH_UNIT),
TIME_FORMATS: [TIME_FORMAT.H24, TIME_FORMAT.H12],
};
},
computed: {
dateFormat: {
get(): DateFormat {
return getDateFormat();
},
set(value: DateFormat) {
setDateFormat(value);
},
},
dateFormatExample(): string {
return this.fmtFullDateTime(new Date(2015, 9, 21, 16, 29));
},
languageOptions: () => {
const locales = Object.entries(LOCALES).map(([key, value]) => {
return { value: key, name: value[1] };
});
// sort by name
locales.sort((a, b) => ((a.name || "") < (b.name || "") ? -1 : 1));
return locales;
},
fullscreenAvailable: () => {
const isSupported = document.fullscreenEnabled;
const isPwa =
(navigator as any).standalone ||
window.matchMedia("(display-mode: standalone)").matches;
return isSupported && !isPwa && !isApp();
},
},
watch: {
unit(value) {
setUnits(value);
},
timeFormat(value) {
set12hFormat(value === TIME_FORMAT.H12);
},
theme(value) {
setThemePreference(value);
},
language(value) {
const i18n = this.$root?.$i18n;
if (!i18n) return;
else if (value) {
setLocalePreference(i18n, value);
} else {
removeLocalePreference(i18n);
}
},
},
mounted() {
document.addEventListener("fullscreenchange", this.fullscreenChange);
},
unmounted() {
document.removeEventListener("fullscreenchange", this.fullscreenChange);
},
methods: {
isApp,
enterFullscreen() {
document.documentElement.requestFullscreen();
},
exitFullscreen() {
document.exitFullscreen();
},
fullscreenChange() {
this.fullscreenActive = !!document.fullscreenElement;
},
},
});
</script>