This commit is contained in:
parent
459deb255d
commit
50056356f3
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -32,5 +32,6 @@
|
|||||||
"**/node_modules": true
|
"**/node_modules": true
|
||||||
},
|
},
|
||||||
"search.useIgnoreFiles": false,
|
"search.useIgnoreFiles": false,
|
||||||
"nxConsole.generateAiAgentRules": true
|
"nxConsole.generateAiAgentRules": true,
|
||||||
|
"nxConsole.nxWorkspacePath": "frontend"
|
||||||
}
|
}
|
||||||
|
|||||||
7
frontend/libs/shared/theme-toggle/README.md
Normal file
7
frontend/libs/shared/theme-toggle/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# theme-toggle
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test theme-toggle` to execute the unit tests.
|
||||||
34
frontend/libs/shared/theme-toggle/eslint.config.mjs
Normal file
34
frontend/libs/shared/theme-toggle/eslint.config.mjs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import nx from '@nx/eslint-plugin';
|
||||||
|
import baseConfig from '../../../eslint.base.config.mjs';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...baseConfig,
|
||||||
|
...nx.configs['flat/angular'],
|
||||||
|
...nx.configs['flat/angular-template'],
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'lib',
|
||||||
|
style: 'camelCase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'lib',
|
||||||
|
style: 'kebab-case',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.html'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
21
frontend/libs/shared/theme-toggle/jest.config.ts
Normal file
21
frontend/libs/shared/theme-toggle/jest.config.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
displayName: 'theme-toggle',
|
||||||
|
preset: '../../../jest.preset.js',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||||
|
coverageDirectory: '../../../coverage/libs/shared/theme-toggle',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
'jest-preset-angular',
|
||||||
|
{
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||||
|
snapshotSerializers: [
|
||||||
|
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||||
|
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||||
|
'jest-preset-angular/build/serializers/html-comment',
|
||||||
|
],
|
||||||
|
};
|
||||||
20
frontend/libs/shared/theme-toggle/project.json
Normal file
20
frontend/libs/shared/theme-toggle/project.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "theme-toggle",
|
||||||
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/shared/theme-toggle/src",
|
||||||
|
"prefix": "lib",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/shared/theme-toggle/jest.config.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/eslint:lint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
frontend/libs/shared/theme-toggle/src/index.ts
Normal file
1
frontend/libs/shared/theme-toggle/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './lib/theme-toggle.service';
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||||
|
import { computed, DOCUMENT, effect, inject, Injectable } from '@angular/core';
|
||||||
|
import { stored } from '@mmstack/primitives';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ThemeToggleService {
|
||||||
|
private readonly overlayCls =
|
||||||
|
inject(OverlayContainer).getContainerElement().classList;
|
||||||
|
private readonly dockument = inject(DOCUMENT);
|
||||||
|
|
||||||
|
private readonly theme = stored<'light' | 'dark'>('light', {
|
||||||
|
key: 'user-theme',
|
||||||
|
syncTabs: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
public isDark = computed(() => this.theme() === 'dark');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const prefersDark =
|
||||||
|
window.matchMedia &&
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
if (prefersDark && this.theme() === 'light') {
|
||||||
|
this.theme.set('dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
effect(() =>
|
||||||
|
this.isDark() ? this.addDarkThemeClass() : this.removeDarkThemeClass()
|
||||||
|
);
|
||||||
|
|
||||||
|
effect(() =>
|
||||||
|
this.dockument.body.setAttribute(
|
||||||
|
'style',
|
||||||
|
`color-scheme: ${this.isDark() ? 'dark' : 'light'}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleTheme() {
|
||||||
|
this.theme.set(this.isDark() ? 'light' : 'dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
private addDarkThemeClass() {
|
||||||
|
this.overlayCls.add('dark-theme');
|
||||||
|
this.dockument.body.classList.add('dark-theme');
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeDarkThemeClass() {
|
||||||
|
this.overlayCls.remove('dark-theme');
|
||||||
|
this.dockument.body.classList.remove('dark-theme');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
6
frontend/libs/shared/theme-toggle/src/test-setup.ts
Normal file
6
frontend/libs/shared/theme-toggle/src/test-setup.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||||
|
|
||||||
|
setupZoneTestEnv({
|
||||||
|
errorOnUnknownElements: true,
|
||||||
|
errorOnUnknownProperties: true,
|
||||||
|
});
|
||||||
28
frontend/libs/shared/theme-toggle/tsconfig.json
Normal file
28
frontend/libs/shared/theme-toggle/tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2022",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
17
frontend/libs/shared/theme-toggle/tsconfig.lib.json
Normal file
17
frontend/libs/shared/theme-toggle/tsconfig.lib.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/test-setup.ts",
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.test.ts"
|
||||||
|
],
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
16
frontend/libs/shared/theme-toggle/tsconfig.spec.json
Normal file
16
frontend/libs/shared/theme-toggle/tsconfig.spec.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2016",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"files": ["src/test-setup.ts"],
|
||||||
|
"include": [
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,35 +1,74 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { StatsWsService } from '@frontend/shared/stats-ws';
|
import { StatsWsService } from '@frontend/shared/stats-ws';
|
||||||
|
import { ThemeToggleService } from '@frontend/shared/theme-toggle';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterModule, CommonModule],
|
imports: [
|
||||||
|
RouterModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatToolbarModule,
|
||||||
|
CommonModule,
|
||||||
|
],
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: `
|
template: `
|
||||||
<div>
|
<mat-toolbar>
|
||||||
<h1>Docker Containers</h1>
|
<span>Docker Containers</span>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<button mat-icon-button (click)="switchTheme()">
|
||||||
|
<mat-icon>{{ isDarkTheme() ? 'light_mode' : 'dark_mode' }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-toolbar>
|
||||||
|
|
||||||
|
<div class="content-container">
|
||||||
@for (container of containers(); track container.id) {
|
@for (container of containers(); track container.id) {
|
||||||
<div class="container">
|
<mat-card class="container">
|
||||||
<h2>{{ container.name }}</h2>
|
<mat-card-header>
|
||||||
<p>ID: {{ container.id }}</p>
|
<mat-card-title>{{ container.name }}</mat-card-title>
|
||||||
<p>Image: {{ container.image }}</p>
|
</mat-card-header>
|
||||||
<p>Status: {{ container.status }}</p>
|
<mat-card-content>
|
||||||
<p>State: {{ container.state }}</p>
|
<p>ID: {{ container.id }}</p>
|
||||||
</div>
|
<p>Image: {{ container.image }}</p>
|
||||||
|
<p>Status: {{ container.status }}</p>
|
||||||
|
<p>State: {{ container.state }}</p>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
`,
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.content-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
private readonly service = inject(StatsWsService);
|
private readonly service = inject(StatsWsService);
|
||||||
|
private readonly themeToggleService = inject(ThemeToggleService);
|
||||||
protected containers = this.service.containers;
|
protected containers = this.service.containers;
|
||||||
|
|
||||||
|
// Add this to check current theme for the template
|
||||||
|
protected isDarkTheme = () => this.themeToggleService.isDark();
|
||||||
|
|
||||||
|
switchTheme() {
|
||||||
|
this.themeToggleService.toggleTheme();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.service.initContainers();
|
this.service.initContainers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1 +1,16 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 100vw;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Roboto, 'Helvetica Neue', sans-serif;
|
||||||
|
background-color: #fafafa;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-theme {
|
||||||
|
background-color: #303030;
|
||||||
|
}
|
||||||
|
|||||||
@ -16,7 +16,8 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@frontend/shared/environment": ["libs/shared/environment/src/index.ts"],
|
"@frontend/shared/environment": ["libs/shared/environment/src/index.ts"],
|
||||||
"@frontend/shared/stats-ws": ["libs/shared/stats-ws/src/index.ts"]
|
"@frontend/shared/stats-ws": ["libs/shared/stats-ws/src/index.ts"],
|
||||||
|
"@frontend/shared/theme-toggle": ["libs/shared/theme-toggle/src/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "tmp"]
|
"exclude": ["node_modules", "tmp"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user