Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user settings context #5984

Merged
merged 1 commit into from
Aug 28, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add user settings hook
thornbill committed Aug 28, 2024
commit aef4a42f8e57e802925f4691ae34e19f6515a845
9 changes: 6 additions & 3 deletions src/RootApp.tsx
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import React from 'react';

import { ApiProvider } from 'hooks/useApi';
import { UserSettingsProvider } from 'hooks/useUserSettings';
import { WebConfigProvider } from 'hooks/useWebConfig';
import { queryClient } from 'utils/query/queryClient';

@@ -11,9 +12,11 @@ import RootAppRouter from 'RootAppRouter';
const RootApp = () => (
<QueryClientProvider client={queryClient}>
<ApiProvider>
<WebConfigProvider>
<RootAppRouter />
</WebConfigProvider>
<UserSettingsProvider>
<WebConfigProvider>
<RootAppRouter />
</WebConfigProvider>
</UserSettingsProvider>
</ApiProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
78 changes: 78 additions & 0 deletions src/hooks/useUserSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { type FC, type PropsWithChildren, createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';

import { FALLBACK_CULTURE } from 'lib/globalize';
import { currentSettings as userSettings } from 'scripts/settings/userSettings';
import Events, { type Event } from 'utils/events';

import { useApi } from './useApi';

interface UserSettings {
theme?: string
dashboardTheme?: string
dateTimeLocale?: string
language?: string
}

// NOTE: This is an incomplete list of only the settings that are currently being used
const UserSettingField = {
// Theme settings
Theme: 'appTheme',
DashboardTheme: 'dashboardTheme',
// Locale settings
DateTimeLocale: 'datetimelocale',
Language: 'language'
};

const UserSettingsContext = createContext<UserSettings>({});

export const useUserSettings = () => useContext(UserSettingsContext);

export const UserSettingsProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
const [ theme, setTheme ] = useState<string>();
const [ dashboardTheme, setDashboardTheme ] = useState<string>();
const [ dateTimeLocale, setDateTimeLocale ] = useState<string>();
const [ language, setLanguage ] = useState<string | undefined>(FALLBACK_CULTURE);

const { user } = useApi();

const context = useMemo<UserSettings>(() => ({
theme,
dashboardTheme,
dateTimeLocale,
locale: language
}), [ theme, dashboardTheme, dateTimeLocale, language ]);

// Update the values of the user settings
const updateUserSettings = useCallback(() => {
setTheme(userSettings.theme());
setDashboardTheme(userSettings.dashboardTheme());
setDateTimeLocale(userSettings.dateTimeLocale());
setLanguage(userSettings.language());
}, []);

const onUserSettingsChange = useCallback((_e: Event, name?: string) => {
if (name && Object.values(UserSettingField).includes(name)) {
updateUserSettings();
}
}, [ updateUserSettings ]);

// Handle user settings changes
useEffect(() => {
Events.on(userSettings, 'change', onUserSettingsChange);

return () => {
Events.off(userSettings, 'change', onUserSettingsChange);
};
}, [ onUserSettingsChange ]);

// Update the settings if the user changes
useEffect(() => {
updateUserSettings();
}, [ updateUserSettings, user ]);

return (
<UserSettingsContext.Provider value={context}>
{children}
</UserSettingsContext.Provider>
);
};
53 changes: 4 additions & 49 deletions src/hooks/useUserTheme.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,12 @@
import { useCallback, useEffect, useState } from 'react';

import { currentSettings as userSettings } from 'scripts/settings/userSettings';
import Events, { type Event } from 'utils/events';

import { useApi } from './useApi';
import { useThemes } from './useThemes';

const THEME_FIELD_NAMES = [ 'appTheme', 'dashboardTheme' ];
import { useUserSettings } from './useUserSettings';

export function useUserTheme() {
const [ theme, setTheme ] = useState<string>();
const [ dashboardTheme, setDashboardTheme ] = useState<string>();

const { user } = useApi();
const { theme, dashboardTheme } = useUserSettings();
const { defaultTheme } = useThemes();

useEffect(() => {
if (defaultTheme) {
if (!theme) setTheme(defaultTheme.id);
if (!dashboardTheme) setDashboardTheme(defaultTheme.id);
}
}, [ dashboardTheme, defaultTheme, theme ]);

// Update the current themes with values from user settings
const updateThemesFromSettings = useCallback(() => {
const userTheme = userSettings.theme();
if (userTheme) setTheme(userTheme);
const userDashboardTheme = userSettings.dashboardTheme();
if (userDashboardTheme) setDashboardTheme(userDashboardTheme);
}, []);

const onUserSettingsChange = useCallback((_e: Event, name?: string) => {
if (name && THEME_FIELD_NAMES.includes(name)) {
updateThemesFromSettings();
}
}, [ updateThemesFromSettings ]);

// Handle user settings changes
useEffect(() => {
Events.on(userSettings, 'change', onUserSettingsChange);

return () => {
Events.off(userSettings, 'change', onUserSettingsChange);
};
}, [ onUserSettingsChange ]);

// Update the theme if the user changes
useEffect(() => {
updateThemesFromSettings();
}, [ updateThemesFromSettings, user ]);

return {
theme,
dashboardTheme
theme: theme || defaultTheme?.id,
dashboardTheme: dashboardTheme || defaultTheme?.id
};
}
15 changes: 7 additions & 8 deletions src/lib/globalize/index.js
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ const Direction = {
ltr: 'ltr'
};

const fallbackCulture = 'en-us';
export const FALLBACK_CULTURE = 'en-us';
const RTL_LANGS = ['ar', 'fa', 'ur', 'he'];

const allTranslations = {};
@@ -41,7 +41,7 @@ function getDefaultLanguage() {
return navigator.languages[0];
}

return fallbackCulture;
return FALLBACK_CULTURE;
}

export function getIsRTL() {
@@ -50,7 +50,6 @@ export function getIsRTL() {

function checkAndProcessDir(culture) {
isRTL = false;
console.log(culture);
for (const lang of RTL_LANGS) {
if (culture.includes(lang)) {
isRTL = true;
@@ -111,9 +110,9 @@ function ensureTranslations(culture) {
for (const i in allTranslations) {
ensureTranslation(allTranslations[i], culture);
}
if (culture !== fallbackCulture) {
if (culture !== FALLBACK_CULTURE) {
for (const i in allTranslations) {
ensureTranslation(allTranslations[i], fallbackCulture);
ensureTranslation(allTranslations[i], FALLBACK_CULTURE);
}
}
}
@@ -163,7 +162,7 @@ export function loadStrings(options) {
register(options);
}
promises.push(ensureTranslation(allTranslations[optionsName], locale));
promises.push(ensureTranslation(allTranslations[optionsName], fallbackCulture));
promises.push(ensureTranslation(allTranslations[optionsName], FALLBACK_CULTURE));
return Promise.all(promises);
}

@@ -183,7 +182,7 @@ function loadTranslation(translations, lang) {

if (!filtered.length) {
filtered = translations.filter(function (t) {
return normalizeLocaleName(t.lang) === fallbackCulture;
return normalizeLocaleName(t.lang) === FALLBACK_CULTURE;
});
}
}
@@ -222,7 +221,7 @@ function translateKeyFromModule(key, module) {
return dictionary[key];
}

dictionary = getDictionary(module, fallbackCulture);
dictionary = getDictionary(module, FALLBACK_CULTURE);
if (dictionary?.[key]) {
return dictionary[key];
}
Loading