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
|
||||
},
|
||||
"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 { 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 { StatsWsService } from '@frontend/shared/stats-ws';
|
||||
import { ThemeToggleService } from '@frontend/shared/theme-toggle';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [RouterModule, CommonModule],
|
||||
imports: [
|
||||
RouterModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatIconModule,
|
||||
MatToolbarModule,
|
||||
CommonModule,
|
||||
],
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<div>
|
||||
<h1>Docker Containers</h1>
|
||||
<mat-toolbar>
|
||||
<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) {
|
||||
<div class="container">
|
||||
<h2>{{ container.name }}</h2>
|
||||
<mat-card class="container">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ container.name }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>ID: {{ container.id }}</p>
|
||||
<p>Image: {{ container.image }}</p>
|
||||
<p>Status: {{ container.status }}</p>
|
||||
<p>State: {{ container.state }}</p>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
.spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.content-container {
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
],
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
private readonly service = inject(StatsWsService);
|
||||
private readonly themeToggleService = inject(ThemeToggleService);
|
||||
protected containers = this.service.containers;
|
||||
|
||||
// Add this to check current theme for the template
|
||||
protected isDarkTheme = () => this.themeToggleService.isDark();
|
||||
|
||||
switchTheme() {
|
||||
this.themeToggleService.toggleTheme();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
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": ".",
|
||||
"paths": {
|
||||
"@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"]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user