feat: added app settings service to handle theme persistance, fix: optimised dark mode contrast
This commit is contained in:
parent
afa7e380aa
commit
35844e0cf1
@ -14,9 +14,9 @@ import { DcAdapterSettings } from './models/DcAdapterSettings'
|
|||||||
import { AppStoreService } from './services/app-store.service'
|
import { AppStoreService } from './services/app-store.service'
|
||||||
import { LicenceService } from './services/licence.service'
|
import { LicenceService } from './services/licence.service'
|
||||||
import '@cds/core/icon/register.js';
|
import '@cds/core/icon/register.js';
|
||||||
import { ClarityIcons, moonIcon, sunIcon } from '@cds/core/icon';
|
import { ClarityIcons, exclamationTriangleIcon, moonIcon, sunIcon } from '@cds/core/icon';
|
||||||
|
|
||||||
ClarityIcons.addIcons(moonIcon, sunIcon);
|
ClarityIcons.addIcons(moonIcon, sunIcon, exclamationTriangleIcon);
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-app',
|
selector: 'my-app',
|
||||||
|
@ -20,10 +20,10 @@ import { UsernavRouteComponent } from './routes/usernav-route/usernav-route.comp
|
|||||||
import { AppService } from './services/app.service'
|
import { AppService } from './services/app.service'
|
||||||
import { InfoModalComponent } from './shared/abort-modal/info-modal.component'
|
import { InfoModalComponent } from './shared/abort-modal/info-modal.component'
|
||||||
import { RequestsModalComponent } from './shared/requests-modal/requests-modal.component'
|
import { RequestsModalComponent } from './shared/requests-modal/requests-modal.component'
|
||||||
import { HomeModule } from './home/home.module'
|
|
||||||
import { DirectivesModule } from './directives/directives.module'
|
import { DirectivesModule } from './directives/directives.module'
|
||||||
import { ViyaApiExplorerComponent } from './viya-api-explorer/viya-api-explorer.component'
|
import { ViyaApiExplorerComponent } from './viya-api-explorer/viya-api-explorer.component'
|
||||||
import { NgxJsonViewerModule } from 'ngx-json-viewer'
|
import { NgxJsonViewerModule } from 'ngx-json-viewer'
|
||||||
|
import { AppSettingsService } from './services/app-settings.service'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -50,7 +50,7 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer'
|
|||||||
DirectivesModule,
|
DirectivesModule,
|
||||||
NgxJsonViewerModule
|
NgxJsonViewerModule
|
||||||
],
|
],
|
||||||
providers: [AppService, SasStoreService, LicensingGuard],
|
providers: [AppService, SasStoreService, LicensingGuard, AppSettingsService],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
@ -2769,7 +2769,6 @@ export class EditorComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
this.currentEditRecordLoadings.push(column)
|
this.currentEditRecordLoadings.push(column)
|
||||||
hot.render()
|
hot.render()
|
||||||
return
|
|
||||||
|
|
||||||
this.sasService
|
this.sasService
|
||||||
.request('editors/getdynamiccolvals', data, undefined, {
|
.request('editors/getdynamiccolvals', data, undefined, {
|
||||||
|
@ -11,9 +11,11 @@ export const errorRenderer = (
|
|||||||
value: any,
|
value: any,
|
||||||
cellProperties: any
|
cellProperties: any
|
||||||
) => {
|
) => {
|
||||||
|
addDarkClass(td)
|
||||||
|
|
||||||
td.innerHTML = `${
|
td.innerHTML = `${
|
||||||
value ? value.toString() : ''
|
value ? value.toString() : ''
|
||||||
} <clr-icon shape="exclamation-circle" status="warning"></clr-icon>`
|
} <cds-icon shape="exclamation-triangle" status="warning"></cds-icon>`
|
||||||
|
|
||||||
return td
|
return td
|
||||||
}
|
}
|
||||||
@ -31,6 +33,8 @@ export const noSpinnerRenderer = (
|
|||||||
value: any,
|
value: any,
|
||||||
cellProperties: any
|
cellProperties: any
|
||||||
) => {
|
) => {
|
||||||
|
addDarkClass(td)
|
||||||
|
|
||||||
td.innerHTML = value ? value : ''
|
td.innerHTML = value ? value : ''
|
||||||
|
|
||||||
return td
|
return td
|
||||||
@ -50,10 +54,20 @@ export const spinnerRenderer = (
|
|||||||
value: any,
|
value: any,
|
||||||
cellProperties: any
|
cellProperties: any
|
||||||
) => {
|
) => {
|
||||||
td.classList.add('htDark')
|
addDarkClass(td)
|
||||||
|
|
||||||
td.innerHTML = `${
|
td.innerHTML = `${
|
||||||
value ? value.toString() : ''
|
value ? value.toString() : ''
|
||||||
} <span class="spinner spinner-sm vertical-align-middle"></span>`
|
} <span class="spinner spinner-sm vertical-align-middle"></span>`
|
||||||
|
|
||||||
return td
|
return td
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a htDark class to a TD element if not existing
|
||||||
|
*/
|
||||||
|
const addDarkClass = (td: any) => {
|
||||||
|
if (!td.classList.contains('htDark')) {
|
||||||
|
td.classList.add('htDark')
|
||||||
|
}
|
||||||
|
}
|
9
client/src/app/models/AppSettings.ts
Normal file
9
client/src/app/models/AppSettings.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface AppSettings {
|
||||||
|
persistSelectedTheme: boolean,
|
||||||
|
selectedTheme: AppThemes
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AppThemes {
|
||||||
|
light = 'light',
|
||||||
|
dark = 'dark'
|
||||||
|
}
|
54
client/src/app/services/app-settings.service.ts
Normal file
54
client/src/app/services/app-settings.service.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { BehaviorSubject } from "rxjs"
|
||||||
|
import { AppSettings, AppThemes } from "../models/AppSettings"
|
||||||
|
|
||||||
|
export class AppSettingsService {
|
||||||
|
public defaultSettings: AppSettings = {
|
||||||
|
persistSelectedTheme: true,
|
||||||
|
selectedTheme: AppThemes.light
|
||||||
|
}
|
||||||
|
public settings = new BehaviorSubject<AppSettings>(this.defaultSettings)
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.restoreAppSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore app settings from the local storage
|
||||||
|
*/
|
||||||
|
restoreAppSettings() {
|
||||||
|
try {
|
||||||
|
const serializedSettings = localStorage.getItem('app-settings')
|
||||||
|
|
||||||
|
if (serializedSettings) {
|
||||||
|
const settings = JSON.parse(serializedSettings)
|
||||||
|
|
||||||
|
this.setAppSettings(settings)
|
||||||
|
} else {
|
||||||
|
console.info('No app settings stored in the localStorage, we will set to default values.')
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.warn('Error restoring settings from local storgae.', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save app settings to the local storage
|
||||||
|
*/
|
||||||
|
storeAppSettings() {
|
||||||
|
localStorage.setItem('app-settings', JSON.stringify(this.settings.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function used in the app to update global settings object
|
||||||
|
*
|
||||||
|
* @param appSettings contains properties that should be updated in settings
|
||||||
|
*/
|
||||||
|
setAppSettings(appSettings: Partial<AppSettings>) {
|
||||||
|
this.settings.next({
|
||||||
|
...this.settings.value,
|
||||||
|
...appSettings
|
||||||
|
})
|
||||||
|
|
||||||
|
this.storeAppSettings()
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,8 @@ import { BehaviorSubject } from 'rxjs'
|
|||||||
import { LoggerService } from './logger.service'
|
import { LoggerService } from './logger.service'
|
||||||
import { EnvironmentInfo } from '../system/models/environment-info.model'
|
import { EnvironmentInfo } from '../system/models/environment-info.model'
|
||||||
import { LicenceService } from './licence.service'
|
import { LicenceService } from './licence.service'
|
||||||
|
import { AppSettingsService } from './app-settings.service'
|
||||||
|
import { AppThemes } from '../models/AppSettings'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
@ -18,6 +20,7 @@ export class AppService {
|
|||||||
private eventService: EventService,
|
private eventService: EventService,
|
||||||
private sasService: SasService,
|
private sasService: SasService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
|
private appSettingsService: AppSettingsService,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {
|
) {
|
||||||
this.subscribe()
|
this.subscribe()
|
||||||
@ -29,6 +32,19 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const appSettings = this.appSettingsService.settings.value
|
||||||
|
|
||||||
|
if (!!appSettings.persistSelectedTheme) {
|
||||||
|
if (appSettings.selectedTheme === AppThemes.light) {
|
||||||
|
this.eventService.toggleDarkMode(false)
|
||||||
|
} else if (appSettings.selectedTheme === AppThemes.dark) {
|
||||||
|
this.eventService.toggleDarkMode(true)
|
||||||
|
} else {
|
||||||
|
// Fallback to light mode
|
||||||
|
this.eventService.toggleDarkMode(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sasServiceInit() {
|
sasServiceInit() {
|
||||||
|
@ -2,6 +2,8 @@ import { Injectable, Output, EventEmitter } from '@angular/core'
|
|||||||
import { InfoModal, AbortDetails } from '../models/InfoModal'
|
import { InfoModal, AbortDetails } from '../models/InfoModal'
|
||||||
import { AlertsService } from '../shared/alerts/alerts.service'
|
import { AlertsService } from '../shared/alerts/alerts.service'
|
||||||
import { BehaviorSubject } from 'rxjs'
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
import { AppSettingsService } from './app-settings.service'
|
||||||
|
import { AppThemes } from '../models/AppSettings'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -20,7 +22,9 @@ export class EventService {
|
|||||||
|
|
||||||
public darkMode: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
public darkMode: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||||
|
|
||||||
constructor(private alertsService: AlertsService) {}
|
constructor(
|
||||||
|
private appSettingsService: AppSettingsService
|
||||||
|
) {}
|
||||||
|
|
||||||
toggleDarkMode(value: boolean) {
|
toggleDarkMode(value: boolean) {
|
||||||
this.darkMode.next(value)
|
this.darkMode.next(value)
|
||||||
@ -30,6 +34,10 @@ export class EventService {
|
|||||||
} else {
|
} else {
|
||||||
document.body.setAttribute('cds-theme', 'light')
|
document.body.setAttribute('cds-theme', 'light')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.appSettingsService.setAppSettings({
|
||||||
|
selectedTheme: value ? AppThemes.dark : AppThemes.light
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public showDemoLimitModal(featureName: string) {
|
public showDemoLimitModal(featureName: string) {
|
||||||
|
@ -12,4 +12,10 @@
|
|||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
margin-top:10px;
|
margin-top:10px;
|
||||||
color: #007cbb;
|
color: #007cbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep body[cds-theme="dark"] {
|
||||||
|
.baseTableLink {
|
||||||
|
color: #4ec0ff;
|
||||||
|
}
|
||||||
}
|
}
|
@ -194,6 +194,16 @@
|
|||||||
|
|
||||||
<hr class="w-100 light" />
|
<hr class="w-100 light" />
|
||||||
|
|
||||||
|
<!-- Keep the logic in case we in future have more then 2 themes (light, dark) -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- <div class="user-action">
|
||||||
|
Keep selected theme after reload
|
||||||
|
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input [(ngModel)]="settings.persistSelectedTheme" (change)="settingChange($event)" value="persistDarkMode" type="checkbox" clrToggle />
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
<ng-container *ngIf="environmentInfo?.ISADMIN === 1">
|
<ng-container *ngIf="environmentInfo?.ISADMIN === 1">
|
||||||
<div *ngIf="serverType === 'SAS9'" class="admin-action">
|
<div *ngIf="serverType === 'SAS9'" class="admin-action">
|
||||||
Refresh Data Lineage
|
Refresh Data Lineage
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-action {
|
.admin-action, .user-action {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -7,6 +7,8 @@ import { LicenceService } from '../services/licence.service'
|
|||||||
import { SasService } from '../services/sas.service'
|
import { SasService } from '../services/sas.service'
|
||||||
import { AppInfo } from './models/app-info.model'
|
import { AppInfo } from './models/app-info.model'
|
||||||
import { EnvironmentInfo } from './models/environment-info.model'
|
import { EnvironmentInfo } from './models/environment-info.model'
|
||||||
|
import { AppSettingsService } from '../services/app-settings.service'
|
||||||
|
import { AppSettings } from '../models/AppSettings'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-system',
|
selector: 'app-system',
|
||||||
@ -36,25 +38,36 @@ export class SystemComponent implements OnInit {
|
|||||||
Infinity = Infinity
|
Infinity = Infinity
|
||||||
|
|
||||||
licenceState = this.licenceService.licenceState
|
licenceState = this.licenceService.licenceState
|
||||||
|
settings: AppSettings
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private appService: AppService,
|
private appService: AppService,
|
||||||
private sasService: SasService,
|
private sasService: SasService,
|
||||||
private licenceService: LicenceService
|
private licenceService: LicenceService,
|
||||||
|
private appSettingsService: AppSettingsService
|
||||||
) {
|
) {
|
||||||
this.serverType = this.sasService.getServerType()
|
this.serverType = this.sasService.getServerType()
|
||||||
this.licenceInfo = this.licenceService.getLicenseKeyData()
|
this.licenceInfo = this.licenceService.getLicenseKeyData()
|
||||||
this.environmentInfo = this.appService.getEnvironmentInfo()
|
this.environmentInfo = this.appService.getEnvironmentInfo()
|
||||||
|
this.settings = this.appSettingsService.settings.value
|
||||||
|
|
||||||
if (this.environmentInfo) {
|
if (this.environmentInfo) {
|
||||||
this.environmentInfo.AUTOEXEC = decodeURIComponent(
|
this.environmentInfo.AUTOEXEC = decodeURIComponent(
|
||||||
this.environmentInfo.AUTOEXEC
|
this.environmentInfo.AUTOEXEC
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.appSettingsService.settings.subscribe((settings: AppSettings) => {
|
||||||
|
this.settings = settings
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
settingChange(event: Event) {
|
||||||
|
this.appSettingsService.setAppSettings(this.settings)
|
||||||
|
}
|
||||||
|
|
||||||
downloadConfiguration() {
|
downloadConfiguration() {
|
||||||
let sasjsConfig = this.sasService.getSasjsConfig()
|
let sasjsConfig = this.sasService.getSasjsConfig()
|
||||||
let storage = sasjsConfig.serverUrl
|
let storage = sasjsConfig.serverUrl
|
||||||
|
@ -4,9 +4,15 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { SystemRoutingModule } from './system-routing.module'
|
import { SystemRoutingModule } from './system-routing.module'
|
||||||
import { SystemComponent } from './system.component'
|
import { SystemComponent } from './system.component'
|
||||||
import { ClarityModule } from '@clr/angular'
|
import { ClarityModule } from '@clr/angular'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [SystemComponent],
|
declarations: [SystemComponent],
|
||||||
imports: [CommonModule, SystemRoutingModule, ClarityModule]
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SystemRoutingModule,
|
||||||
|
ClarityModule,
|
||||||
|
FormsModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class SystemModule {}
|
export class SystemModule {}
|
||||||
|
@ -33,6 +33,10 @@ body[cds-theme="dark"] {
|
|||||||
// Track
|
// Track
|
||||||
border: 3px solid $trackColor;
|
border: 3px solid $trackColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clr-icon.is-highlight {
|
||||||
|
fill: #4ec0ff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// body::-webkit-scrollbar {
|
// body::-webkit-scrollbar {
|
||||||
|
14
sas/package-lock.json
generated
14
sas/package-lock.json
generated
@ -7,7 +7,7 @@
|
|||||||
"name": "dc-sas",
|
"name": "dc-sas",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/cli": "^4.11.1",
|
"@sasjs/cli": "^4.11.1",
|
||||||
"@sasjs/core": "^4.52.1"
|
"@sasjs/core": "^4.52.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@coolaj86/urequest": {
|
"node_modules/@coolaj86/urequest": {
|
||||||
@ -116,9 +116,9 @@
|
|||||||
"integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw=="
|
"integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw=="
|
||||||
},
|
},
|
||||||
"node_modules/@sasjs/core": {
|
"node_modules/@sasjs/core": {
|
||||||
"version": "4.52.1",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.4.tgz",
|
||||||
"integrity": "sha512-XPNuKD1T5XLGMKg4Ll3KggOTjhHgnjdbefpwajpfro/8/9bJK7lyNehzUCcmbhJnijJbbChE7drIOF+uSaVxVg=="
|
"integrity": "sha512-8lf5ixlA312EgA2DorwbpNXXPfLPzUHO67exIV7SjKiU23Tn1au5GD6hT0Ysr2kophOs10Mp1TCXJjhEq7Qk4A=="
|
||||||
},
|
},
|
||||||
"node_modules/@sasjs/lint": {
|
"node_modules/@sasjs/lint": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
@ -1834,9 +1834,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@sasjs/core": {
|
"@sasjs/core": {
|
||||||
"version": "4.52.1",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.4.tgz",
|
||||||
"integrity": "sha512-XPNuKD1T5XLGMKg4Ll3KggOTjhHgnjdbefpwajpfro/8/9bJK7lyNehzUCcmbhJnijJbbChE7drIOF+uSaVxVg=="
|
"integrity": "sha512-8lf5ixlA312EgA2DorwbpNXXPfLPzUHO67exIV7SjKiU23Tn1au5GD6hT0Ysr2kophOs10Mp1TCXJjhEq7Qk4A=="
|
||||||
},
|
},
|
||||||
"@sasjs/lint": {
|
"@sasjs/lint": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
|
@ -29,6 +29,6 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/cli": "^4.11.1",
|
"@sasjs/cli": "^4.11.1",
|
||||||
"@sasjs/core": "^4.52.1"
|
"@sasjs/core": "^4.52.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user