mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-06-17 22:38:08 +00:00
Initial implementation
This commit is contained in:
12
angular.json
12
angular.json
@ -16,6 +16,7 @@
|
|||||||
"main": "src/main.ts",
|
"main": "src/main.ts",
|
||||||
"tsConfig": "src/tsconfig.app.json",
|
"tsConfig": "src/tsconfig.app.json",
|
||||||
"polyfills": "src/polyfills.ts",
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"extractCss": true,
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/assets",
|
"src/assets",
|
||||||
"src/favicon.ico",
|
"src/favicon.ico",
|
||||||
@ -25,7 +26,16 @@
|
|||||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
"node_modules/notosans-fontface/css/notosans-fontface.min.css",
|
"node_modules/notosans-fontface/css/notosans-fontface.min.css",
|
||||||
"src/styles.css",
|
"src/styles.css",
|
||||||
"src/theme.scss"
|
{
|
||||||
|
"input": "src/theme.scss",
|
||||||
|
"lazy": true,
|
||||||
|
"bundleName": "theme-default-dark"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "src/theme-light.scss",
|
||||||
|
"lazy": true,
|
||||||
|
"bundleName": "theme-default"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ import { MatIconRegistry } from '@angular/material';
|
|||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
import { ElectronService } from 'ngx-electron';
|
import { ElectronService } from 'ngx-electron';
|
||||||
import { SettingsService } from './services/settings.service';
|
import { SettingsService } from './services/settings.service';
|
||||||
|
import { ThemeService } from './services/theme.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -14,7 +15,8 @@ export class AppComponent implements OnInit {
|
|||||||
iconReg: MatIconRegistry,
|
iconReg: MatIconRegistry,
|
||||||
sanitizer: DomSanitizer,
|
sanitizer: DomSanitizer,
|
||||||
private settingsService: SettingsService,
|
private settingsService: SettingsService,
|
||||||
private electronService: ElectronService
|
private electronService: ElectronService,
|
||||||
|
private themeService: ThemeService
|
||||||
) {
|
) {
|
||||||
iconReg.addSvgIcon('gns3', sanitizer.bypassSecurityTrustResourceUrl('./assets/gns3_icon.svg'));
|
iconReg.addSvgIcon('gns3', sanitizer.bypassSecurityTrustResourceUrl('./assets/gns3_icon.svg'));
|
||||||
}
|
}
|
||||||
@ -25,5 +27,6 @@ export class AppComponent implements OnInit {
|
|||||||
this.electronService.ipcRenderer.send('settings.changed', settings);
|
this.electronService.ipcRenderer.send('settings.changed', settings);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.themeService.setDarkMode(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,6 +255,7 @@ import { DeviceDetectorModule } from 'ngx-device-detector';
|
|||||||
import { ConfigDialogComponent } from './components/project-map/context-menu/dialogs/config-dialog/config-dialog.component';
|
import { ConfigDialogComponent } from './components/project-map/context-menu/dialogs/config-dialog/config-dialog.component';
|
||||||
import { Gns3vmComponent } from './components/preferences/gns3vm/gns3vm.component';
|
import { Gns3vmComponent } from './components/preferences/gns3vm/gns3vm.component';
|
||||||
import { Gns3vmService } from './services/gns3vm.service';
|
import { Gns3vmService } from './services/gns3vm.service';
|
||||||
|
import { ThemeService } from './services/theme.service';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
||||||
@ -516,7 +517,8 @@ if (environment.production) {
|
|||||||
TracengService,
|
TracengService,
|
||||||
PacketCaptureService,
|
PacketCaptureService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
Gns3vmService
|
Gns3vmService,
|
||||||
|
ThemeService
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AddServerDialogComponent,
|
AddServerDialogComponent,
|
||||||
|
@ -42,6 +42,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel [expanded]="false">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title> Theme settings </mat-panel-title>
|
||||||
|
<mat-panel-description> Customize theme settings </mat-panel-description>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
|
<div class="theme-panel">
|
||||||
|
<button mat-raised-button (click)="setDarkMode(false)">Switch to light theme</button>
|
||||||
|
<button mat-raised-button (click)="setDarkMode(true)">Switch to dark theme</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
.theme-panel {
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { SettingsService } from '../../services/settings.service';
|
import { SettingsService } from '../../services/settings.service';
|
||||||
import { ToasterService } from '../../services/toaster.service';
|
import { ToasterService } from '../../services/toaster.service';
|
||||||
import { ConsoleService } from '../../services/settings/console.service';
|
import { ConsoleService } from '../../services/settings/console.service';
|
||||||
|
import { ThemeService } from '../../services/theme.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: 'app-settings',
|
||||||
@ -15,7 +16,9 @@ export class SettingsComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private settingsService: SettingsService,
|
private settingsService: SettingsService,
|
||||||
private toaster: ToasterService,
|
private toaster: ToasterService,
|
||||||
private consoleService: ConsoleService) {}
|
private consoleService: ConsoleService,
|
||||||
|
private themeService: ThemeService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.settings = this.settingsService.getAll();
|
this.settings = this.settingsService.getAll();
|
||||||
@ -26,4 +29,9 @@ export class SettingsComponent implements OnInit {
|
|||||||
this.settingsService.setAll(this.settings);
|
this.settingsService.setAll(this.settings);
|
||||||
this.toaster.success('Settings have been saved.');
|
this.toaster.success('Settings have been saved.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDarkMode(value: boolean) {
|
||||||
|
this.themeService.setDarkMode(value);
|
||||||
|
//this value should be saved and stored in local memory
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
56
src/app/services/theme.service.ts
Normal file
56
src/app/services/theme.service.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Injectable, RendererFactory2, Renderer2, Inject } from '@angular/core';
|
||||||
|
import { Observable, BehaviorSubject, combineLatest } from 'rxjs';
|
||||||
|
import { DOCUMENT } from '@angular/common';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ThemeService {
|
||||||
|
|
||||||
|
private _mainTheme$: BehaviorSubject<string> = new BehaviorSubject('theme-default');
|
||||||
|
private _darkMode$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
|
darkMode$: Observable<boolean> = this._darkMode$.asObservable();
|
||||||
|
|
||||||
|
private _renderer: Renderer2;
|
||||||
|
private head: HTMLElement;
|
||||||
|
private themeLinks: HTMLElement[] = [];
|
||||||
|
|
||||||
|
theme$: Observable<[string, boolean]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
rendererFactory: RendererFactory2,
|
||||||
|
@Inject(DOCUMENT) document: Document
|
||||||
|
) {
|
||||||
|
this.head = document.head;
|
||||||
|
this._renderer = rendererFactory.createRenderer(null, null);
|
||||||
|
this.theme$ = combineLatest(this._mainTheme$, this._darkMode$);
|
||||||
|
this.theme$.subscribe(async ([mainTheme, darkMode]) => {
|
||||||
|
const cssExt = '.css';
|
||||||
|
const cssFilename = darkMode ? mainTheme + '-dark' + cssExt : mainTheme + cssExt;
|
||||||
|
await this.loadCss(cssFilename);
|
||||||
|
if (this.themeLinks.length == 2)
|
||||||
|
this._renderer.removeChild(this.head, this.themeLinks.shift());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setMainTheme(name: string) {
|
||||||
|
this._mainTheme$.next(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDarkMode(value: boolean) {
|
||||||
|
this._darkMode$.next(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadCss(filename: string) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const linkEl: HTMLElement = this._renderer.createElement('link');
|
||||||
|
this._renderer.setAttribute(linkEl, 'rel', 'stylesheet');
|
||||||
|
this._renderer.setAttribute(linkEl, 'type', 'text/css');
|
||||||
|
this._renderer.setAttribute(linkEl, 'href', filename);
|
||||||
|
this._renderer.setProperty(linkEl, 'onload', resolve);
|
||||||
|
this._renderer.appendChild(this.head, linkEl);
|
||||||
|
this.themeLinks = [...this.themeLinks, linkEl];
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
70
src/theme-light.scss
Normal file
70
src/theme-light.scss
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
@import '~@angular/material/theming';
|
||||||
|
@import '~material-design-icons/iconfont/material-icons.css';
|
||||||
|
@import '~typeface-roboto/index.css';
|
||||||
|
|
||||||
|
// Include non-theme styles for core.
|
||||||
|
@include mat-core();
|
||||||
|
|
||||||
|
// Define a theme.
|
||||||
|
$primary: mat-palette($mat-cyan, 700, 500, 900);
|
||||||
|
$accent: mat-palette($mat-blue, A200, A100, A700);
|
||||||
|
|
||||||
|
$theme: mat-light-theme($primary, $accent);
|
||||||
|
|
||||||
|
$light-palette: mat-palette($mat-blue, 900, A100, A400);
|
||||||
|
$light-background: mat-light-theme($light-palette, $primary);
|
||||||
|
|
||||||
|
$mat-dark-theme-background: (
|
||||||
|
// status-bar: black,
|
||||||
|
// app-bar: map_get($mat-blue-grey, 900),
|
||||||
|
// background: map_get($mat-blue-grey, 900),
|
||||||
|
// hover: rgba(white, 0.04),
|
||||||
|
// card: map_get($mat-blue-grey, 900),
|
||||||
|
// dialog: map_get($mat-blue-grey, 900),
|
||||||
|
// disabled-button: $white-12-opacity,
|
||||||
|
// raised-button: map-get($mat-grey, 800),
|
||||||
|
// focused-button: $white-6-opacity,
|
||||||
|
// selected-button: map_get($mat-grey, 900),
|
||||||
|
// selected-disabled-button: map_get($mat-grey, 800),
|
||||||
|
// disabled-button-toggle: black,
|
||||||
|
// unselected-chip: map_get($mat-grey, 700),
|
||||||
|
// disabled-list-option: black
|
||||||
|
);
|
||||||
|
|
||||||
|
$theme: map-merge(
|
||||||
|
$theme,
|
||||||
|
(
|
||||||
|
background: $mat-light-theme-background
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
@include mat-core-theme($theme);
|
||||||
|
@include mat-autocomplete-theme($theme);
|
||||||
|
@include mat-button-theme($theme);
|
||||||
|
@include mat-button-toggle-theme($theme);
|
||||||
|
@include mat-card-theme($theme);
|
||||||
|
@include mat-checkbox-theme($theme);
|
||||||
|
@include mat-chips-theme($theme);
|
||||||
|
@include mat-table-theme($theme);
|
||||||
|
@include mat-datepicker-theme($theme);
|
||||||
|
@include mat-dialog-theme($theme);
|
||||||
|
@include mat-expansion-panel-theme($theme);
|
||||||
|
@include mat-form-field-theme($theme);
|
||||||
|
@include mat-grid-list-theme($theme);
|
||||||
|
@include mat-icon-theme($theme);
|
||||||
|
@include mat-input-theme($theme);
|
||||||
|
@include mat-list-theme($theme);
|
||||||
|
@include mat-menu-theme($theme);
|
||||||
|
@include mat-paginator-theme($theme);
|
||||||
|
@include mat-progress-bar-theme($theme);
|
||||||
|
@include mat-progress-spinner-theme($theme);
|
||||||
|
@include mat-radio-theme($theme);
|
||||||
|
@include mat-select-theme($theme);
|
||||||
|
@include mat-sidenav-theme($theme);
|
||||||
|
@include mat-slide-toggle-theme($theme);
|
||||||
|
@include mat-slider-theme($theme);
|
||||||
|
@include mat-stepper-theme($theme);
|
||||||
|
@include mat-tabs-theme($theme);
|
||||||
|
@include mat-toolbar-theme($light-background);
|
||||||
|
@include mat-tooltip-theme($theme);
|
||||||
|
@include mat-snack-bar-theme($theme);
|
Reference in New Issue
Block a user