feat: added app settings service to handle theme persistance, fix: optimised dark mode contrast
This commit is contained in:
		@@ -14,9 +14,9 @@ import { DcAdapterSettings } from './models/DcAdapterSettings'
 | 
			
		||||
import { AppStoreService } from './services/app-store.service'
 | 
			
		||||
import { LicenceService } from './services/licence.service'
 | 
			
		||||
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({
 | 
			
		||||
  selector: 'my-app',
 | 
			
		||||
 
 | 
			
		||||
@@ -20,10 +20,10 @@ import { UsernavRouteComponent } from './routes/usernav-route/usernav-route.comp
 | 
			
		||||
import { AppService } from './services/app.service'
 | 
			
		||||
import { InfoModalComponent } from './shared/abort-modal/info-modal.component'
 | 
			
		||||
import { RequestsModalComponent } from './shared/requests-modal/requests-modal.component'
 | 
			
		||||
import { HomeModule } from './home/home.module'
 | 
			
		||||
import { DirectivesModule } from './directives/directives.module'
 | 
			
		||||
import { ViyaApiExplorerComponent } from './viya-api-explorer/viya-api-explorer.component'
 | 
			
		||||
import { NgxJsonViewerModule } from 'ngx-json-viewer'
 | 
			
		||||
import { AppSettingsService } from './services/app-settings.service'
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@@ -50,7 +50,7 @@ import { NgxJsonViewerModule } from 'ngx-json-viewer'
 | 
			
		||||
    DirectivesModule,
 | 
			
		||||
    NgxJsonViewerModule
 | 
			
		||||
  ],
 | 
			
		||||
  providers: [AppService, SasStoreService, LicensingGuard],
 | 
			
		||||
  providers: [AppService, SasStoreService, LicensingGuard, AppSettingsService],
 | 
			
		||||
  bootstrap: [AppComponent]
 | 
			
		||||
})
 | 
			
		||||
export class AppModule {}
 | 
			
		||||
 
 | 
			
		||||
@@ -2769,7 +2769,6 @@ export class EditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
 | 
			
		||||
        this.currentEditRecordLoadings.push(column)
 | 
			
		||||
        hot.render()
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
        this.sasService
 | 
			
		||||
          .request('editors/getdynamiccolvals', data, undefined, {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,11 @@ export const errorRenderer = (
 | 
			
		||||
  value: any,
 | 
			
		||||
  cellProperties: any
 | 
			
		||||
) => {
 | 
			
		||||
  addDarkClass(td)
 | 
			
		||||
 | 
			
		||||
  td.innerHTML = `${
 | 
			
		||||
    value ? value.toString() : ''
 | 
			
		||||
  } <clr-icon shape="exclamation-circle" status="warning"></clr-icon>`
 | 
			
		||||
  } <cds-icon shape="exclamation-triangle" status="warning"></cds-icon>`
 | 
			
		||||
 | 
			
		||||
  return td
 | 
			
		||||
}
 | 
			
		||||
@@ -31,6 +33,8 @@ export const noSpinnerRenderer = (
 | 
			
		||||
  value: any,
 | 
			
		||||
  cellProperties: any
 | 
			
		||||
) => {
 | 
			
		||||
  addDarkClass(td)
 | 
			
		||||
 | 
			
		||||
  td.innerHTML = value ? value : ''
 | 
			
		||||
 | 
			
		||||
  return td
 | 
			
		||||
@@ -50,10 +54,20 @@ export const spinnerRenderer = (
 | 
			
		||||
  value: any,
 | 
			
		||||
  cellProperties: any
 | 
			
		||||
) => {
 | 
			
		||||
  td.classList.add('htDark')
 | 
			
		||||
  addDarkClass(td)
 | 
			
		||||
 | 
			
		||||
  td.innerHTML = `${
 | 
			
		||||
    value ? value.toString() : ''
 | 
			
		||||
  } <span class="spinner spinner-sm vertical-align-middle"></span>`
 | 
			
		||||
 | 
			
		||||
  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 { EnvironmentInfo } from '../system/models/environment-info.model'
 | 
			
		||||
import { LicenceService } from './licence.service'
 | 
			
		||||
import { AppSettingsService } from './app-settings.service'
 | 
			
		||||
import { AppThemes } from '../models/AppSettings'
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class AppService {
 | 
			
		||||
@@ -18,6 +20,7 @@ export class AppService {
 | 
			
		||||
    private eventService: EventService,
 | 
			
		||||
    private sasService: SasService,
 | 
			
		||||
    private loggerService: LoggerService,
 | 
			
		||||
    private appSettingsService: AppSettingsService,
 | 
			
		||||
    private router: Router
 | 
			
		||||
  ) {
 | 
			
		||||
    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() {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@ import { Injectable, Output, EventEmitter } from '@angular/core'
 | 
			
		||||
import { InfoModal, AbortDetails } from '../models/InfoModal'
 | 
			
		||||
import { AlertsService } from '../shared/alerts/alerts.service'
 | 
			
		||||
import { BehaviorSubject } from 'rxjs'
 | 
			
		||||
import { AppSettingsService } from './app-settings.service'
 | 
			
		||||
import { AppThemes } from '../models/AppSettings'
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root'
 | 
			
		||||
@@ -20,7 +22,9 @@ export class EventService {
 | 
			
		||||
 | 
			
		||||
  public darkMode: BehaviorSubject<boolean> = new BehaviorSubject(false)
 | 
			
		||||
 | 
			
		||||
  constructor(private alertsService: AlertsService) {}
 | 
			
		||||
  constructor(
 | 
			
		||||
    private appSettingsService: AppSettingsService
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  toggleDarkMode(value: boolean) {
 | 
			
		||||
    this.darkMode.next(value)
 | 
			
		||||
@@ -30,6 +34,10 @@ export class EventService {
 | 
			
		||||
    } else {
 | 
			
		||||
      document.body.setAttribute('cds-theme', 'light')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.appSettingsService.setAppSettings({
 | 
			
		||||
      selectedTheme: value ? AppThemes.dark : AppThemes.light
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public showDemoLimitModal(featureName: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,3 +13,9 @@
 | 
			
		||||
  margin-top:10px;
 | 
			
		||||
  color: #007cbb;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::ng-deep body[cds-theme="dark"] {
 | 
			
		||||
  .baseTableLink {
 | 
			
		||||
    color: #4ec0ff;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -194,6 +194,16 @@
 | 
			
		||||
 | 
			
		||||
      <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">
 | 
			
		||||
        <div *ngIf="serverType === 'SAS9'" class="admin-action">
 | 
			
		||||
          Refresh Data Lineage
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-action {
 | 
			
		||||
.admin-action, .user-action {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@ import { LicenceService } from '../services/licence.service'
 | 
			
		||||
import { SasService } from '../services/sas.service'
 | 
			
		||||
import { AppInfo } from './models/app-info.model'
 | 
			
		||||
import { EnvironmentInfo } from './models/environment-info.model'
 | 
			
		||||
import { AppSettingsService } from '../services/app-settings.service'
 | 
			
		||||
import { AppSettings } from '../models/AppSettings'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-system',
 | 
			
		||||
@@ -36,25 +38,36 @@ export class SystemComponent implements OnInit {
 | 
			
		||||
  Infinity = Infinity
 | 
			
		||||
 | 
			
		||||
  licenceState = this.licenceService.licenceState
 | 
			
		||||
  settings: AppSettings
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private appService: AppService,
 | 
			
		||||
    private sasService: SasService,
 | 
			
		||||
    private licenceService: LicenceService
 | 
			
		||||
    private licenceService: LicenceService,
 | 
			
		||||
    private appSettingsService: AppSettingsService
 | 
			
		||||
  ) {
 | 
			
		||||
    this.serverType = this.sasService.getServerType()
 | 
			
		||||
    this.licenceInfo = this.licenceService.getLicenseKeyData()
 | 
			
		||||
    this.environmentInfo = this.appService.getEnvironmentInfo()
 | 
			
		||||
    this.settings = this.appSettingsService.settings.value
 | 
			
		||||
 | 
			
		||||
    if (this.environmentInfo) {
 | 
			
		||||
      this.environmentInfo.AUTOEXEC = decodeURIComponent(
 | 
			
		||||
        this.environmentInfo.AUTOEXEC
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.appSettingsService.settings.subscribe((settings: AppSettings) => {
 | 
			
		||||
      this.settings = settings
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
 | 
			
		||||
  settingChange(event: Event) {
 | 
			
		||||
    this.appSettingsService.setAppSettings(this.settings)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  downloadConfiguration() {
 | 
			
		||||
    let sasjsConfig = this.sasService.getSasjsConfig()
 | 
			
		||||
    let storage = sasjsConfig.serverUrl
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,15 @@ import { CommonModule } from '@angular/common'
 | 
			
		||||
import { SystemRoutingModule } from './system-routing.module'
 | 
			
		||||
import { SystemComponent } from './system.component'
 | 
			
		||||
import { ClarityModule } from '@clr/angular'
 | 
			
		||||
import { FormsModule } from '@angular/forms'
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [SystemComponent],
 | 
			
		||||
  imports: [CommonModule, SystemRoutingModule, ClarityModule]
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
    SystemRoutingModule,
 | 
			
		||||
    ClarityModule,
 | 
			
		||||
    FormsModule
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
export class SystemModule {}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,10 @@ body[cds-theme="dark"] {
 | 
			
		||||
    // Track
 | 
			
		||||
    border: 3px solid $trackColor;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clr-icon.is-highlight {
 | 
			
		||||
    fill: #4ec0ff;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// body::-webkit-scrollbar {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								sas/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								sas/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -7,7 +7,7 @@
 | 
			
		||||
      "name": "dc-sas",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@sasjs/cli": "^4.11.1",
 | 
			
		||||
        "@sasjs/core": "^4.52.1"
 | 
			
		||||
        "@sasjs/core": "^4.52.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@coolaj86/urequest": {
 | 
			
		||||
@@ -116,9 +116,9 @@
 | 
			
		||||
      "integrity": "sha512-Grwydm5GxBsYk238PZw41XPjXVVQ9vWcvfZ06L2P0bQbvK0sGn7l69JA7H5MGr3QcaLpiD4Kg70cAh7PgE+JOw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@sasjs/core": {
 | 
			
		||||
      "version": "4.52.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.1.tgz",
 | 
			
		||||
      "integrity": "sha512-XPNuKD1T5XLGMKg4Ll3KggOTjhHgnjdbefpwajpfro/8/9bJK7lyNehzUCcmbhJnijJbbChE7drIOF+uSaVxVg=="
 | 
			
		||||
      "version": "4.52.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.4.tgz",
 | 
			
		||||
      "integrity": "sha512-8lf5ixlA312EgA2DorwbpNXXPfLPzUHO67exIV7SjKiU23Tn1au5GD6hT0Ysr2kophOs10Mp1TCXJjhEq7Qk4A=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@sasjs/lint": {
 | 
			
		||||
      "version": "2.3.1",
 | 
			
		||||
@@ -1834,9 +1834,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@sasjs/core": {
 | 
			
		||||
      "version": "4.52.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.1.tgz",
 | 
			
		||||
      "integrity": "sha512-XPNuKD1T5XLGMKg4Ll3KggOTjhHgnjdbefpwajpfro/8/9bJK7lyNehzUCcmbhJnijJbbChE7drIOF+uSaVxVg=="
 | 
			
		||||
      "version": "4.52.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.52.4.tgz",
 | 
			
		||||
      "integrity": "sha512-8lf5ixlA312EgA2DorwbpNXXPfLPzUHO67exIV7SjKiU23Tn1au5GD6hT0Ysr2kophOs10Mp1TCXJjhEq7Qk4A=="
 | 
			
		||||
    },
 | 
			
		||||
    "@sasjs/lint": {
 | 
			
		||||
      "version": "2.3.1",
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,6 @@
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@sasjs/cli": "^4.11.1",
 | 
			
		||||
    "@sasjs/core": "^4.52.1"
 | 
			
		||||
    "@sasjs/core": "^4.52.4"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user