feat: added app settings service to handle theme persistance, fix: optimised dark mode contrast

This commit is contained in:
Mihajlo Medjedovic 2024-05-29 14:35:43 +02:00
parent afa7e380aa
commit 35844e0cf1
16 changed files with 158 additions and 19 deletions

View File

@ -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',

View File

@ -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 {}

View File

@ -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, {

View File

@ -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')
}
}

View File

@ -0,0 +1,9 @@
export interface AppSettings {
persistSelectedTheme: boolean,
selectedTheme: AppThemes
}
export enum AppThemes {
light = 'light',
dark = 'dark'
}

View 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()
}
}

View File

@ -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() {

View File

@ -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) {

View File

@ -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;
}
} }

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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 {}

View File

@ -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
View File

@ -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",

View File

@ -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"
} }
} }