feat: Add week start day setting and apply to calendars and charts

This commit is contained in:
pa
2026-03-19 17:49:21 +09:00
parent f980b0ee6e
commit bbb7d596bb
7 changed files with 93 additions and 33 deletions

View File

@@ -150,7 +150,7 @@
const { t, locale } = useI18n();
const { userDialog } = storeToRefs(useUserStore());
const { isDarkMode } = storeToRefs(useAppearanceSettingsStore());
const { isDarkMode, weekStartsOn } = storeToRefs(useAppearanceSettingsStore());
const chartRef = ref(null);
const isLoading = ref(false);
@@ -190,16 +190,10 @@
t('dialog.user.activity.days.sat')
]);
// Reorder: Mon-Sun for display (row 0=Mon at top, row 6=Sun at bottom)
const displayDayLabels = computed(() => [
dayLabels.value[1], // Mon
dayLabels.value[2], // Tue
dayLabels.value[3], // Wed
dayLabels.value[4], // Thu
dayLabels.value[5], // Fri
dayLabels.value[6], // Sat
dayLabels.value[0] // Sun
]);
const displayDayLabels = computed(() => {
const start = weekStartsOn.value;
return Array.from({ length: 7 }, (_, i) => dayLabels.value[(start + i) % 7]);
});
const hourLabels = Array.from({ length: 24 }, (_, i) => `${String(i).padStart(2, '0')}:00`);
@@ -230,6 +224,7 @@
watch(() => isDarkMode.value, rebuildChart);
watch(locale, rebuildChart);
watch(weekStartsOn, rebuildChart);
watch(selectedPeriod, () => {
if (cachedTargetSessions.length > 0 && echartsInstance) {
initChart();
@@ -356,10 +351,11 @@
peakTimeText.value = peakTimeResult;
const data = [];
const wso = weekStartsOn.value;
for (let day = 0; day < 7; day++) {
for (let hour = 0; hour < 24; hour++) {
const count = grid[day][hour];
const displayDay = day === 0 ? 6 : day - 1;
const displayDay = (day - wso + 7) % 7;
data.push([hour, displayDay, count]);
}
}
@@ -685,10 +681,11 @@
if (!overlapEchartsInstance) return;
const data = [];
const wso = weekStartsOn.value;
for (let day = 0; day < 7; day++) {
for (let hour = 0; hour < 24; hour++) {
const count = result.grid[day][hour];
const displayDay = day === 0 ? 6 : day - 1;
const displayDay = (day - wso + 7) % 7;
data.push([hour, displayDay, count]);
}
}

View File

@@ -23,6 +23,15 @@
"h": "h",
"m": "m",
"s": "s"
},
"days": {
"sunday": "Sunday",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday"
}
},
"nav_tooltip": {
@@ -874,7 +883,9 @@
"time_format": "Time Format",
"time_format_24": "24 Hour",
"time_format_12": "12 Hour",
"force_iso_date_format": "Force ISO Date Format"
"force_iso_date_format": "Force ISO Date Format",
"week_starts_on": "Week Starts On",
"week_starts_on_description": "Choose the first day of the week. Affects calendars, charts, and activity views."
},
"theme_color": {
"header": "Theme Color",

View File

@@ -70,6 +70,7 @@ export const useAppearanceSettingsStore = defineStore(
const tablePageSizes = ref([...DEFAULT_TABLE_PAGE_SIZES]);
const dtHour12 = ref(false);
const dtIsoFormat = ref(false);
const weekStartsOn = ref(1);
const sidebarSortMethod1 = ref('Sort Private to Bottom');
const sidebarSortMethod2 = ref('Sort by Time in Instance');
const sidebarSortMethod3 = ref('Sort by Last Active');
@@ -159,6 +160,7 @@ export const useAppearanceSettingsStore = defineStore(
tablePageSizesConfig,
dtHour12Config,
dtIsoFormatConfig,
weekStartsOnConfig,
sidebarSortMethodsConfig,
navWidthConfig,
isSidebarGroupByInstanceConfig,
@@ -209,6 +211,7 @@ export const useAppearanceSettingsStore = defineStore(
),
configRepository.getBool('VRCX_dtHour12', false),
configRepository.getBool('VRCX_dtIsoFormat', false),
configRepository.getInt('VRCX_weekStartsOn', 1),
configRepository.getString(
'VRCX_sidebarSortMethods',
JSON.stringify([
@@ -250,7 +253,10 @@ export const useAppearanceSettingsStore = defineStore(
configRepository.getBool('VRCX_navIsCollapsed', false),
configRepository.getBool('VRCX_dataTableStriped', false),
configRepository.getBool('VRCX_showPointerOnHover', false),
configRepository.getBool('VRCX_accessibleStatusIndicators', false),
configRepository.getBool(
'VRCX_accessibleStatusIndicators',
false
),
configRepository.getBool('VRCX_useOfficialStatusColors', true),
configRepository.getBool('VRCX_showNewDashboardButton', true),
configRepository.getString(
@@ -317,6 +323,9 @@ export const useAppearanceSettingsStore = defineStore(
dtHour12.value = dtHour12Config;
dtIsoFormat.value = dtIsoFormatConfig;
weekStartsOn.value = [0, 1, 6].includes(weekStartsOnConfig)
? weekStartsOnConfig
: 1;
currentCulture.value = await AppApi.CurrentCulture();
@@ -724,6 +733,14 @@ export const useAppearanceSettingsStore = defineStore(
dtIsoFormat.value = !dtIsoFormat.value;
configRepository.setBool('VRCX_dtIsoFormat', dtIsoFormat.value);
}
/**
* @param {number} value - 0 (Sunday), 1 (Monday), or 6 (Saturday)
*/
function setWeekStartsOn(value) {
const v = [0, 1, 6].includes(value) ? value : 1;
weekStartsOn.value = v;
configRepository.setInt('VRCX_weekStartsOn', v);
}
/**
* @param {string} method
*/
@@ -962,7 +979,8 @@ export const useAppearanceSettingsStore = defineStore(
*
*/
function toggleAccessibleStatusIndicators() {
accessibleStatusIndicators.value = !accessibleStatusIndicators.value;
accessibleStatusIndicators.value =
!accessibleStatusIndicators.value;
configRepository.setBool(
'VRCX_accessibleStatusIndicators',
accessibleStatusIndicators.value
@@ -1216,6 +1234,7 @@ export const useAppearanceSettingsStore = defineStore(
tablePageSizes,
dtHour12,
dtIsoFormat,
weekStartsOn,
sidebarSortMethod1,
sidebarSortMethod2,
sidebarSortMethod3,
@@ -1259,6 +1278,7 @@ export const useAppearanceSettingsStore = defineStore(
setTablePageSizes,
setDtHour12,
setDtIsoFormat,
setWeekStartsOn,
setSidebarSortMethod1,
setSidebarSortMethod2,
setSidebarSortMethod3,

View File

@@ -131,6 +131,7 @@
:default-placeholder="defaultCalendarPlaceholder"
:is-date-disabled="isCalendarDateDisabled"
:prevent-deselect="true"
:week-starts-on="weekStartsOn"
initial-focus
@update:modelValue="handleCalendarModelUpdate" />
</PopoverContent>
@@ -216,7 +217,7 @@
const { friends, allFavoriteFriendIds } = storeToRefs(friendStore);
const { currentUser } = storeToRefs(useUserStore());
const { t } = useI18n();
const { isDarkMode, dtHour12 } = storeToRefs(appearanceSettingsStore);
const { isDarkMode, dtHour12, weekStartsOn } = storeToRefs(appearanceSettingsStore);
const instanceActivityRef = ref(null);
@@ -596,15 +597,15 @@
const location = parseLocation(instanceData.location);
return `
<div style="display: flex; align-items: center;">
<div style="width: 10px; height: 55px; background-color: ${color}; margin-right: 6px;"></div>
<div>
<div>${name} #${location.instanceName} ${location.accessTypeName}</div>
<div>${formattedJoinDateTime} - ${formattedLeftDateTime}</div>
<div>${timeString}</div>
<div style="display: flex; align-items: center;">
<div style="width: 10px; height: 55px; background-color: ${color}; margin-right: 6px;"></div>
<div>
<div>${name} #${location.instanceName} ${location.accessTypeName}</div>
<div>${formattedJoinDateTime} - ${formattedLeftDateTime}</div>
<div>${timeString}</div>
</div>
</div>
</div>
`;
`;
};
const format = dtHour12.value ? 'hh:mm A' : 'HH:mm';

View File

@@ -28,7 +28,8 @@
v-model="dateRange"
:locale="locale"
:max-value="todayDate"
:number-of-months="2" />
:number-of-months="2"
:week-starts-on="weekStartsOn" />
<div class="flex justify-end gap-2 mt-3">
<Button variant="outline" size="sm" @click="clearDateFilter">
{{ t('common.actions.clear') }}
@@ -109,6 +110,7 @@
const { feedTable, feedTableData } = storeToRefs(useFeedStore());
const { feedTableLookup } = useFeedStore();
const appearanceSettingsStore = useAppearanceSettingsStore();
const { weekStartsOn } = storeToRefs(appearanceSettingsStore);
const vrcxStore = useVrcxStore();
const { t, locale } = useI18n();

View File

@@ -119,11 +119,10 @@
" />
</SettingsItem>
<SettingsItem :label="t('view.settings.appearance.appearance.age_gated_instances')"
<SettingsItem
:label="t('view.settings.appearance.appearance.age_gated_instances')"
:description="t('view.settings.appearance.appearance.age_gated_instances_description')">
<Switch
:model-value="isAgeGatedInstancesVisible"
@update:modelValue="setIsAgeGatedInstancesVisible" />
<Switch :model-value="isAgeGatedInstancesVisible" @update:modelValue="setIsAgeGatedInstancesVisible" />
</SettingsItem>
<SettingsItem :label="t('view.settings.appearance.appearance.striped_data_table_mode')">
@@ -137,7 +136,9 @@
<SettingsItem
:label="t('view.settings.appearance.appearance.accessible_status_indicators')"
:description="t('view.settings.appearance.appearance.accessible_status_indicators_description')">
<Switch :model-value="accessibleStatusIndicators" @update:modelValue="toggleAccessibleStatusIndicators" />
<Switch
:model-value="accessibleStatusIndicators"
@update:modelValue="toggleAccessibleStatusIndicators" />
</SettingsItem>
<SettingsItem
@@ -275,6 +276,21 @@
<SettingsItem :label="t('view.settings.appearance.timedate.force_iso_date_format')">
<Switch :model-value="dtIsoFormat" @update:modelValue="setDtIsoFormat" />
</SettingsItem>
<SettingsItem
:label="t('view.settings.appearance.timedate.week_starts_on')"
:description="t('view.settings.appearance.timedate.week_starts_on_description')">
<Select :model-value="String(weekStartsOn)" @update:modelValue="handleWeekStartsOnChange">
<SelectTrigger size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">{{ t('common.days.monday') }}</SelectItem>
<SelectItem value="0">{{ t('common.days.sunday') }}</SelectItem>
<SelectItem value="6">{{ t('common.days.saturday') }}</SelectItem>
</SelectContent>
</Select>
</SettingsItem>
</SettingsGroup>
<SettingsGroup :title="t('view.settings.appearance.user_dialog.header')">
@@ -395,6 +411,7 @@
instanceUsersSortAlphabetical,
dtHour12,
dtIsoFormat,
weekStartsOn,
hideUserNotes,
hideUserMemos,
hideUnfriends,
@@ -419,6 +436,7 @@
setInstanceUsersSortAlphabetical,
setDtHour12,
setDtIsoFormat,
setWeekStartsOn,
setHideUserNotes,
setHideUserMemos,
setHideUnfriends,
@@ -437,8 +455,6 @@
setAppCjkFontPack
} = appearanceSettingsStore;
const trustColorEntries = [
{
key: 'untrusted',
@@ -597,6 +613,14 @@
}
}
/**
*
* @param value
*/
function handleWeekStartsOnChange(value) {
setWeekStartsOn(Number(value));
}
const tablePageSizesModel = computed({
get: () => tablePageSizes.value.map(String),
set: (values) => {

View File

@@ -16,9 +16,12 @@
import { fromDate, getLocalTimeZone } from '@internationalized/date';
import { CalendarRoot } from 'reka-ui';
import { toDate } from 'reka-ui/date';
import { storeToRefs } from 'pinia';
import dayjs from 'dayjs';
import { useAppearanceSettingsStore } from '../../../stores';
const props = defineProps({
modelValue: {
type: Date,
@@ -40,6 +43,7 @@
const emit = defineEmits(['update:modelValue']);
const { weekStartsOn } = storeToRefs(useAppearanceSettingsStore());
const timeZone = getLocalTimeZone();
const internalValue = ref(fromDate(props.modelValue ?? new Date(), timeZone));
@@ -123,6 +127,7 @@
:placeholder="placeholder"
@update:placeholder="onUpdatePlaceholder"
:prevent-deselect="true"
:week-starts-on="weekStartsOn"
class="p-4">
<CalendarHeader class="pt-0">
<nav class="flex items-center gap-1 absolute top-0 inset-x-0 justify-between">