mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Preferences: Add theme preference to match system theme (#61986)
* user essentials mob! 🔱 lastFile:pkg/api/preferences.go * user essentials mob! 🔱 * user essentials mob! 🔱 lastFile:packages/grafana-data/src/types/config.ts * user essentials mob! 🔱 lastFile:public/app/core/services/echo/utils.test.ts * user essentials mob! 🔱 * user essentials mob! 🔱 lastFile:public/views/index-template.html * user essentials mob! 🔱 * Restore currentUser.lightTheme for backwards compat * fix types * Apply suggestions from code review Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * cleanup * cleanup --------- Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com> Co-authored-by: Joao Silva <joao.silva@grafana.com> Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
This commit is contained in:
parent
be7b90bbd1
commit
d51e7ec7ef
@ -423,7 +423,7 @@ verify_email_enabled = false
|
||||
login_hint = email or username
|
||||
password_hint = password
|
||||
|
||||
# Default UI theme ("dark" or "light")
|
||||
# Default UI theme ("dark" or "light" or "system")
|
||||
default_theme = dark
|
||||
|
||||
# Default UI language (supported IETF language tag, such as en-US)
|
||||
|
@ -772,7 +772,9 @@ Text used as placeholder text on login page for password input.
|
||||
|
||||
### default_theme
|
||||
|
||||
Set the default UI theme: `dark` or `light`. Default is `dark`.
|
||||
Sets the default UI theme: `dark`, `light`, or `system`. The default theme is `dark`.
|
||||
|
||||
`system` matches the user's system theme.
|
||||
|
||||
### default_language
|
||||
|
||||
|
@ -117,7 +117,7 @@ export interface CurrentUserDTO {
|
||||
login: string;
|
||||
email: string;
|
||||
name: string;
|
||||
lightTheme: boolean;
|
||||
theme: string; // dark | light | system
|
||||
orgCount: number;
|
||||
orgId: number;
|
||||
orgName: string;
|
||||
@ -129,6 +129,9 @@ export interface CurrentUserDTO {
|
||||
locale: string;
|
||||
language: string;
|
||||
permissions?: Record<string, boolean>;
|
||||
|
||||
/** @deprecated Use theme instead */
|
||||
lightTheme: boolean;
|
||||
}
|
||||
|
||||
/** Contains essential user and config info
|
||||
|
@ -149,6 +149,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
|
||||
constructor(options: GrafanaBootConfig) {
|
||||
this.bootData = options.bootData;
|
||||
this.bootData.user.lightTheme = getThemeMode(options) === 'light';
|
||||
this.isPublicDashboardView = options.bootData.settings.isPublicDashboardView;
|
||||
|
||||
const defaults = {
|
||||
@ -189,8 +190,24 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
}
|
||||
}
|
||||
|
||||
function getThemeMode(config: GrafanaBootConfig) {
|
||||
let mode: 'light' | 'dark' = 'dark';
|
||||
const themePref = config.bootData.user.theme;
|
||||
|
||||
if (themePref === 'light' || themePref === 'dark') {
|
||||
mode = themePref;
|
||||
} else if (themePref === 'system') {
|
||||
const mediaResult = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
mode = mediaResult.matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
function getThemeCustomizations(config: GrafanaBootConfig) {
|
||||
// if/when we remove CurrentUserDTO.lightTheme, change this to use getThemeMode instead
|
||||
const mode = config.bootData.user.lightTheme ? 'light' : 'dark';
|
||||
|
||||
const themeOptions: NewThemeOptions = {
|
||||
colors: { mode },
|
||||
};
|
||||
|
@ -33,7 +33,8 @@ type CurrentUser struct {
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
LightTheme bool `json:"lightTheme"`
|
||||
Theme string `json:"theme"`
|
||||
LightTheme bool `json:"lightTheme"` // deprecated, use theme instead
|
||||
OrgCount int `json:"orgCount"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
OrgName string `json:"orgName"`
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
// swagger:model
|
||||
type UpdatePrefsCmd struct {
|
||||
// Enum: light,dark
|
||||
// Enum: light,dark,system
|
||||
Theme string `json:"theme"`
|
||||
// The numerical :id of a favorited dashboard
|
||||
// Default:0
|
||||
|
@ -17,8 +17,9 @@ import (
|
||||
|
||||
const (
|
||||
// Themes
|
||||
lightName = "light"
|
||||
darkName = "dark"
|
||||
lightName = "light"
|
||||
darkName = "dark"
|
||||
systemName = "system"
|
||||
)
|
||||
|
||||
func (hs *HTTPServer) editorInAnyFolder(c *contextmodel.ReqContext) bool {
|
||||
@ -100,6 +101,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
|
||||
OrgRole: c.OrgRole,
|
||||
GravatarUrl: dtos.GetGravatarUrl(c.Email),
|
||||
IsGrafanaAdmin: c.IsGrafanaAdmin,
|
||||
Theme: prefs.Theme,
|
||||
LightTheme: prefs.Theme == lightName,
|
||||
Timezone: prefs.Timezone,
|
||||
WeekStart: weekStart,
|
||||
@ -150,12 +152,9 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
|
||||
}
|
||||
|
||||
themeURLParam := c.Query("theme")
|
||||
if themeURLParam == lightName {
|
||||
data.User.LightTheme = true
|
||||
data.Theme = lightName
|
||||
} else if themeURLParam == darkName {
|
||||
data.User.LightTheme = false
|
||||
data.Theme = darkName
|
||||
if themeURLParam == lightName || themeURLParam == darkName || themeURLParam == systemName {
|
||||
data.User.Theme = themeURLParam
|
||||
data.Theme = themeURLParam
|
||||
}
|
||||
|
||||
hs.HooksService.RunIndexDataHooks(&data, c)
|
||||
|
@ -17,6 +17,7 @@ const (
|
||||
defaultTheme string = ""
|
||||
darkTheme string = "dark"
|
||||
lightTheme string = "light"
|
||||
systemTheme string = "system"
|
||||
)
|
||||
|
||||
// POST /api/preferences/set-home-dash
|
||||
@ -134,7 +135,7 @@ func (hs *HTTPServer) UpdateUserPreferences(c *contextmodel.ReqContext) response
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) updatePreferencesFor(ctx context.Context, orgID, userID, teamId int64, dtoCmd *dtos.UpdatePrefsCmd) response.Response {
|
||||
if dtoCmd.Theme != lightTheme && dtoCmd.Theme != darkTheme && dtoCmd.Theme != defaultTheme {
|
||||
if dtoCmd.Theme != lightTheme && dtoCmd.Theme != darkTheme && dtoCmd.Theme != defaultTheme && dtoCmd.Theme != systemTheme {
|
||||
return response.Error(400, "Invalid theme", nil)
|
||||
}
|
||||
|
||||
@ -191,7 +192,7 @@ func (hs *HTTPServer) PatchUserPreferences(c *contextmodel.ReqContext) response.
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) patchPreferencesFor(ctx context.Context, orgID, userID, teamId int64, dtoCmd *dtos.PatchPrefsCmd) response.Response {
|
||||
if dtoCmd.Theme != nil && *dtoCmd.Theme != lightTheme && *dtoCmd.Theme != darkTheme && *dtoCmd.Theme != defaultTheme {
|
||||
if dtoCmd.Theme != nil && *dtoCmd.Theme != lightTheme && *dtoCmd.Theme != darkTheme && *dtoCmd.Theme != defaultTheme && *dtoCmd.Theme != systemTheme {
|
||||
return response.Error(400, "Invalid theme", nil)
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
{ value: '', label: t('shared-preferences.theme.default-label', 'Default') },
|
||||
{ value: 'dark', label: t('shared-preferences.theme.dark-label', 'Dark') },
|
||||
{ value: 'light', label: t('shared-preferences.theme.light-label', 'Light') },
|
||||
{ value: 'system', label: t('shared-preferences.theme.system-label', 'System') },
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -7,14 +7,14 @@ import { CurrentUserInternal } from 'app/types/config';
|
||||
|
||||
import config from '../../core/config';
|
||||
|
||||
export class User implements CurrentUserInternal {
|
||||
export class User implements Omit<CurrentUserInternal, 'lightTheme'> {
|
||||
isSignedIn: boolean;
|
||||
id: number;
|
||||
login: string;
|
||||
email: string;
|
||||
name: string;
|
||||
externalUserId: string;
|
||||
lightTheme: boolean;
|
||||
theme: string;
|
||||
orgCount: number;
|
||||
orgId: number;
|
||||
orgName: string;
|
||||
@ -43,7 +43,7 @@ export class User implements CurrentUserInternal {
|
||||
this.timezone = '';
|
||||
this.fiscalYearStartMonth = 0;
|
||||
this.helpFlags1 = 0;
|
||||
this.lightTheme = false;
|
||||
this.theme = 'dark';
|
||||
this.hasEditPermissionInFolders = false;
|
||||
this.email = '';
|
||||
this.name = '';
|
||||
|
@ -8,7 +8,8 @@ const baseUser: CurrentUserDTO = {
|
||||
login: 'myUsername',
|
||||
email: 'email@example.com',
|
||||
name: 'My Name',
|
||||
lightTheme: false,
|
||||
theme: 'dark',
|
||||
lightTheme: false, // deprecated
|
||||
orgCount: 1,
|
||||
orgId: 1,
|
||||
orgName: 'Main Org.',
|
||||
|
@ -489,7 +489,8 @@
|
||||
"theme": {
|
||||
"dark-label": "Dunkel",
|
||||
"default-label": "Standard",
|
||||
"light-label": "Hell"
|
||||
"light-label": "Hell",
|
||||
"system-label": ""
|
||||
},
|
||||
"title": "Einstellungen"
|
||||
},
|
||||
|
@ -489,7 +489,8 @@
|
||||
"theme": {
|
||||
"dark-label": "Dark",
|
||||
"default-label": "Default",
|
||||
"light-label": "Light"
|
||||
"light-label": "Light",
|
||||
"system-label": "System"
|
||||
},
|
||||
"title": "Preferences"
|
||||
},
|
||||
|
@ -489,7 +489,8 @@
|
||||
"theme": {
|
||||
"dark-label": "Oscuro",
|
||||
"default-label": "Por defecto",
|
||||
"light-label": "Claro"
|
||||
"light-label": "Claro",
|
||||
"system-label": ""
|
||||
},
|
||||
"title": "Preferencias"
|
||||
},
|
||||
|
@ -489,7 +489,8 @@
|
||||
"theme": {
|
||||
"dark-label": "Sombre",
|
||||
"default-label": "Par défaut",
|
||||
"light-label": "Clair"
|
||||
"light-label": "Clair",
|
||||
"system-label": ""
|
||||
},
|
||||
"title": "Préférences"
|
||||
},
|
||||
|
@ -489,7 +489,8 @@
|
||||
"theme": {
|
||||
"dark-label": "Đäřĸ",
|
||||
"default-label": "Đęƒäūľŧ",
|
||||
"light-label": "Ŀįģĥŧ"
|
||||
"light-label": "Ŀįģĥŧ",
|
||||
"system-label": "Ŝyşŧęm"
|
||||
},
|
||||
"title": "Přęƒęřęʼnčęş"
|
||||
},
|
||||
@ -576,4 +577,4 @@
|
||||
"option-tooltip": "Cľęäř şęľęčŧįőʼnş"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -489,7 +489,8 @@
|
||||
"theme": {
|
||||
"dark-label": "深色",
|
||||
"default-label": "默认",
|
||||
"light-label": "浅色"
|
||||
"light-label": "浅色",
|
||||
"system-label": ""
|
||||
},
|
||||
"title": "首选项"
|
||||
},
|
||||
|
@ -14,9 +14,10 @@
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="[[.AppleTouchIcon]]" />
|
||||
<link rel="mask-icon" href="[[.ContentDeliveryURL]]public/img/grafana_mask_icon.svg" color="#F05A28" />
|
||||
|
||||
<!-- If theme is "system", we inject the stylesheets with javascript further down the page -->
|
||||
[[ if eq .Theme "light" ]]
|
||||
<link rel="stylesheet" href="[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.light %>" />
|
||||
[[ else ]]
|
||||
[[ else if eq .Theme "dark" ]]
|
||||
<link rel="stylesheet" href="[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.dark %>" />
|
||||
[[ end ]]
|
||||
|
||||
@ -251,6 +252,26 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Set theme to match system only on startup.
|
||||
// Do not react to changes in system theme after startup.
|
||||
if (window.grafanaBootData.user.theme === "system") {
|
||||
document.body.classList.remove("theme-system");
|
||||
var darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
var cssLink = document.createElement("link");
|
||||
cssLink.rel = 'stylesheet';
|
||||
|
||||
if (darkQuery.matches) {
|
||||
document.body.classList.add("theme-dark");
|
||||
cssLink.href = window.grafanaBootData.themePaths.dark;
|
||||
window.grafanaBootData.user.lightTheme = false;
|
||||
} else {
|
||||
document.body.classList.add("theme-light");
|
||||
cssLink.href = window.grafanaBootData.themePaths.light;
|
||||
window.grafanaBootData.user.lightTheme = true;
|
||||
}
|
||||
document.head.appendChild(cssLink);
|
||||
}
|
||||
|
||||
window.__grafana_load_failed = function() {
|
||||
var preloader = document.getElementsByClassName("preloader");
|
||||
if (preloader.length) {
|
||||
|
Loading…
Reference in New Issue
Block a user