From a48602d34b0b272d3a9f42c478d7c03f0cef715a Mon Sep 17 00:00:00 2001 From: "Felix T.J. Dietrich" Date: Sun, 11 Aug 2024 20:10:31 +0200 Subject: [PATCH 1/5] Add theme switcher to header (#45) --- webapp/.storybook/preview.ts | 6 +- webapp/src/app/app.component.html | 10 ++- webapp/src/app/app.component.ts | 4 +- webapp/src/app/app.config.ts | 6 +- .../theme-switcher.component.html | 5 ++ .../theme-switcher.component.ts | 30 ++++++++ .../theme-switcher/theme-switcher.service.ts | 71 +++++++++++++++++++ .../theme-switcher/theme-switcher.stories.ts | 26 +++++++ webapp/src/index.html | 14 ++++ 9 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 webapp/src/app/components/theme-switcher/theme-switcher.component.html create mode 100644 webapp/src/app/components/theme-switcher/theme-switcher.component.ts create mode 100644 webapp/src/app/components/theme-switcher/theme-switcher.service.ts create mode 100644 webapp/src/app/components/theme-switcher/theme-switcher.stories.ts diff --git a/webapp/.storybook/preview.ts b/webapp/.storybook/preview.ts index bd976444..f7573730 100644 --- a/webapp/.storybook/preview.ts +++ b/webapp/.storybook/preview.ts @@ -1,8 +1,9 @@ -import { Preview } from '@storybook/angular'; +import { applicationConfig, Preview } from '@storybook/angular'; import { withThemeByClassName } from '@storybook/addon-themes'; import { DocsContainer } from '@storybook/blocks'; import { createElement } from 'react'; import { themes } from '@storybook/core/theming'; +import { appConfig } from 'app/app.config'; const preview: Preview = { parameters: { @@ -54,8 +55,9 @@ const preview: Preview = { light: '', dark: 'dark bg-background', }, - defaultTheme: 'light', + defaultTheme: 'dark', }), + applicationConfig(appConfig), ], }; diff --git a/webapp/src/app/app.component.html b/webapp/src/app/app.component.html index 460bba46..9b87933d 100644 --- a/webapp/src/app/app.component.html +++ b/webapp/src/app/app.component.html @@ -1,6 +1,10 @@ -
-
-

Header

+
+
+ + + Hephaestus + +
diff --git a/webapp/src/app/app.component.ts b/webapp/src/app/app.component.ts index 513811ab..b75100bd 100644 --- a/webapp/src/app/app.component.ts +++ b/webapp/src/app/app.component.ts @@ -2,11 +2,13 @@ import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { CounterComponent } from './example/counter/counter.component'; import { HelloComponent } from './example/hello/hello.component'; +import { ThemeSwitcherComponent } from './components/theme-switcher/theme-switcher.component'; +import { LucideAngularModule } from 'lucide-angular'; @Component({ selector: 'app-root', standalone: true, - imports: [RouterOutlet, CounterComponent, HelloComponent], + imports: [RouterOutlet, CounterComponent, HelloComponent, LucideAngularModule, ThemeSwitcherComponent], templateUrl: './app.component.html', styles: [] }) diff --git a/webapp/src/app/app.config.ts b/webapp/src/app/app.config.ts index c3b646d2..515151a9 100644 --- a/webapp/src/app/app.config.ts +++ b/webapp/src/app/app.config.ts @@ -1,8 +1,9 @@ import { ApplicationConfig, importProvidersFrom, provideExperimentalZonelessChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { provideAngularQuery, QueryClient } from '@tanstack/angular-query-experimental'; -import { LucideAngularModule, Home } from 'lucide-angular'; +import { LucideAngularModule, Home, Sun, Moon, Hammer } from 'lucide-angular'; import { routes } from './app.routes'; import { BASE_PATH } from './core/modules/openapi'; @@ -13,6 +14,7 @@ export const appConfig: ApplicationConfig = { provideAngularQuery(new QueryClient()), { provide: BASE_PATH, useValue: 'http://localhost:8080' }, provideHttpClient(withInterceptorsFromDi()), - importProvidersFrom(LucideAngularModule.pick({ Home })) + provideAnimationsAsync(), + importProvidersFrom(LucideAngularModule.pick({ Home, Sun, Moon, Hammer })) ] }; diff --git a/webapp/src/app/components/theme-switcher/theme-switcher.component.html b/webapp/src/app/components/theme-switcher/theme-switcher.component.html new file mode 100644 index 00000000..f2ca7226 --- /dev/null +++ b/webapp/src/app/components/theme-switcher/theme-switcher.component.html @@ -0,0 +1,5 @@ + +
+ +
+
diff --git a/webapp/src/app/components/theme-switcher/theme-switcher.component.ts b/webapp/src/app/components/theme-switcher/theme-switcher.component.ts new file mode 100644 index 00000000..86e285b3 --- /dev/null +++ b/webapp/src/app/components/theme-switcher/theme-switcher.component.ts @@ -0,0 +1,30 @@ +import { Component, inject } from '@angular/core'; +import { LucideAngularModule } from 'lucide-angular'; +import { ButtonComponent } from 'app/ui/button/button.component'; +import { AppTheme, ThemeSwitcherService } from './theme-switcher.service'; +import { animate, state, style, transition, trigger } from '@angular/animations'; + +@Component({ + selector: 'app-theme-switcher', + standalone: true, + imports: [ButtonComponent, LucideAngularModule], + templateUrl: './theme-switcher.component.html', + animations: [ + trigger('iconTrigger', [ + state('*', style({ transform: 'rotate(0deg)' })), + transition('light => dark', animate('0.25s ease-out', style({ transform: 'rotate(90deg)' }))), + transition('dark => light', animate('0.25s ease-out', style({ transform: 'rotate(360deg)' }))) + ]) + ] +}) +export class ThemeSwitcherComponent { + themeSwitcherService = inject(ThemeSwitcherService); + + toggleTheme() { + if (this.themeSwitcherService.currentTheme() === AppTheme.DARK) { + this.themeSwitcherService.setLightTheme(); + } else { + this.themeSwitcherService.setDarkTheme(); + } + } +} diff --git a/webapp/src/app/components/theme-switcher/theme-switcher.service.ts b/webapp/src/app/components/theme-switcher/theme-switcher.service.ts new file mode 100644 index 00000000..40d58eb2 --- /dev/null +++ b/webapp/src/app/components/theme-switcher/theme-switcher.service.ts @@ -0,0 +1,71 @@ +import { Injectable, signal } from '@angular/core'; + +export enum AppTheme { + LIGHT = 'light', + DARK = 'dark' +} + +const IS_CLIENT_RENDER = typeof localStorage !== 'undefined'; +const LOCAL_STORAGE_THEME_KEY = 'theme'; + +let selectedTheme: AppTheme | undefined = undefined; + +if (IS_CLIENT_RENDER) { + selectedTheme = (localStorage.getItem(LOCAL_STORAGE_THEME_KEY) as AppTheme) || undefined; +} + +@Injectable({ + providedIn: 'root' +}) +export class ThemeSwitcherService { + currentTheme = signal(selectedTheme); + + setLightTheme() { + this.currentTheme.set(AppTheme.LIGHT); + this.setToLocalStorage(AppTheme.LIGHT); + this.removeClassFromHtml('dark'); + } + + setDarkTheme() { + this.currentTheme.set(AppTheme.DARK); + this.setToLocalStorage(AppTheme.DARK); + this.addClassToHtml('dark'); + } + + setSystemTheme() { + this.currentTheme.set(undefined); + this.removeFromLocalStorage(); + + const isSystemDark = window?.matchMedia('(prefers-color-scheme: dark)').matches ?? false; + if (isSystemDark) { + this.addClassToHtml('dark'); + } else { + this.removeClassFromHtml('dark'); + } + } + + private addClassToHtml(className: string) { + if (IS_CLIENT_RENDER) { + this.removeClassFromHtml(className); + document.documentElement.classList.add(className); + } + } + + private removeClassFromHtml(className: string) { + if (IS_CLIENT_RENDER) { + document.documentElement.classList.remove(className); + } + } + + private setToLocalStorage(theme: AppTheme) { + if (IS_CLIENT_RENDER) { + localStorage.setItem(LOCAL_STORAGE_THEME_KEY, theme); + } + } + + private removeFromLocalStorage() { + if (IS_CLIENT_RENDER) { + localStorage.removeItem(LOCAL_STORAGE_THEME_KEY); + } + } +} diff --git a/webapp/src/app/components/theme-switcher/theme-switcher.stories.ts b/webapp/src/app/components/theme-switcher/theme-switcher.stories.ts new file mode 100644 index 00000000..527dde1c --- /dev/null +++ b/webapp/src/app/components/theme-switcher/theme-switcher.stories.ts @@ -0,0 +1,26 @@ +import { moduleMetadata, type Meta, type StoryObj } from '@storybook/angular'; +import { LucideAngularModule, Sun, Moon } from 'lucide-angular'; +import { ThemeSwitcherComponent } from './theme-switcher.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories +const meta: Meta = { + title: 'Components/ThemeSwitcher', + component: ThemeSwitcherComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [LucideAngularModule.pick({ Sun, Moon }), BrowserAnimationsModule] + }) + ] +}; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Default: Story = { + render: () => ({ + template: `` + }) +}; diff --git a/webapp/src/index.html b/webapp/src/index.html index 755019f9..efa75a11 100644 --- a/webapp/src/index.html +++ b/webapp/src/index.html @@ -7,6 +7,20 @@ + From 66b939da37da60e8ddfdfbfe64da71229abad591 Mon Sep 17 00:00:00 2001 From: "Felix T.J. Dietrich" Date: Sun, 11 Aug 2024 20:50:15 +0200 Subject: [PATCH 2/5] Add PR workflows for assigning PR author and labeling (#48) --- .github/labeler.yml | 21 +++++++++++ .github/workflows/pr-labeler.yml | 56 ++++++++++++++++++++++++++++++ .github/workflows/pull-request.yml | 11 ++++++ 3 files changed, 88 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/pr-labeler.yml create mode 100644 .github/workflows/pull-request.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..34193423 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,21 @@ +documentation: + - changed-files: + - any-glob-to-any-file: docs/** + +client: + - changed-files: + - any-glob-to-any-file: webapp/** + +application-server: + - changed-files: + - any-glob-to-any-file: server/application-server/** + +intelligence-service: + - changed-files: + - any-glob-to-any-file: "server/intelligence-service/**" + +feature: + - head-branch: ['^feature', 'feature', '^feat', 'feat'] + +bug: + - head-branch: ['^fix', 'fix', '^bug', 'bug'] \ No newline at end of file diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 00000000..a9915bff --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,56 @@ +name: "Pull Request Labeler" +on: + pull_request_target: + +jobs: + labeler: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/labeler@v5 + with: + configuration-path: .github/labeler.yml + + - name: Calculate the PR size + uses: actions/github-script@v7 + id: calculate-size + with: + script: | + const diff = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + mediaType: { + format: "diff", + }, + }); + + const changedLines = diff.data + .split("\n") + .filter(line => line.startsWith('+') || line.startsWith('-')) + .length; + + let label = ''; + if (changedLines > 1000) label = 'size:XXL'; + else if (changedLines > 499) label = 'size:XL'; + else if (changedLines > 99) label = 'size:L'; + else if (changedLines > 29) label = 'size:M'; + else if (changedLines > 9) label = 'size:S'; + else label = 'size:XS'; + + return label; + + - name: Apply size label + uses: actions/github-script@v7 + if: steps.calculate-size.outputs.result + with: + script: | + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [ "${{steps.calculate-size.outputs.result}}" ] + }) + diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 00000000..dd87882d --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,11 @@ +on: + pull_request: + types: [opened] +name: Pull Request +jobs: + assignAuthor: + name: Assign author to PR + runs-on: ubuntu-latest + steps: + - name: Assign author to PR + uses: technote-space/assign-author@v1 \ No newline at end of file From 3a310d465b0ba303458119cf38045abf212a7829 Mon Sep 17 00:00:00 2001 From: "Felix T.J. Dietrich" Date: Sun, 11 Aug 2024 20:57:07 +0200 Subject: [PATCH 3/5] Fix labeler workflow (#50) --- .github/workflows/pr-labeler.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index a9915bff..213b1b2b 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -40,17 +40,19 @@ jobs: else if (changedLines > 9) label = 'size:S'; else label = 'size:XS'; - return label; + core.setOutput("size-label", label); - name: Apply size label uses: actions/github-script@v7 - if: steps.calculate-size.outputs.result with: script: | - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - labels: [ "${{steps.calculate-size.outputs.result}}" ] - }) + const label = core.getInput('size-label'); + if (label) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [label] + }); + } From 7472072a4794b97a610c5f7524ca667e1c3f0b19 Mon Sep 17 00:00:00 2001 From: "Felix T.J. Dietrich" Date: Sun, 11 Aug 2024 21:04:00 +0200 Subject: [PATCH 4/5] add workflow logging --- .github/workflows/pr-labeler.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 213b1b2b..5f928441 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -4,6 +4,7 @@ on: jobs: labeler: + name: "Apply labels" runs-on: ubuntu-latest permissions: contents: read @@ -18,6 +19,7 @@ jobs: id: calculate-size with: script: | + console.log("Fetching pull request diff..."); const diff = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, @@ -26,11 +28,13 @@ jobs: format: "diff", }, }); + console.log("Pull request diff fetched successfully."); const changedLines = diff.data .split("\n") .filter(line => line.startsWith('+') || line.startsWith('-')) .length; + console.log(`Number of changed lines: ${changedLines}`); let label = ''; if (changedLines > 1000) label = 'size:XXL'; @@ -40,6 +44,7 @@ jobs: else if (changedLines > 9) label = 'size:S'; else label = 'size:XS'; + console.log(`Assigned label: ${label}`); core.setOutput("size-label", label); - name: Apply size label @@ -47,12 +52,18 @@ jobs: with: script: | const label = core.getInput('size-label'); + console.log(`Retrieved label from previous step: ${label}`); + if (label) { + console.log(`Applying label "${label}" to the pull request...`); await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, labels: [label] }); + console.log(`Label "${label}" applied successfully.`); + } else { + console.log("No label to apply."); } From 781a6c7e59d42d9060e62e9b10cbad9121a51515 Mon Sep 17 00:00:00 2001 From: "Felix T.J. Dietrich" Date: Sun, 11 Aug 2024 21:08:51 +0200 Subject: [PATCH 5/5] fix labeler workflow --- .github/workflows/pr-labeler.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 5f928441..f1954abc 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -10,13 +10,15 @@ jobs: contents: read pull-requests: write steps: + - name: "Checkout repository" + uses: actions/checkout@v4 + - uses: actions/labeler@v5 with: configuration-path: .github/labeler.yml - - name: Calculate the PR size + - name: "Apply size label" uses: actions/github-script@v7 - id: calculate-size with: script: | console.log("Fetching pull request diff..."); @@ -44,16 +46,6 @@ jobs: else if (changedLines > 9) label = 'size:S'; else label = 'size:XS'; - console.log(`Assigned label: ${label}`); - core.setOutput("size-label", label); - - - name: Apply size label - uses: actions/github-script@v7 - with: - script: | - const label = core.getInput('size-label'); - console.log(`Retrieved label from previous step: ${label}`); - if (label) { console.log(`Applying label "${label}" to the pull request...`); await github.rest.issues.addLabels({