Themes: Switch theme without reload using global shortcut (#32180)

* Themes: Switch theme without reload using global shortcut

* Review updates
pull/32016/head
Torkel Ödegaard 4 years ago committed by GitHub
parent 79f0cf7874
commit 5821783068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      public/app/core/components/SharedPreferences/SharedPreferences.tsx
  2. 14
      public/app/core/services/PreferencesService.ts
  3. 4
      public/app/core/services/keybindingSrv.ts
  4. 43
      public/app/core/services/toggleTheme.ts
  5. 71
      public/app/features/profile/ProfileCtrl.ts
  6. 1
      public/app/types/index.ts
  7. 7
      public/app/types/preferences.ts
  8. 6
      public/views/index-template.html

@ -19,6 +19,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types';
import { backendSrv } from 'app/core/services/backend_srv';
import { PreferencesService } from 'app/core/services/PreferencesService';
export interface Props {
resourceUri: string;
@ -38,11 +39,12 @@ const themes: SelectableValue[] = [
];
export class SharedPreferences extends PureComponent<Props, State> {
backendSrv = backendSrv;
service: PreferencesService;
constructor(props: Props) {
super(props);
this.service = new PreferencesService(props.resourceUri);
this.state = {
homeDashboardId: 0,
theme: '',
@ -52,7 +54,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
}
async componentDidMount() {
const prefs = await backendSrv.get(`/api/${this.props.resourceUri}/preferences`);
const prefs = await this.service.load();
const dashboards = await backendSrv.search({ starred: true });
const defaultDashboardHit: DashboardSearchHit = {
id: 0,
@ -88,12 +90,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
onSubmitForm = async () => {
const { homeDashboardId, theme, timezone } = this.state;
await backendSrv.put(`/api/${this.props.resourceUri}/preferences`, {
homeDashboardId,
theme,
timezone,
});
this.service.update({ homeDashboardId, theme, timezone });
window.location.reload();
};

@ -0,0 +1,14 @@
import { UserPreferencesDTO } from 'app/types';
import { backendSrv } from './backend_srv';
export class PreferencesService {
constructor(private resourceUri: string) {}
update(preferences: UserPreferencesDTO): Promise<any> {
return backendSrv.put(`/api/${this.resourceUri}/preferences`, preferences);
}
load(): Promise<UserPreferencesDTO> {
return backendSrv.get<UserPreferencesDTO>(`/api/${this.resourceUri}/preferences`);
}
}

@ -20,6 +20,7 @@ import {
import { contextSrv } from '../core';
import { getDatasourceSrv } from '../../features/plugins/datasource_srv';
import { getTimeSrv } from '../../features/dashboard/services/TimeSrv';
import { toggleTheme } from './toggleTheme';
export class KeybindingSrv {
modalOpen = false;
@ -43,6 +44,9 @@ export class KeybindingSrv {
this.bind('esc', this.exit);
this.bindGlobal('esc', this.globalEsc);
}
this.bind('t t', () => toggleTheme(false));
this.bind('t r', () => toggleTheme(true));
}
private globalEsc() {

@ -0,0 +1,43 @@
import { getTheme } from '@grafana/ui';
import { ThemeChangedEvent } from 'app/types/events';
import appEvents from '../app_events';
import { config } from '../config';
import { PreferencesService } from './PreferencesService';
export async function toggleTheme(runtimeOnly: boolean) {
const currentTheme = config.theme;
const newTheme = getTheme(currentTheme.isDark ? 'light' : 'dark');
appEvents.publish(new ThemeChangedEvent(newTheme));
if (runtimeOnly) {
return;
}
// Add css file for new theme
const newCssLink = document.createElement('link');
newCssLink.rel = 'stylesheet';
newCssLink.href = config.bootData.themePaths[newTheme.type];
document.body.appendChild(newCssLink);
// Remove old css file
const bodyLinks = document.getElementsByTagName('link');
for (let i = 0; i < bodyLinks.length; i++) {
const link = bodyLinks[i];
if (link.href && link.href.indexOf(`build/grafana.${currentTheme.type}`) > 0) {
// Remove existing link after a 500ms to allow new css to load to avoid flickering
// If we add new css at the same time we remove current one the page will be rendered without css
// As the new css file is loading
setTimeout(() => link.remove(), 500);
}
}
// Persist new theme
const service = new PreferencesService('user');
const currentPref = await service.load();
await service.update({
...currentPref,
theme: newTheme.type,
});
}

@ -1,71 +0,0 @@
import { coreModule, NavModelSrv } from 'app/core/core';
import { dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data';
import { UserSession } from 'app/types';
import { getBackendSrv } from '@grafana/runtime';
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
import { IScope } from 'angular';
export class ProfileCtrl {
sessions: object[] = [];
navModel: any;
/** @ngInject */
constructor(private $scope: IScope, navModelSrv: NavModelSrv) {
this.getUserSessions();
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
}
getUserSessions() {
promiseToDigest(this.$scope)(
getBackendSrv()
.get('/api/user/auth-tokens')
.then((sessions: UserSession[]) => {
sessions.reverse();
const found = sessions.findIndex((session: UserSession) => {
return session.isActive;
});
if (found) {
const now = sessions[found];
sessions.splice(found, found);
sessions.unshift(now);
}
this.sessions = sessions.map((session: UserSession) => {
return {
id: session.id,
isActive: session.isActive,
seenAt: dateTimeFormatTimeAgo(session.seenAt),
createdAt: dateTimeFormat(session.createdAt, { format: 'MMMM DD, YYYY' }),
clientIp: session.clientIp,
browser: session.browser,
browserVersion: session.browserVersion,
os: session.os,
osVersion: session.osVersion,
device: session.device,
};
});
})
);
}
revokeUserSession(tokenId: number) {
promiseToDigest(this.$scope)(
getBackendSrv()
.post('/api/user/revoke-auth-token', {
authTokenId: tokenId,
})
.then(() => {
this.sessions = this.sessions.filter((session: UserSession) => {
if (session.id === tokenId) {
return false;
}
return true;
});
})
);
}
}
coreModule.controller('ProfileCtrl', ProfileCtrl);

@ -16,6 +16,7 @@ export * from './ldap';
export * from './appEvent';
export * from './angular';
export * from './query';
export * from './preferences';
import * as CoreEvents from './events';
export { CoreEvents };

@ -0,0 +1,7 @@
import { TimeZone } from '@grafana/data';
export interface UserPreferencesDTO {
timezone: TimeZone;
homeDashboardId: number;
theme: string;
}

@ -257,7 +257,11 @@
window.grafanaBootData = {
user: [[.User]],
settings: [[.Settings]],
navTree: [[.NavTree]]
navTree: [[.NavTree]],
themePaths: {
light: '[[.ContentDeliveryURL]]public/build/grafana.light.<%= webpack.hash %>.css',
dark: '[[.ContentDeliveryURL]]public/build/grafana.dark.<%= webpack.hash %>.css'
}
};
// In case the js files fails to load the code below will show an info message.

Loading…
Cancel
Save